简体   繁体   中英

How can I fix “Out of Memory” error when trying to send back data from a Webworker, after the data has been processed in an Emscripten'd C++ program

In order to allow new textures to be streamed in to WebGL without locking the main UI thread, we have compiled both libjpeg and a real-time DXT texture compressor to javascript using emscripten with asm.js and are running them both inside a single webworker.

On an 6 year old laptop, dealing with 2048x2048 Jpeg source images, we are decoding each jpeg in around 300ms and are then compressing them to DXT1 compressed texture format in around 230ms. This is more than adequate performance for our needs even though we are certain it could be improved somewhat.

However the problem we are having is that deserializing the returned data from the webworker still causes the main UI thread to hang. Which considering that each returned DTX1 file is 2mb, this is not at all surprising. To remedy this we intended to send the data back using using webworker transferable objects allowing the resulting ArrayBuffer to simply be transferred across to the main thread without it needing to be copied.

However, whenever we attempt to do so, we receive an InternalError: out of memory error on the postMessage call.

This is the way in which we are calling the DXT compressor and sending back the resulting typedArray . ( this.decoded is simply a decoded JPEG file as raw RGBA data in a Uint8Array and the variable STB accesses the emscripten'd version of our DXT compressor)

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();

}

The second we remove the transferable list and change the postMessage call to the following:

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

Then everything works as expected. I can only assume this is down to our inexperience with emscripten and that we are doing something wrong with _malloc , _free and our typedArrays and ArrayBuffers . But we haven't, as of yet, been able to work out where we are going wrong.

Any help would be greatly appreciated.

Well, the answer turned out to be frightfully simple. In the above code we were actually transferring part of the emscripten heap. A copy needs to be made beforehand:

Changing a single line resolved the issue:

var result = new Uint8Array(outputHeap);

This creates a copy of the ArrayBuffer , rather than simply adding a new view over the emscripten heap.

Alternatively you could use slice(); to create the new ArrayBuffer . But I believe this method is faster.

I ran into the same issue. For any future readers who don't understand what the problem is I'll further explain the reason for OOM error.

When using postMessage() between main thread and web worker to transfer data stored in a typed array or other object implementing Transferable interface it is often preferred to use postMessage(data, [data.someArrayBuffer]) . The downside to this is that any ArrayBuffer specified in the list will be then inaccessible to the thread sending it. In the case here the ArrayBuffer being passed happened to be Emscripten HEAP memory buffer STB.HEAP8 , so once ownership was transferred (and not copied) it becomes inaccessible to Emscripten and therefore OOM error.

I couldn't afford the overhead of a buffer copy and there was little benefit to running inside a web worker so I moved Emscripten into the main thread.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM