我正在使用新的 .NET 8“WebAssembly Browser App”项目模板(不是 Blazor)来尝试制作一个小型游戏引擎。
我需要做的事情之一就是尽快在画布上绘画。我发现很多问题都在问这个具体问题,但它们都有几年历史了,WASM 在那段时间发生了很大变化,所以我认为很多答案都已经过时了。
这个答案特别有希望,因为它意味着某种内存共享可以避免复制,但它基本上只是指向this项目。该项目非常相关,因为它是 WASM 中的游戏引擎,但不幸的是它是基于 Blazor 构建的,因此似乎存在一些差异,阻碍了它为我工作。
具体来说,从 IntPtr 绘制到画布上的代码是
window.PaintCanvas = function PaintCanvas(dataPtr) {
imageData.data.set(Uint8ClampedArray.from(Module.HEAPU8.subarray(dataPtr, dataPtr + imageData.data.length)));
context.putImageData(imageData, 0, 0);
context.drawImage(canvas, 0, 0, canvas.width, canvas.height);
return true;
}
但我收到“未捕获的引用错误:模块未定义”,我假设这是因为 Blazor 正在执行某些操作来访问 WebAssembly 浏览器应用程序项目未执行的 WASM 堆。
所以我的问题是
编辑:
好吧,我想我已经解决了 Module.HEAPU8 问题 - 脚手架 main.js 分解了 dotnet.create 返回的对象,但如果你不这样做,那么你会得到一个带有
localHeapViewU8
函数的对象
我认为与 Module.HEAPU8 相同。
所以我不再收到 JS 错误,但它仍然不起作用。我从 C# 发送到 JS 的 IntPtr 到达时未定义,所以我认为这就是问题所在,但根据极少的文档,您应该能够发送 IntPtr。
我尝试更改它以发送数组的切片而不是指向数组的固定指针,但它显示“源生成的 JavaScript 互操作不支持类型“切片”。生成的源不会处理参数“dataPtr”的编组'。”它向我指出了文档,说明您应该能够发送切片...
好的,我自己解决了这个问题。
首先,我不确定这是在画布上绘画的最有效的方式(也许这与显卡有关),但这正是我想要的 - 一种无需编组所有像素的绘画方式并复制它们。
我将在这个答案中添加很多代码,因为我在搜索答案时找不到很多示例,所以希望这会对某人有所帮助。
这是我的 main.js:
import { dotnet } from './_framework/dotnet.js'
const wasm = await dotnet
.withDiagnosticTracing(false)
.withApplicationArgumentsFromQuery()
.create();
wasm.setModuleImports('main.js', {
InitCanvas: () => InitCanvas(),
PaintCanvas: (dataPtr) => PaintCanvas(dataPtr)
});
const width = 320;
const height = 180;
let canvas;
let context;
let imageData;
function InitCanvas() {
canvas = document.getElementById('screen');
canvas.width = width;
canvas.height = height;
context = canvas.getContext('2d');
context.mozImageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;
imageData = context.createImageData(width, height);
}
function PaintCanvas(dataPtr) {
window.requestAnimationFrame(function() {
const slice = wasm.localHeapViewU8().subarray(dataPtr, dataPtr + imageData.data.length);
const bytes = Uint8ClampedArray.from(slice);
imageData.data.set(bytes);
context.putImageData(imageData, 0, 0);
context.drawImage(canvas, 0, 0, canvas.width, canvas.height);
});
}
const config = wasm.getConfig();
const exports = await wasm.getAssemblyExports(config.mainAssemblyName);
await dotnet.run();
exports.Tactics.Run();
您可以看到(正如我在问题的编辑中提到的)我如何解决 Module.HEAPU8 问题 - 我将 dotnet.create 返回的对象存储在名为 wasm 的常量中,以便稍后可以调用 wasm.localHeapViewU8( )来获取 WASM 堆的引用。
然后问题是我从C#发送到JS的IntPtr总是未定义的。事实证明这是我的代码中的一个错误 - 在 wasm.setModuleImports 调用中我没有将 dataPtr 参数传递给 window.PaintCanvas。
最后我无法判断它是否有效,因为我发送的测试像素是白色且透明的,因此它们在白色背景上不可见🤦u200d♂️
这里是 C# 方面,以防它对任何人有用:
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript;
public partial class Tactics {
readonly static uint[] _screen = new uint[320 * 180];
[JSExport]
internal static void Run() {
// set pixels in _screen here - each entry in the array represents one pixel so you're going to need to convert the r/g/b/a of your pixel to a uint
var gchscreen = GCHandle.Alloc(_screen, GCHandleType.Pinned);
try {
var pinnedscreen = gchscreen.AddrOfPinnedObject();
PaintCanvas(pinnedscreen);
} finally {
gchscreen.Free();
}
}
[JSImport("InitCanvas", "main.js")]
internal static partial void InitCanvas();
[JSImport("PaintCanvas", "main.js")]
internal static partial void PaintCanvas(IntPtr dataPtr);
}