Search code examples
javascriptemscriptenunity-webgl

creating an array at a known heap address in emscripten


I'm passing a few kB of data (a generated PNG file) from a Unity3D WebGL context to javascript so that the user can download the PNG file without leaving the WebGL context. Unity uses emscripten and embeds the js as jslib. It's the fist time I've looked at emscripten or used pointers in js, having trouble finding the basics in the emscripten docs.

It's working, but I think it is a poor implementation, here's the code:

mergeInto(LibraryManager.library, {
    JSDownload: function(filenamePointer, dataPointer, dataLength) {
        filename = Pointer_stringify(filenamePointer);
        var data = new Uint8Array(dataLength);
        for (var i = 0; i < dataLength; i++) {
            data[i]=HEAPU8[dataPointer+i];
        }
        var blob = new Blob([data], {type: 'application/octet-stream'});
        if(window.navigator.msSaveOrOpenBlob) {
            window.navigator.msSaveBlob(blob, filename);
        }
        else{
            var elem = window.document.createElement('a');
            elem.href = window.URL.createObjectURL(blob);
            elem.download = filename;        
            document.body.appendChild(elem);
            elem.click();        
            document.body.removeChild(elem);
        }
    }
});

What bothers me is stepping through the data like that, since I already have the address and the length I want to instantiate the 'data' array at the known address, like I would with * and & in C, rather than copying it byte by byte, or if I have to copy it, at least do that in one hit rather than a loop. I think my biggest issue is not knowing where to look for the documentation. I've found more from looking at random projects on GitHub than here: https://emscripten.org/docs/api_reference/preamble.js.html

Any help would be appreciated, thanks.


Solution

  • So you don't like this part?

    var data = new Uint8Array(dataLength);
    for (var i = 0; i < dataLength; i++) {
        data[i]=HEAPU8[dataPointer+i];
    }
    var blob = new Blob([data], {type: 'application/octet-stream'});
    

    You can make it one-liner:

    var blob = new Blob([HEAPU8.subarray(dataPointer, dataPointer + dataLength)], {type: 'application/octet-stream'});
    
    // or this
    
    var blob = new Blob([new Uint8Array(HEAPU8.buffer, dataPointer, dataLength)], {type: 'application/octet-stream'});
    

    Both of them should be much faster then your original code, and both of them should have exactly the same performance. It's because they create a new Blob directly from HEAPU8 without creating duplicated array like your original code.

    HEAPU8 is a Uint8Array, one of TypedArray family. One really important thing about TypedArray is that it is actually not buffer/data but it's rather a "view" of the underlying ArrayBuffer (it's HEAPU8.buffer) object which holds the actual data. See ArrayBufferView.

    So HEAPU8 provides an interface for HEAPU8.buffer ArrayBuffer object, specifically WebAssembly.Memory.buffer in Emscripten, to look like an uint8_t array. Emscripten also provides HEAPU16, HEAPU32, HEAPF32, and etc but they have the same ArrayBuffer with different views.

    What .subarray(start, end) and new Uint8Array(buffer, offset, size) do is to create a new "view" of the ArrayBuffer object with the specified range, not to copy the buffer. So you will have the minimal performance penalty.