从 WASM 高效地在画布上绘画

问题描述 投票:0回答:1

我正在使用新的 .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 堆。

所以我的问题是

  1. 尽快在画布上作画的最佳方法是什么?
  2. 如果它使用 Module.HEAPU8 那么我如何访问它?

编辑:

好吧,我想我已经解决了 Module.HEAPU8 问题 - 脚手架 main.js 分解了 dotnet.create 返回的对象,但如果你不这样做,那么你会得到一个带有

localHeapViewU8
函数的对象 我认为与 Module.HEAPU8 相同。

所以我不再收到 JS 错误,但它仍然不起作用。我从 C# 发送到 JS 的 IntPtr 到达时未定义,所以我认为这就是问题所在,但根据极少的文档,您应该能够发送 IntPtr。

我尝试更改它以发送数组的切片而不是指向数组的固定指针,但它显示“源生成的 JavaScript 互操作不支持类型“切片”。生成的源不会处理参数“dataPtr”的编组'。”它向我指出了文档,说明您应该能够发送切片...

c# html5-canvas webassembly
1个回答
0
投票

好的,我自己解决了这个问题。

首先,我不确定这是在画布上绘画的有效的方式(也许这与显卡有关),但这正是我想要的 - 一种无需编组所有像素的绘画方式并复制它们。

我将在这个答案中添加很多代码,因为我在搜索答案时找不到很多示例,所以希望这会对某人有所帮助。

这是我的 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);
}
© www.soinside.com 2019 - 2024. All rights reserved.