Я хочу написать эмулятор gameboy на JavaScript. Разрешение экрана 160 x 144
. Он имеет четыре оттенка цвета: черный, темно-серый, светло-серый и белый. Gameboy имеет частоту обновления экрана 60 кадров в секунду.
У меня есть массив screen
размером 160*144=23040
, где каждый индекс хранит цветовой оттенок 0-3.
Чтобы отобразить экран пользователю, я хочу использовать холст HTML5.
Насколько я понимаю, самый простой способ сделать это - создать объект ImageData
с растровым изображением Uint8ClampedArray
RGBA и передать его на холст с помощью putImageData
.
Для этого в каждом кадре мне нужно преобразовать свой экран в Uint8ClampedArray
и преобразовать каждый оттенок в соответствующие им 4-байтовые числа RGBA. Это новое растровое изображение RGBA будет иметь размер (160 * 144 * 4 bytes per pixel = 92160
:
function screenToBuffer(screen) {
return Uint8ClampedArray.from(screen.map(shade => shadeToRGBA(shade)).flat());
}
Затем этот буфер передается пользовательской функции обратного вызова, где они отображают его на холст следующим образом:
onFrame: function(frameBuffer) {
var image = new ImageData(frameBuffer, 160, 144)
ctx.putImageData(image, 0, 0);
}
Теоретически это прекрасно работает, но это наивный подход, и я обнаружил, что он вообще неэффективен. При использовании профилировщика только функция screenToBuffer
занимает ~ 25 мс. Если экран будет обновляться со скоростью 60 кадров в секунду, он будет рендериться каждые 16,6 мс!
Как лучше всего подойти к этой проблеме? Создание совершенно нового 92kb Uint8ClampedArray
и отображение 23 000 цветовых оттенков на кадр слишком дорого.
Я хочу сохранить массив screen
, потому что он меньше и намного проще рассуждать в моем коде, чем Uint8ClampedArray
. Я полагаю, что было бы лучше инициализировать Uint8ClampedArray
один раз и обновлять его всякий раз, когда происходит изменение в массиве screen
. Таким образом, я могу просто вернуть буфер RGBA в каждом кадре, и он уже должен быть синхронизирован с моим экраном.
Мне также интересно, если создание нового объекта размером 92 КБ ImageData
каждый кадр также требует значительных ресурсов, и может ли быть лучший способ справиться с этим.
Есть предложения?