with one click
webgl
WebGL shaders and effects for JARVIS 3D HUD
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
WebGL shaders and effects for JARVIS 3D HUD
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Three.js 3D building system with spatial indexing, structural physics, and multiplayer networking. Use when creating survival games, sandbox builders, or any game with player-constructed structures. Covers performance optimization (spatial hash grids, octrees, chunk loading), structural validation (arcade/heuristic/realistic physics modes), and multiplayer sync (delta compression, client prediction, conflict resolution).
GPU-based particle systems using instanced rendering, buffer attributes, Points geometry, and custom shaders. Use when rendering thousands to millions of particles efficiently, creating particle effects like snow, rain, stars, or abstract visualizations.
Particle lifecycle management—emission/spawning, death conditions, object pooling, trails, fade-in/out, and state transitions. Use when particles need birth/death cycles, continuous emission, trail effects, or memory-efficient recycling.
Physics simulation for particle systems—forces (gravity, wind, drag), attractors/repulsors, velocity fields, turbulence, and collision. Use when particles need realistic or artistic motion, swarm behavior, or field-based animation.
Decision framework for particle system projects. Routes to specialized particle skills (gpu, physics, lifecycle) based on task requirements. Use when building particle effects or needing guidance on which particle techniques to combine.
| name | webgl |
| description | WebGL shaders and effects for JARVIS 3D HUD |
| model | sonnet |
| risk_level | MEDIUM |
| version | 1.0.0 |
File Organization: This skill uses split structure. See
references/for advanced patterns and security examples.
This skill provides WebGL expertise for creating custom shaders and visual effects in the JARVIS AI Assistant HUD. It focuses on GPU-accelerated rendering with security considerations.
Risk Level: MEDIUM - Direct GPU access, potential for resource exhaustion, driver vulnerabilities
Primary Use Cases:
| Browser | WebGL 2.0 | Notes |
|---|---|---|
| Chrome | 56+ | Full support |
| Firefox | 51+ | Full support |
| Safari | 15+ | WebGL 2.0 support |
| Edge | 79+ | Chromium-based |
// Check WebGL support and capabilities
function getWebGLContext(
canvas: HTMLCanvasElement,
): WebGL2RenderingContext | null {
const gl = canvas.getContext("webgl2", {
alpha: true,
antialias: true,
powerPreference: "high-performance",
failIfMajorPerformanceCaveat: true, // Fail if software rendering
});
if (!gl) {
console.warn("WebGL 2.0 not supported");
return null;
}
return gl;
}
// utils/shaderUtils.ts
// ✅ Safe shader compilation with error handling
export function compileShader(
gl: WebGL2RenderingContext,
source: string,
type: number,
): WebGLShader | null {
const shader = gl.createShader(type);
if (!shader) return null;
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const error = gl.getShaderInfoLog(shader);
console.error("Shader compilation error:", error);
gl.deleteShader(shader);
return null;
}
return shader;
}
// ✅ Safe program linking
export function createProgram(
gl: WebGL2RenderingContext,
vertexShader: WebGLShader,
fragmentShader: WebGLShader,
): WebGLProgram | null {
const program = gl.createProgram();
if (!program) return null;
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const error = gl.getProgramInfoLog(program);
console.error("Program linking error:", error);
gl.deleteProgram(program);
return null;
}
return program;
}
// composables/useWebGL.ts
export function useWebGL(canvas: Ref<HTMLCanvasElement | null>) {
const gl = ref<WebGL2RenderingContext | null>(null);
const contextLost = ref(false);
onMounted(() => {
if (!canvas.value) return;
// ✅ Handle context loss
canvas.value.addEventListener("webglcontextlost", (e) => {
e.preventDefault();
contextLost.value = true;
console.warn("WebGL context lost");
});
canvas.value.addEventListener("webglcontextrestored", () => {
contextLost.value = false;
initializeGL();
console.info("WebGL context restored");
});
initializeGL();
});
function initializeGL() {
gl.value = getWebGLContext(canvas.value!);
// Reinitialize all resources
}
return { gl, contextLost };
}
// shaders/holographic.frag
#version 300 es
precision highp float;
uniform float uTime;
uniform vec3 uColor;
uniform float uScanlineIntensity;
in vec2 vUv;
out vec4 fragColor;
void main() {
// Scanline effect
float scanline = sin(vUv.y * 200.0 + uTime * 2.0) * 0.5 + 0.5;
scanline = mix(1.0, scanline, uScanlineIntensity);
// Edge glow
float edge = smoothstep(0.0, 0.1, vUv.x) *
smoothstep(1.0, 0.9, vUv.x) *
smoothstep(0.0, 0.1, vUv.y) *
smoothstep(1.0, 0.9, vUv.y);
vec3 color = uColor * scanline * edge;
float alpha = edge * 0.8;
fragColor = vec4(color, alpha);
}
// utils/resourceManager.ts
export class WebGLResourceManager {
private textures: Set<WebGLTexture> = new Set();
private buffers: Set<WebGLBuffer> = new Set();
private programs: Set<WebGLProgram> = new Set();
private textureMemory = 0;
private readonly MAX_TEXTURE_MEMORY = 256 * 1024 * 1024; // 256MB
constructor(private gl: WebGL2RenderingContext) {}
createTexture(width: number, height: number): WebGLTexture | null {
const size = width * height * 4; // RGBA
// ✅ Enforce memory limits
if (this.textureMemory + size > this.MAX_TEXTURE_MEMORY) {
console.error("Texture memory limit exceeded");
return null;
}
const texture = this.gl.createTexture();
if (texture) {
this.textures.add(texture);
this.textureMemory += size;
}
return texture;
}
dispose(): void {
this.textures.forEach((t) => this.gl.deleteTexture(t));
this.buffers.forEach((b) => this.gl.deleteBuffer(b));
this.programs.forEach((p) => this.gl.deleteProgram(p));
this.textureMemory = 0;
}
}
// ✅ Type-safe uniform setting
export function setUniforms(
gl: WebGL2RenderingContext,
program: WebGLProgram,
uniforms: Record<string, number | number[] | Float32Array>,
): void {
for (const [name, value] of Object.entries(uniforms)) {
const location = gl.getUniformLocation(program, name);
if (!location) {
console.warn(`Uniform '${name}' not found`);
continue;
}
if (typeof value === "number") {
gl.uniform1f(location, value);
} else if (Array.isArray(value)) {
switch (value.length) {
case 2:
gl.uniform2fv(location, value);
break;
case 3:
gl.uniform3fv(location, value);
break;
case 4:
gl.uniform4fv(location, value);
break;
case 16:
gl.uniformMatrix4fv(location, false, value);
break;
}
}
}
}
// Step 1: tests/webgl/shaderCompilation.test.ts
import { describe, it, expect, beforeEach } from "vitest";
import { compileShader } from "@/utils/shaderUtils";
describe("WebGL Shader Compilation", () => {
let gl: WebGL2RenderingContext;
beforeEach(() => {
gl = document.createElement("canvas").getContext("webgl2")!;
});
it("should compile valid shader", () => {
const source = `#version 300 es
in vec4 aPosition;
void main() { gl_Position = aPosition; }`;
expect(compileShader(gl, source, gl.VERTEX_SHADER)).not.toBeNull();
});
it("should return null for invalid shader", () => {
expect(compileShader(gl, "invalid", gl.FRAGMENT_SHADER)).toBeNull();
});
});
// Step 2-3: Implement and refactor (see section 4.1)
// Step 4: npm test && npm run typecheck && npm run build
describe("WebGL Context", () => {
it("should handle context loss", async () => {
const { gl, contextLost } = useWebGL(ref(canvas));
gl.value?.getExtension("WEBGL_lose_context")?.loseContext();
await nextTick();
expect(contextLost.value).toBe(true);
});
});
describe("Resource Manager", () => {
it("should enforce memory limits", () => {
const manager = new WebGLResourceManager(gl);
expect(manager.createTexture(1024, 1024)).not.toBeNull();
expect(manager.createTexture(16384, 16384)).toBeNull(); // Exceeds limit
});
});
// Bad - Creates new buffer every frame
const buffer = gl.createBuffer();
gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW);
gl.deleteBuffer(buffer);
// Good - Reuse buffer, update only data
gl.bufferSubData(gl.ARRAY_BUFFER, 0, data); // Update existing buffer
// Bad - One draw call per object
objects.forEach(obj => {
gl.useProgram(obj.program)
gl.drawElements(...)
})
// Good - Batch by material/shader
const batches = groupByMaterial(objects)
batches.forEach(batch => {
gl.useProgram(batch.program)
batch.objects.forEach(obj => gl.drawElements(...))
})
// Bad - Always uncompressed RGBA
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
// Good - Use compressed formats when available
const ext = gl.getExtension('WEBGL_compressed_texture_s3tc')
if (ext) gl.compressedTexImage2D(gl.TEXTURE_2D, 0, ext.COMPRESSED_RGBA_S3TC_DXT5_EXT, ...)
// Bad - Individual draw calls for particles
particles.forEach((p) => {
gl.uniform3fv(uPosition, p.position);
gl.drawArrays(gl.TRIANGLES, 0, 6);
});
// Good - Single instanced draw call
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, particles.length);
// Bad - Rebind attributes every frame
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
// Good - Use VAO to store attribute state
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// Set up once, then just bind VAO for rendering
| CVE | Severity | Description | Mitigation |
|---|---|---|---|
| CVE-2024-11691 | HIGH | Apple M series memory corruption | Update browser, OS patches |
| CVE-2023-1531 | HIGH | Chrome use-after-free | Update Chrome |
| OWASP Category | Risk | Mitigation |
|---|---|---|
| A06 Vulnerable Components | HIGH | Keep browsers updated |
| A10 SSRF | LOW | Context isolation by browser |
// ✅ Implement resource limits
const LIMITS = {
maxDrawCalls: 100,
maxTriangles: 1_000_000,
maxTextures: 32,
maxTextureSize: 4096,
};
function checkLimits(stats: RenderStats): boolean {
if (stats.drawCalls > LIMITS.maxDrawCalls) {
console.error("Draw call limit exceeded");
return false;
}
if (stats.triangles > LIMITS.maxTriangles) {
console.error("Triangle limit exceeded");
return false;
}
return true;
}
// ❌ DANGEROUS - App crashes on context loss
const gl = canvas.getContext("webgl2");
// No context loss handler!
// ✅ SECURE - Handle gracefully
canvas.addEventListener("webglcontextlost", handleLoss);
canvas.addEventListener("webglcontextrestored", handleRestore);
// ❌ DANGEROUS - GPU memory exhaustion
for (let i = 0; i < userCount; i++) {
textures.push(gl.createTexture());
}
// ✅ SECURE - Enforce limits
if (textureCount < MAX_TEXTURES) {
textures.push(gl.createTexture());
}
// ❌ BAD - Unbatched draw calls
objects.forEach(obj => {
gl.useProgram(obj.program)
gl.bindTexture(gl.TEXTURE_2D, obj.texture)
gl.drawElements(...)
})
// ✅ GOOD - Batch by material
batches.forEach(batch => {
gl.useProgram(batch.program)
gl.bindTexture(gl.TEXTURE_2D, batch.texture)
batch.objects.forEach(obj => gl.drawElements(...))
})
npm test -- --run tests/webgl/npm run typechecknpm run buildWebGL provides GPU-accelerated graphics for JARVIS HUD. Key principles: handle context loss, enforce resource limits, validate shaders, track memory, batch draw calls, minimize state changes.
Remember: WebGL bypasses browser sandboxing - always protect against resource exhaustion.
References: references/advanced-patterns.md, references/security-examples.md