我想用JavaScript编写一个Gameboy模拟器。屏幕分辨率为160 x 144
。它有四种颜色:黑色,深灰色,浅灰色和白色。该游戏男孩的屏幕刷新率为60fps。
我有一个screen
数组,其大小为160*144=23040
,其中每个索引存储从0-3的颜色阴影。
要将屏幕呈现给用户,我想使用HTML5画布。
据我了解,最简单的方法是使用ImageData
RGBA位图创建Uint8ClampedArray
对象,然后通过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
函数就需要约25ms。如果要以60fps更新屏幕,则需要每16.6毫秒渲染一次!解决此问题的最佳方法是什么?创建一个全新的92kb
Uint8ClampedArray
并每帧映射23,000种颜色太昂贵了。我想保留
screen
数组,因为它比Uint8ClampedArray
更小,并且在我的代码中更容易推理。我想最好一次初始化一个Uint8ClampedArray
并在screen
数组发生变化时对其进行更新。这样,我可以简单地在每个帧上返回RGBA缓冲区,并且该缓冲区应该已经与我的屏幕同步了。我还想知道是否每个帧都创建一个新的92kb
ImageData
对象是否占用大量资源,是否有更好的方法来处理这一问题。有什么建议么?
最佳答案
捆绑绘图调用并提前保存调色板,这样就不会有太多问题了。
看看下面的我的代码段:
//Generate output node for time
var output = document.body.appendChild(document.createElement("p"));
//Generate canvas
var canvas = document.body.appendChild(document.createElement("canvas"));
var ctx = canvas.getContext("2d");
canvas.style.width = "500px";
//Setup canvas
canvas.width = 160;
canvas.height = 144;
//Generate color pallette
var colors = ["black", "#444", "#ccc", "white"]
.map(function (b) {
ctx.fillStyle = b;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return ctx.getImageData(0, 0, 1, 1).data;
});
//Generate framedata (referencing back to one of our 4 base colors)
var data = [];
while (data.length < canvas.width * canvas.height) {
data.push(Math.floor(Math.random() * 4));
}
//draw function
function drawCanvas() {
//Start time
var t = Date.now();
//Fill with basic color
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
//Prepare ImageData
var frame = new ImageData(canvas.width, canvas.height);
//Loop through buffer
for (var i = 0; i < data.length; i++) {
var dataPoint = data[i];
//Skip base color
if (dataPoint == 0) {
continue;
}
//Set color from palette
frame.data.set(colors[dataPoint], i * 4);
}
//Put ImageData on canvas
ctx.putImageData(frame, 0, 0);
//Output time
output.textContent = (Date.now() - t).toFixed(2);
//Schedule next frame
requestAnimationFrame(drawCanvas);
}
//Start drawing
drawCanvas();
//Start simulation at 60 fps
setInterval(function () {
data = data.map(function () { return Math.floor(Math.random() * 4); });
}, 16);
我持续获得每帧2ms的时间。