javascript - 如何优化 60fps 模拟器的画布性能?
问题描述
我想用 JavaScript 写一个 gameboy 模拟器。屏幕分辨率为160 x 144
。它有四种颜色:黑色、深灰色、浅灰色和白色。gameboy的屏幕刷新率为60fps。
我有screen
一个大小为 的数组160*144=23040
,其中每个索引存储 0-3 的颜色阴影。
为了向用户呈现屏幕,我想使用 HTML5 画布。据我了解,最直接的方法是ImageData
使用 RGBA 位图创建一个对象,Uint8ClampedArray
然后通过putImageData
.
为了做到这一点,每一帧我都必须将我的屏幕转换为 a 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 毫秒。如果屏幕要以 60fps 更新,则需要每 16.6ms 渲染一次!
解决这个问题的最佳方法是什么?创建一个全新的 92kbUint8ClampedArray
并在每帧映射 23,000 个色度的成本太高了。
我想保留screen
数组,因为在我的代码中它比Uint8ClampedArray
. 我想最好初始化一次,并在数组Uint8ClampedArray
发生更改时更新它。screen
这样我就可以简单地在每一帧上返回 RGBA 缓冲区,它应该已经与我的屏幕同步了。
我还想知道是否ImageData
每帧创建一个新的 92kb 对象也是资源密集型的,是否有更好的方法来处理它。
有什么建议么?
解决方案
捆绑绘图调用并提前保存调色板,您不应该对此有太多问题。
看看我下面的片段:
//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);
我一直得到大约每帧 2 毫秒。
推荐阅读
- java - Jenkins - Java & Selenium - 如何处理 2 个随机异常?
- laravel - 从关系中获取数据表的一条记录
- javascript - 如何在 ASP.NET Core Razor 视图中更新/刷新 ViewData?
- python - 根据两列上的条件创建新的熊猫列
- android - 无法解决:com.mapbox.maps:android:10.0.0
- qt - 使用 .ts 文件的语言学家对 Qt 语言文件的版本处理
- json - Ansible - 在不知道对象/键名的情况下对对象进行嵌套循环
- opengl - 在 OpenGL 中,是否可以仅绘制未遮挡三角形的边缘?
- javascript - 如何将控制台的内容显示到一个div中?
- amazon-web-services - EKS 节点无法与 AWS Classic 负载均衡器通信