跳到主要内容

污染CORS与readPixels

1. 机制:谁把 GPU 表面弄脏

<canvas> 默认 origin-clean。下列上传会标脏(以 WHATWG + WebGL / WebGPU 为准):

  • 跨源 HTMLImageElement / HTMLVideoElement / SVGImageElement,且这次请求不是 CORS 成功
  • 另一张 已经脏了的 canvas / OffscreenCanvas / ImageBitmap
  • WebGPU importExternalTexture / copyExternalImageToTexture 吃了上述源

同源 blob、data URL、fetch + CORS 成功再 createImageBitmap 的,保持干净。

脏了之后:

  • 继续 draw* / submit 给用户看,通常没问题
  • readPixelscanvas.toBlob / toDataURLSecurityError
  • 把这张 canvas 当源再 texImage2D 或画进 2D,脏性跟着走
export function assertGlClean(gl: WebGL2RenderingContext): void {
const pixels = new Uint8Array(4);
try {
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
} catch (error) {
if (error instanceof DOMException && error.name === "SecurityError") {
throw new Error("gpu canvas is tainted; readPixels and toBlob are blocked");
}
throw error;
}
}

不要在热路径用 1×1 readPixels 当探针——它本身就是 CPU–GPU 同步。架构上 跟踪源:所有位图 / 视频必须经过 loader,loader 只产出干净对象。

readPixels 读的是 drawing buffer 左下 原点。脏与否和 Y 翻转无关;把干净像素当 PNG 存仍要翻行,见 NDC 与 Y 轴

2. 适用:先 CORS,再上传

export async function loadCleanBitmap(url: string): Promise<ImageBitmap> {
const response = await fetch(url, { mode: "cors", credentials: "omit" });
if (!response.ok) {
throw new Error(`fetch ${url} -> ${response.status}`);
}
return createImageBitmap(await response.blob());
}

export function texImageClean(
gl: WebGL2RenderingContext,
texture: WebGLTexture,
bitmap: ImageBitmap,
): void {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
}

credentials: "omit" 避免把 cookie 带到图床;否则 ACAO 不能是 *fetch 失败是 HTTP 状态,不是「onload 了但 canvas 脏了」。

必须走 <img> / &lt;video> 时,crossOrigin = "anonymous" 必须在设 src 之前。设晚了,这次请求按无 CORS 走;元素能显示,texImage2D / importExternalTexture 仍会 taint。产品表现是「预览正常,点导出才 SecurityError」。

3. 不适用

  • img.crossOrigin 当「一定干净」。CDN 没回 ACAO 时 onload 仍可能触发。
  • drawImage 进 2D 再 texImage2D(canvas):多一次拷,脏性照传。
  • 指望 WebGPU 绕开 taint。importExternalTexture({ source: video }) 对跨源视频同一套 origin-clean。
  • 用像素 hash 当场景相等金标。GPU、DPR、premultipliedAlpha 都会改字节。

4. 失败形态

症状原因
预览正常,toBlob / readPixels SecurityError跨源无 CORS,或 crossOrigin 设晚
texImage2D 直接抛源已脏,或实现选择在上传点拒绝
2D 离屏也被脏drawImage(glCanvas) 时 GL 表面已脏
fetch CORS 失败图床 * + 带 cookie,或没有 ACAO
导出偶发成功有时走了同源缓存,有时走了跨源无 CORS

视频路径见 视频纹理。2D 导出细节见 Canvas 污染

权威资料

核对日期:2026-08-26