2016-02-01 36 views
0

为了让新纹理无需锁定即可流入WebGL主UI线程中,我们使用emscripten和asm.js编译了libjpeg和一个实时DXT纹理压缩器到JavaScript,并在单个webworker中运行它们。在Emscripten'd C++程序中处理完数据之后,如何在从Webworker发回数据时如何解决“内存不足”错误

在6年前处理2048x2048 Jpeg源图像的笔记本电脑上,我们在约300ms内解码每个jpeg,然后在230ms左右将它们压缩为DXT1压缩纹理格式。尽管我们确信它可以有所改进,但这对我们的需求来说已经足够了。

但是,我们遇到的问题是,从webworker反序列化返回的数据仍然会导致主UI线程挂起。考虑到每个返回的DTX1文件是2MB,这并不令人意外。为了弥补这一点,我们打算使用webworker可转移对象将数据发送回去,从而允许将产生的ArrayBuffer简单地转移到主线程而不需要被复制。

但是,每当我们尝试这样做时,我们都会在postMessage调用中收到InternalError: out of memory错误。

这是我们呼叫DXT压缩机并发回的方式。 (this.decoded仅仅是解码JPEG文件的Uint8Array和可变STB原始RGBA数据存取我们的DXT压缩机的emscripten'd版)

ImageDecoder.prototype._compressDXT = function(){ 
    console.log('COMPRESS DXT'); 

    var start = Date.now(); 
    var srcSize = this.decoded.length*this.decoded.BYTES_PER_ELEMENT; 
    var inputPtr = STB._malloc(srcSize); 
    var outputPtr = STB._malloc(srcSize/8); 
    var inputHeap = new Uint8Array(STB.HEAPU8.buffer, inputPtr, srcSize); 
    var outputHeap = new Uint8Array(STB.HEAPU8.buffer, outputPtr, srcSize/8); 

    //set inputHeap to jpeg decoded RGBA data 
    inputHeap.set(this.decoded); 


    //compress data to DXT1 
    STB.ccall('rygCompress', null, ['number', 'number', 'number', 'number'], 
     [outputPtr, inputPtr, 2048, 2048]); 


    var result = new Uint8Array(outputHeap.buffer, outputHeap.byteOffset, outputHeap.length); 

    STB._free(inputHeap.byteOffset); 
    STB._free(outputHeap.byteOffset); 

    console.log('FINAL SIZE: ' + result.length*result.BYTES_PER_ELEMENT); 
    console.log('compressed in: ' + (Date.now() - start) + 'ms'); 

    //send back to main thread 
    postMessage({ 
     complete: true, 
     result: result 
    }, [result.buffer]); 


    //perform clean up 
    this._cleanUp(); 

} 

第二,我们删除transferable列表并更改postMessage调用如下:

postMessage({ 
    complete: true, 
    result: result 
}); 

然后一切都按预期工作。我只能假设这是由于我们对emscripten的经验不足,并且我们在_malloc,_free和我们的typedArraysArrayBuffers中做错了。但是,到目前为止,我们还没有能够弄清楚我们会出错的地方。

任何帮助将不胜感激。

+0

'postMessage'上是否发生错误,而不是'this._cleanUp()'? – zakki

+0

是的,在'''postMessage'''但是我找到了原因,并会回答我自己的问题。 – gordyr

回答

0

那么,答案结果是非常简单。在上面的代码中,我们实际上正在传输emscripten堆的一部分。副本需要预先进行:

更改一行解决了这个问题:

var result = new Uint8Array(outputHeap); 

这将创建ArrayBuffer的副本,而不是简单地增加在emscripten堆一个新的观点。

或者,您可以使用slice();来创建新的ArrayBuffer。但我相信这种方法更快。

+0

不制作ArrayBuffer的副本会否定转移所述缓冲区所有权的好处?唯一的例外是我可以想象的是让postMessage()做一个副本而不转移所有权将复制整个Emscripten HEAP存储空间,而不仅仅是需要的字节,所以当你只打算复制16MB或更多时复制的数据少得多。从Emscripten缓冲区创建新视图至少会提供限制缓冲区大小的机会。 – user1810418

0

我遇到了同样的问题。对于任何未了解问题的读者,我将进一步解释OOM错误的原因。

当在主线程和web worker之间使用postMessage()传输存储在类型数组或其他实现Transferable接口的对象中的数据时,通常最好使用postMessage(data, [data.someArrayBuffer])。这个缺点是任何在列表中指定的ArrayBuffer将无法​​被发送给它的线程访问。在这种情况下,ArrayBuffer被传递为Emscripten HEAP内存缓冲区STB.HEAP8,所以一旦所有权被转移(并且未被复制),Emscripten将无法访问并因此OOM错误。

我无法承受缓冲区副本的开销,并且在web worker内部运行几乎没有好处,所以我将Emscripten移到了主线程中。

相关问题