2016-02-01 4 views
0

Чтобы позволить новые текстуры транслироваться в WebGL без блокировки основной поток пользовательского интерфейса, мы скомпилировали как libjpeg, так и в реальном времени DXT-текстурный компрессор для javascript, используя emscripten с asm.js, и запускаем их как внутри одного веб-исполнителя.Как исправить ошибку «Недостаточно памяти» при попытке отправить данные из веб-мастера после обработки данных в программе на C++ Emscripten'а

На 6-летнем ноутбуке, работающем с изображениями исходного изображения 2048x2048 Jpeg, мы декодируем каждый jpeg примерно за 300 мс и затем сжимаем их до формата сжатой текстуры DXT1 примерно в 230 мс. Это более чем адекватная производительность для наших потребностей, хотя мы уверены, что она может быть улучшена несколько.

Однако проблема, с которой мы сталкиваемся, заключается в том, что десериализация возвращаемых данных из веб-сайта по-прежнему вызывает зависание основного потока пользовательского интерфейса. Что касается того, что каждый возвращаемый файл DTX1 равен 2 МБ, это совсем не удивительно. Чтобы исправить это, мы намеревались отправить данные обратно с использованием переносимых веб-сайтов объектов, позволяя получить ArrayBuffer просто в основной поток без необходимости его копирования.

Однако, когда мы пытаемся это сделать, мы получаем ошибку InternalError: out of memory на вызов postMessage.

Это способ, которым мы вызываем компрессор DXT и отправляем обратно полученный результат typedArray. (this.decoded просто декодируются файл JPEG в качестве исходных данных RGBA в Uint8Array и переменной STB доступов версия emscripten'd нашего DXT компрессора)

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 и нашими typedArrays и ArrayBuffers. Но пока мы еще не смогли понять, в чем мы ошибаемся.

Любая помощь была бы принята с благодарностью.

+0

Происходит ли ошибка в 'postMessage', а не' this._cleanUp() '? – zakki

+0

Да, на '' 'postMessage'', однако я нашел причину и отвечу на мой собственный вопрос. – gordyr

ответ

0

Ну, ответ оказался ужасно простым. В приведенном выше коде мы фактически переносили часть кучи emscripten. Копия должна быть сделана заранее:

Изменение одной линии решен вопрос:

var result = new Uint8Array(outputHeap); 

Это создает копию ArrayBuffer, а не просто добавить новый вид на emscripten куче.

Альтернативно вы можете использовать slice();, чтобы создать новый ArrayBuffer. Но я считаю, что этот метод быстрее.

+0

Не делает ли копирование ArrayBuffer отрицательным преимуществом передачи права собственности на указанный буфер? Единственное исключение из этого, что я могу себе представить, это то, что позволить 'postMessage()' делать копию и не передавать права собственности копирует все пространство памяти Empscripten HEAP, а не только необходимые байты, поэтому это может означать копирование 16 МБ или более, когда вы только намереваетесь чтобы копировать значительно меньше данных. Создание нового представления из буфера Emscripten, по крайней мере, даст возможность ограничить размер буфера. – user1810418

0

Я столкнулся с тем же вопросом. Для будущих читателей, которые не понимают, в чем проблема, я объясню причину ошибки OOM.

При использовании postMessage() между основной нитью и веб-работником для передачи данных, хранящихся в типизированном массиве или другом объекте, реализующем переносимый интерфейс, часто предпочитают использовать postMessage(data, [data.someArrayBuffer]).Недостатком этого является то, что любой ArrayBuffer, указанный в списке, будет тогда недоступен для потока, отправляющего его. В данном случае передаваемый ArrayBuffer оказался буфером памяти Empscripten HEAP STB.HEAP8, поэтому, как только собственность была передана (и не скопирована), она становится недоступной для Emscripten и, следовательно, ошибки OOM.

Я не мог позволить себе накладные расходы на буферную копию, и было мало пользы для работы внутри веб-рабочего, поэтому я переместил Emscripten в основной поток.