我试图创建一个结构体,它有足够大的数据缓冲区来保存大于64 x 64像素的HTML5画布ImageData。
// src/lib.rs
use wasm_bindgen::prelude::*;
extern crate fixedbitset;
extern crate web_sys;
#[wasm_bindgen]
pub struct CanvasSource {
width: u32,
height: u32,
data: Vec<u8>,
}
#[wasm_bindgen]
impl CanvasSource {
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
// returns pointer to canvas image data
pub fn data(&self) -> *const u8 {
self.data.as_ptr()
}
pub fn cover_in_blood(&mut self) {
let blood: Vec<u8> = vec![252, 3, 27, 255];
let mut new_data = blood.clone();
for _ in 0..self.width {
for _ in 0..self.height {
let pixel = blood.clone();
new_data = [new_data, pixel].concat()
}
}
self.data = new_data;
}
pub fn new(width: u32, height: u32, initial_data: Vec<u8>) -> CanvasSource {
let data_size = (width * height) as usize;
let mut data = initial_data;
data.resize(data_size, 0);
CanvasSource {
width,
height,
data,
}
}
}
...并在Typescript中从此处调用:
import { useEffect, useRef, useState } from "react";
import { CanvasSource } from "rust-canvas-prototype";
import { memory } from "rust-canvas-prototype/rust_canvas_prototype_bg.wasm";
import styles from "./DirectCanvas.module.css";
const getRenderLoop = (
source: CanvasSource,
ctx: CanvasRenderingContext2D,
) => {
if (source && ctx) {
const loop = () => {
const sourceDataPtr = source.data();
const width = source.width();
const height = source.height();
const regionSize = width * height * 4;
const pixelData = new Uint8ClampedArray(
memory.buffer,
sourceDataPtr,
regionSize
)
const imageData = new ImageData(pixelData, width, height);
ctx.putImageData(imageData, 0, 0)
};
return loop;
}
return null;
}
export function DirectCanvas() {
const [source, setSource] = useState<CanvasSource>();
const [ctx, setCtx] = useState<CanvasRenderingContext2D | null>(null);
const [paused, setPaused] = useState<boolean>(false);
// undefined on init, null when paused
const [animationId, setAnimationId] = useState<number>(0);
const canvasElement = useRef<HTMLCanvasElement>(null);
const initialized = source && ctx;
// initialization
useEffect(() => {
if (!source) {
console.log("loading source");
let [width, height] = [100, 100];
// uncomment below to cause error
// [width, height] = [358, 358]
setSource(CanvasSource.new(width, height, new Uint8Array([])))
}
if (source && !ctx && canvasElement.current) {
canvasElement.current.height = source.height();
canvasElement.current.width = source.width();
setCtx(canvasElement.current.getContext("2d"));
}
}, [source, ctx])
useEffect(() => {
if (initialized) {
const renderLoop = getRenderLoop(source, ctx);
if (renderLoop) {
renderLoop();
setTimeout(() => {
setAnimationId(prev => prev + 1);
}, 10)
}
}
}, [source, ctx, animationId]);
return (
<div className={styles.Container}>
<span className={styles.Controls}>
<button onClick={() => source?.cover_in_blood()}>Splatter</button>
</span>
<canvas ref={canvasElement}></canvas>
</div>
)
}
该函数在大小为~ 100 x100或更小的情况下正常工作,但一旦总面积开始超过该值,JS将抛出以下错误:
Uncaught RangeError: attempting to construct out-of-bounds Uint8ClampedArray on ArrayBuffer
loop DirectCanvas.tsx:23
DirectCanvas DirectCanvas.tsx:77
...
Preliminary research暗示Rust端的堆栈大小问题,但尝试在config.toml
抛出自己的错误时增加堆栈大小:= note: rust-lld: error: unknown argument: -Wl,-zstack-size=29491200
如何分配足够大的堆栈大小来绘制大于100 x100的画布?(找到的最小可重现示例here)
1条答案
按热度按时间vmjh9lq91#
哎呀,蛋在我脸上:问题其实不在生 rust 的那一面。
Uint 8Array的默认初始化大小不足以容纳大于~ 100 x100的ImageData。确保
CanvasSource
被 * 初始化 * 为正确的大小就可以了:在生 rust 的一面,我确实有一行代码,我认为可以处理这个:
但是,很明显,在Rust端调整大小要么(1)没有更新JS要找的空间,要么(2)我误用了Vec.resize()。这两种情况都有可能。谢谢大家指出的东西。