问题修复
在浏览器中开发 WebGL2 着色器与 GPU 特效时…
适用于用 WebGL2 实现自定义着色器、后处理、粒子或实时数据可视化,并需要处理 GPU 安全、资源管理、性能预算和降级的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在浏览器中开发 WebGL2 着色器与 通用
解决思路
- 用 canvas.getContext('webgl2', { alpha: true, antialias: true, powerPreference: 'high-performance', failIfMajorPerformanceCaveat: true }) 获取 WebGL2 上下文;若返回 null 则 console.warn('WebGL 2.0 not supported') 并返回 null。
- 实现 compileShader:用 gl.createShader(type)、gl.shaderSource、gl.compileShader;检查 gl.getShaderParameter(shader, gl.COMPILE_STATUS),失败时读取 gl.getShaderInfoLog(shader)、console.error、gl.deleteShader(shader) 并返回 null。
- 实现 createProgram:用 gl.createProgram、gl.attachShader、gl.linkProgram;检查 gl.getProgramParameter(program, gl.LINK_STATUS),失败时读取 gl.getProgramInfoLog(program)、console.error、gl.deleteProgram(program) 并返回 null。
- 在 canvas 上监听 webglcontextlost:调用 e.preventDefault()、将 contextLost 置为 true、console.warn('WebGL context lost')。
- 在 canvas 上监听 webglcontextrestored:将 contextLost 置为 false、调用 initializeGL() 并通过 getWebGLContext 重新初始化所有资源。
- 实现全息 fragment shader:接收 uTime、uColor、uScanlineIntensity、vUv,计算 scanline = sin(vUv.y * 200.0 + uTime * 2.0) * 0.5 + 0.5,再 mix(1.0, scanline, uScanlineIntensity),用 smoothstep 计算边缘 edge,输出 vec4(uColor * scanline * edge, edge * 0.8)。
- 实现 WebGLResourceManager:用 Set 跟踪 textures、buffers、programs,记录 textureMemory,设置 MAX_TEXTURE_MEMORY = 256 * 1024 * 1024。
- 在 createTexture(width, height) 中计算 size = width * height * 4;若 textureMemory + size > MAX_TEXTURE_MEMORY,则 console.error('Texture memory limit exceeded') 并返回 null;否则 gl.createTexture、加入 Set、累加 textureMemory。
- 在 dispose() 中遍历删除所有 textures、buffers、programs,并将 textureMemory 置为 0。
- 实现 setUniforms:遍历 uniforms 的 [name, value],用 gl.getUniformLocation(program, name);location 不存在时 console.warn(`Uniform '${name}' not found`) 并 continue。
- 在 setUniforms 中对 number 使用 gl.uniform1f;对数组长度 2/3/4/16 分别使用 gl.uniform2fv、gl.uniform3fv、gl.uniform4fv、gl.uniformMatrix4fv(location, false, value)。
- 按 TDD 流程开发:先写失败测试,再实现最小功能,然后重构,最后验证。
- 编写 WebGL shader 编译测试:beforeEach 用 document.createElement('canvas').getContext('webgl2') 获取 gl;测试有效 shader 时 expect(compileShader(...)).not.toBeNull();测试无效 shader 时 expect(...).toBeNull()。
- 编写上下文丢失测试:用 useWebGL(ref(canvas)),调用 gl.value?.getExtension('WEBGL_lose_context')?.loseContext(),await nextTick() 后 expect(contextLost.value).toBe(true)。
- 编写资源管理器测试:expect(manager.createTexture(1024, 1024)).not.toBeNull();expect(manager.createTexture(16384, 16384)).toBeNull() 以验证内存限制。
- 提交前运行 npm test -- --run tests/webgl/、npm run typecheck、npm run build,并验证性能和 no WebGL fallback。
- 复用 buffer:不要每帧 gl.createBuffer/gl.bufferData/gl.deleteBuffer;改为保留已有 buffer,并用 gl.bufferSubData(gl.ARRAY_BUFFER, 0, data) 只更新数据。
- 批量绘制:按材质/着色器 groupByMaterial(objects),对每个 batch 只调用一次 gl.useProgram(batch.program),再遍历 batch.objects 调用 gl.drawElements(...)。
- 纹理压缩:优先用 gl.getExtension('WEBGL_compressed_texture_s3tc');若扩展存在,用 gl.compressedTexImage2D(gl.TEXTURE_2D, 0, ext.COMPRESSED_RGBA_S3TC_DXT5_EXT, ...) 替代未压缩 RGBA。
- 粒子使用实例化渲染:用 gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, particles.length) 做单次实例化绘制,避免每个粒子单独 uniform 和 drawArrays。
- 使用 VAO 存储顶点属性状态:gl.createVertexArray()、gl.bindVertexArray(vao),设置一次属性,之后渲染时只绑定 VAO,避免每帧重新 enableVertexAttribArray/vertexAttribPointer。
- 针对已知 CVE 做缓解:CVE-2024-11691(Apple M 系列内存损坏)需更新浏览器和 OS 补丁;CVE-2023-1531(Chrome use-after-free)需更新 Chrome。
- 按 OWASP 风险处理:A06 Vulnerable Components 保持浏览器更新;A10 SSRF 依赖浏览器上下文隔离,风险较低。
- 实现 GPU 资源保护限制:LIMITS = { maxDrawCalls: 100, maxTriangles: 1_000_000, maxTextures: 32, maxTextureSize: 4096 }。
- 在 checkLimits(stats) 中,若 stats.drawCalls > LIMITS.maxDrawCalls 或 stats.triangles > LIMITS.maxTriangles,则 console.error 并返回 false;否则返回 true。
- 不要跳过上下文丢失处理:必须添加 webglcontextlost 和 webglcontextrestored 监听并优雅处理。
- 不要无限分配资源:创建纹理前检查 textureCount < MAX_TEXTURES,满足才 gl.createTexture()。
- 避免过多状态切换:不要对每个对象单独 useProgram/bindTexture/drawElements;改为按材质批处理后一次 useProgram、一次 bindTexture,再批量 drawElements。
- 阶段 1 写代码前:为 shaders、context、resources 写失败测试;定义性能预算(draw calls < 100,memory < 256MB);识别所需 WebGL extensions。
- 阶段 2 实现中:添加上下文丢失处理与恢复;实施资源限制和内存跟踪;编译前验证 shader;使用 VAO、批量绘制、复用 buffer;粒子使用实例化渲染。
- 阶段 3 提交前:确认测试通过 npm test -- --run tests/webgl/;运行 npm run typecheck;运行 npm run build;验证性能(draws、memory);测试 no WebGL fallback。
- 阅读 references/advanced-patterns.md 和 references/security-examples.md 获取高级模式和安全示例。
- 为 GPU 操作设置超时机制和资源限制。
- 在编译前验证所有 shader 输入。
- 提供非 WebGL fallback。
- 跟踪并限制 texture/buffer 使用。
- 确保目标浏览器支持 WebGL2:Chrome 56+、Firefox 51+、Safari 15+、Edge 79+。
- 因为 WebGL 绕过浏览器沙箱,始终防止资源耗尽。
适用边界 · 注意事项
- 目标环境不支持 WebGL2 且无法提供非 WebGL fallback 时不适用。
- 需要原生 OpenGL/Vulkan/Metal/DirectX 或非浏览器 GPU 计算的场景不直接适用。
- 无法更新浏览器/OS/驱动来缓解已知 CVE 和驱动漏洞时,安全敏感或不受控环境慎用。