### Quick Start: WebGPU Renderer with TSL Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/SKILL.md Minimal setup for a WebGPU renderer with a node material using TSL. Requires importing from 'three/webgpu' and 'three/tsl'. ```javascript import * as THREE from 'three/webgpu'; import { color, time, oscSine } from 'three/tsl'; const renderer = new THREE.WebGPURenderer(); await renderer.init(); const material = new THREE.MeshStandardNodeMaterial(); material.colorNode = color(0xff0000).mul(oscSine(time)); ``` -------------------------------- ### Basic RenderPipeline Setup Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/post-processing.md Initializes the WebGPURenderer and sets up a basic render pipeline with a scene pass. ```javascript import * as THREE from 'three/webgpu'; import { pass } from 'three/tsl'; // Create renderer const renderer = new THREE.WebGPURenderer(); await renderer.init(); // Create render pipeline (formerly PostProcessing, renamed in r183) const renderPipeline = new THREE.RenderPipeline(renderer); // Create scene pass const scenePass = pass(scene, camera); const scenePassColor = scenePass.getTextureNode('output'); // Output (passthrough) renderPipeline.outputNode = scenePassColor; // Render with post-processing function animate() { renderPipeline.render(); // Not renderer.render() } ``` -------------------------------- ### Creating Node Materials Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/materials.md Examples of initializing different types of Node Materials. ```javascript import * as THREE from 'three/webgpu'; // Standard PBR material const material = new THREE.MeshStandardNodeMaterial(); // Physical material with advanced features const physicalMat = new THREE.MeshPhysicalNodeMaterial(); // Unlit material const basicMat = new THREE.MeshBasicNodeMaterial(); ``` -------------------------------- ### Implement WebGPU device loss recovery Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/device-loss.md This example demonstrates initializing a WebGPURenderer, setting up a listener for the device.lost promise, and implementing a recovery routine that cleans up the renderer and re-initializes the application. ```javascript import * as THREE from 'three/webgpu'; import { color, time, oscSine } from 'three/tsl'; let renderer, scene, camera, mesh; let hadPreviousDevice = false; async function init() { // Check WebGPU support if (!navigator.gpu) { showError('WebGPU not supported'); return; } // Create renderer renderer = new THREE.WebGPURenderer({ antialias: true }); try { await renderer.init(); } catch (e) { if (hadPreviousDevice) { showError('GPU recovery failed. Please restart browser.'); } else { showError('Failed to initialize WebGPU.'); } return; } hadPreviousDevice = true; // Setup device loss handler const device = renderer.backend.device; device.lost.then(handleDeviceLoss); // Setup scene renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 5; const geometry = new THREE.BoxGeometry(); const material = new THREE.MeshStandardNodeMaterial(); material.colorNode = color(0x00ff00).mul(oscSine(time)); mesh = new THREE.Mesh(geometry, material); scene.add(mesh); const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(5, 5, 5); scene.add(light); scene.add(new THREE.AmbientLight(0x404040)); animate(); } function handleDeviceLoss(info) { console.error('Device lost:', info.reason, info.message); if (info.reason === 'unknown') { // Cleanup if (renderer) { renderer.domElement.remove(); renderer.dispose(); } // Attempt recovery after short delay setTimeout(() => { init(); }, 100); } } function animate() { if (!renderer) return; requestAnimationFrame(animate); mesh.rotation.x += 0.01; mesh.rotation.y += 0.01; renderer.render(scene, camera); } function showError(message) { const div = document.createElement('div'); div.textContent = message; div.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:20px;background:#f44;color:#fff;border-radius:8px;'; document.body.appendChild(div); } init(); ``` -------------------------------- ### Install Claude Code Skill Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/README.md Command to install the WebGPU Three.js TSL skill directly from the repository into Claude Code. ```bash # Install from this repository /skill install webgpu-threejs-tsl@/webgpu-claude-skill ``` -------------------------------- ### Basic Compute Shader Setup Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/compute-shaders.md Shows how to set up a basic compute shader that uses a storage buffer and writes to it. Initialize the renderer with await renderer.init() before calling renderer.compute(). Note the import of * as THREE from 'three/webgpu'. ```javascript import * as THREE from 'three/webgpu'; import { Fn, instancedArray, instanceIndex, vec3 } from 'three/tsl'; // Create storage buffer const count = 100000; const positions = instancedArray(count, 'vec3'); // Create compute shader const computeShader = Fn(() => { const position = positions.element(instanceIndex); position.x.addAssign(0.01); })().compute(count); // Initialize renderer first, then use synchronous compute await renderer.init(); renderer.compute(computeShader); ``` -------------------------------- ### Minimize Texture Samples Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/wgsl-integration.md Performance tip: minimize texture samples by sampling once and reusing the result. The example stores the texture sample in a local variable and then accesses its components. ```wgsl // Sample once, use multiple times let sample = textureSample(tex, sampler, uv); let r = sample.r; let g = sample.g; let b = sample.b; ``` -------------------------------- ### TSL Swizzling Example Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/REFERENCE.md Shows how to access vector components using swizzling. Supports x, y, z, w and r, g, b, a aliases. ```javascript const v = vec3(1, 2, 3); v.x // 1 v.xy // vec2(1, 2) v.zyx // vec3(3, 2, 1) v.rgb // same as xyz ``` -------------------------------- ### WGSL Vector Types Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/wgsl-integration.md Shows the syntax for WGSL vector types, parameterized by element type and component count. Examples cover float, integer, and unsigned integer vectors. ```wgsl vec2 // 2D float vector vec3 // 3D float vector vec4 // 4D float vector vec2 // 2D integer vector vec2 // 2D unsigned integer vector ``` -------------------------------- ### Looping constructs Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/core-concepts.md Examples of simple, ranged, nested, and controlled loops using the Loop, Break, and Continue TSL primitives. ```javascript import { Loop, Break, Continue } from 'three/tsl'; // Simple loop (0 to 9) const sum = float(0).toVar(); Loop(10, ({ i }) => { sum.addAssign(float(i)); }); // Ranged loop with options Loop({ start: int(0), end: int(count), type: 'int' }, ({ i }) => { // Loop body }); // Nested loops Loop(width, height, ({ i, j }) => { // i = outer loop index // j = inner loop index }); // Loop control Loop(100, ({ i }) => { If(shouldStop, () => { Break(); // Exit loop }); If(shouldSkip, () => { Continue(); // Skip to next iteration }); }); ``` -------------------------------- ### Use Local Variables to Cache Calculations Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/wgsl-integration.md Performance tip: use local variables to cache repeated calculations. The example computes p2 and p4 once and reuses them, avoiding redundant multiplications. ```wgsl fn compute(p: vec2) -> f32 { // Cache repeated calculations let p2 = p * p; let p4 = p2 * p2; return p2.x + p2.y + p4.x * p4.y; } ``` -------------------------------- ### TSL Utility Functions Usage Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/core-concepts.md Examples of common TSL utility functions for procedural generation and math operations. ```javascript import { hash, checker, remap, range, rotate } from 'three/tsl'; // Pseudo-random hash hash(seed) // Returns 0-1 // Checkerboard pattern checker(uv()) // Returns 0 or 1 // Remap value from one range to another remap(x, 0, 1, -1, 1) // Map 0-1 to -1 to 1 // Generate value in range range(min, max) // Random in range (per instance) // Rotate 2D vector rotate(vec2(1, 0), angle) ``` -------------------------------- ### Post-Processing with RenderPipeline Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/REFERENCE.md Demonstrates setting up a render pipeline, applying effects like bloom, and rendering the final output. ```javascript import { pass } from 'three/tsl'; import { bloom } from 'three/addons/tsl/display/BloomNode.js'; // Setup (RenderPipeline replaced PostProcessing in r183) const renderPipeline = new THREE.RenderPipeline(renderer); const scenePass = pass(scene, camera); const color = scenePass.getTextureNode('output'); // Apply effects const bloomPass = bloom(color); renderPipeline.outputNode = color.add(bloomPass); // Render renderPipeline.render(); ``` -------------------------------- ### Conditional logic with If-Else and select Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/core-concepts.md Examples of using If-ElseIf-Else blocks and the ternary select operator for conditional logic. ```javascript import { If, select } from 'three/tsl'; // If-ElseIf-Else (use with .toVar() or direct .assign()) const result = vec3(0).toVar(); If(value.greaterThan(0.5), () => { result.assign(vec3(1, 0, 0)); // Red }).ElseIf(value.greaterThan(0.25), () => { result.assign(vec3(0, 1, 0)); // Green }).Else(() => { result.assign(vec3(0, 0, 1)); // Blue }); // Ternary operator (select) - PREFERRED for simple conditionals const color = select( condition, // if true vec3(1, 0, 0), // return this vec3(0, 0, 1) // else return this ); ``` -------------------------------- ### Querying Available Features (Raw WebGPU) Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/limits-and-features.md Shows how to request an adapter and check for supported features using the WebGPU API. ```APIDOC ## Querying Available Features (Raw WebGPU) ### Description Requests a GPU adapter and checks for the presence of optional features. ### Method navigator.gpu.requestAdapter() ### Endpoint navigator.gpu.requestAdapter() ### Parameters None ### Request Example ```javascript const adapter = await navigator.gpu?.requestAdapter(); ``` ### Response #### Success Response - **adapter** (GPUAdapter) - The adapter object with a `features` property (a Set of feature names). #### Response Example ```javascript console.log(adapter.features.has('float32-filterable')); console.log(adapter.features.has('shader-f16')); ``` ``` -------------------------------- ### Configuring Environment and Lighting Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/materials.md Setting environment maps and custom lights for node materials. ```javascript import { cubeTexture, envMap } from 'three/tsl'; // Environment map reflection material.envMapNode = cubeTexture(envCubeMap); // Custom lights material.lightsNode = lights(); ``` -------------------------------- ### Handle WebGPU Device Loss Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/REFERENCE.md Demonstrates how to listen for device loss and trigger a recovery, as well as how to manually simulate a loss for testing purposes. ```javascript // Listen for device loss renderer.backend.device.lost.then((info) => { if (info.reason === 'unknown') { // Unexpected loss - recover renderer.dispose(); initWebGPU(); // Reinitialize } }); // Simulate loss for testing renderer.backend.device.destroy(); ``` -------------------------------- ### WGSL Matrix Types Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/wgsl-integration.md Lists WGSL matrix types, where the first number is the column count and the second is the row count. All examples use f32 elements. ```wgsl mat2x2 // 2x2 matrix mat3x3 // 3x3 matrix mat4x4 // 4x4 matrix mat2x3 // 2 columns, 3 rows ``` -------------------------------- ### Querying Available Features in Raw WebGPU Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/limits-and-features.md Demonstrates how to check for specific optional features using the adapter's features Set. ```javascript const adapter = await navigator.gpu?.requestAdapter(); // adapter.features is a Set console.log(adapter.features.has('float32-filterable')); console.log(adapter.features.has('shader-f16')); ``` -------------------------------- ### Configuring PBR Properties Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/materials.md Setting metalness, roughness, and emissive properties for MeshStandardNodeMaterial. ```javascript import { texture, float, color } from 'three/tsl'; // Metalness (0 = dielectric, 1 = metal) material.metalnessNode = texture(metalMap).r; material.metalnessNode = float(0.0); // Roughness (0 = smooth/mirror, 1 = rough) material.roughnessNode = texture(roughnessMap).r; material.roughnessNode = float(0.5); // Emissive (self-illumination) material.emissiveNode = color(0xff0000).mul(2.0); material.emissiveNode = texture(emissiveMap); ``` -------------------------------- ### FBM: Fractal Brownian Motion with wgslFn Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/wgsl-integration.md Defines a WGSL fbm function that uses the snoise function from the previous example to create fractal noise by accumulating octaves. ```javascript const fbm = wgslFn(` fn fbm(p: vec2, octaves: i32) -> f32 { var value = 0.0; var amplitude = 0.5; var frequency = 1.0; var pos = p; for (var i = 0; i < octaves; i = i + 1) { value = value + amplitude * snoise(pos * frequency); amplitude = amplitude * 0.5; frequency = frequency * 2.0; } return value; } `); ``` -------------------------------- ### Import Pattern: WebGPU Entry Point Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/SKILL.md Always use the WebGPU entry point for Three.js and import TSL functions from 'three/tsl'. ```javascript // Always use the WebGPU entry point import * as THREE from 'three/webgpu'; import { /* TSL functions */ } from 'three/tsl'; ``` -------------------------------- ### Initialize WebGPU Renderer and TSL Material Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/README.md Sets up a Three.js WebGPURenderer and creates a MeshStandardNodeMaterial with a custom emissive node using TSL for an animated fresnel effect. ```javascript import * as THREE from 'three/webgpu'; import { color, time, oscSine, normalWorld, cameraPosition, positionWorld, Fn, float } from 'three/tsl'; // WebGPU renderer const renderer = new THREE.WebGPURenderer(); await renderer.init(); // TSL material with animated fresnel const material = new THREE.MeshStandardNodeMaterial(); material.colorNode = color(0x0066ff); material.emissiveNode = Fn(() => { const viewDir = cameraPosition.sub(positionWorld).normalize(); const fresnel = float(1).sub(normalWorld.dot(viewDir).saturate()).pow(3); return color(0x00ffff).mul(fresnel).mul(oscSine(time)); })(); ``` -------------------------------- ### Basic Usage of wgslFn: Gamma Correction Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/wgsl-integration.md Shows the basic wgslFn setup. Import wgslFn, float, and vec3 from 'three/tsl', define a WGSL gammaCorrect function, then call it in a TSL node graph. ```javascript import { wgslFn, float, vec3 } from 'three/tsl'; // Define WGSL function const gammaCorrect = wgslFn(` fn gammaCorrect(color: vec3, gamma: f32) -> vec3 { return pow(color, vec3(1.0 / gamma)); } `); // Use in TSL material.colorNode = gammaCorrect(inputColor, float(2.2)); ``` -------------------------------- ### Check adapter limits before requesting Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/REFERENCE.md Verify if the GPU adapter supports the desired buffer size before requesting higher limits. ```javascript const adapter = await navigator.gpu?.requestAdapter(); if (adapter.limits.maxBufferSize >= desiredSize) { // Safe to request } ``` -------------------------------- ### Set workgroup size in TSL compute shaders Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/compute-shaders.md Shows how to set the workgroup size as an array when calling compute(). Default sizes are typically 64 or 256; examples include 1D and 2D workgroups. ```javascript // Default workgroup size is typically 64 or 256 // Pass workgroup size as an array const computeShader = Fn(() => { // shader code })().compute(count, [64]); // 2D workgroup const compute2D = Fn(() => { // shader code })().compute(width * height, [8, 8]); ``` -------------------------------- ### Configuring Color and Opacity Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/materials.md Setting color and opacity nodes for materials, including alpha testing. ```javascript import { texture, color, float } from 'three/tsl'; // Color from texture material.colorNode = texture(diffuseMap); // Solid color material.colorNode = color(0xff0000); // Computed color material.colorNode = positionLocal.normalize(); // Opacity (requires material.transparent = true) material.opacityNode = float(0.8); material.transparent = true; // Alpha test threshold material.alphaTestNode = float(0.5); ``` -------------------------------- ### Avoid Branching with mix and step Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/wgsl-integration.md Performance tip: avoid branching in WGSL by using mix and step functions instead of an if-else statement. The example shows the branching version and the equivalent branchless version. ```wgsl // Instead of: if (x > 0.5) { result = a; } else { result = b; } // Use: result = mix(b, a, step(0.5, x)); ``` -------------------------------- ### Custom Function Definitions Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/REFERENCE.md Shows how to define reusable functions using Fn, including support for default parameter values. ```javascript // Basic function const myFn = Fn(([a, b]) => { return a.add(b); }); // With defaults const myFn = Fn(([a = 1.0, b = 2.0]) => { return a.add(b); }); // Usage myFn(x, y); myFn(); // uses defaults ``` -------------------------------- ### Create Basic Custom Effect Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/post-processing.md Demonstrates creating a custom effect using Fn to invert colors. ```javascript import { Fn, screenUV, float, vec4 } from 'three/tsl'; const customEffect = Fn(() => { const color = scenePassColor.toVar(); // Invert colors color.rgb.assign(float(1.0).sub(color.rgb)); return color; }); renderPipeline.outputNode = customEffect(); ``` -------------------------------- ### Additional Effects (r182+) Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/post-processing.md Lists various post-processing effects added in recent Three.js releases, each with its import statement and usage example. Includes god rays, retro, anamorphic, lensflare, denoise, SSGI, TAA, and blur modes. ```javascript // Volumetric god rays (r183) import { godrays } from 'three/addons/tsl/display/GodraysNode.js'; const godraysPass = godrays(scenePassColor, depthNode, camera, lightPosition); // Retro/CRT effect (r183) import { retroPass } from 'three/addons/tsl/display/RetroNode.js'; // Anamorphic lens flare import { anamorphic } from 'three/addons/tsl/display/AnamorphicNode.js'; const anamorphicPass = anamorphic(scenePassColor); // Lens flare import { lensflare } from 'three/addons/tsl/display/LensflareNode.js'; // Denoising import { denoise } from 'three/addons/tsl/display/DenoiseNode.js'; // Screen-space global illumination import { ssgi } from 'three/addons/tsl/display/SSGINode.js'; // Temporal anti-aliasing (replaces FXAA/SMAA for better quality) import { traa } from 'three/addons/tsl/display/TRAANode.js'; // Alternative blur modes import { boxBlur } from 'three/addons/tsl/display/BoxBlurNode.js'; // Mobile-friendly import { hashBlur } from 'three/addons/tsl/display/HashBlurNode.js'; // Single-pass import { bilateralBlur } from 'three/addons/tsl/display/BilateralBlurNode.js'; // Edge-preserving // 3D texture sampling (r182) import { texture3DLoad, texture3DLevel } from 'three/tsl'; ``` -------------------------------- ### Restore Application State on Recovery Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/device-loss.md Demonstrates how to persist and restore application state, such as camera position, when reinitializing the renderer after device loss. ```javascript let appState = { cameraPosition: { x: 0, y: 5, z: 10 }, settings: {}, // Don't save transient data like particle positions }; // Save state periodically function saveState() { appState.cameraPosition = { x: camera.position.x, y: camera.position.y, z: camera.position.z }; localStorage.setItem('appState', JSON.stringify(appState)); } // Restore on recovery async function initWebGPU() { renderer = new THREE.WebGPURenderer(); await renderer.init(); const savedState = localStorage.getItem('appState'); if (savedState) { appState = JSON.parse(savedState); } setupScene(); // Restore camera position camera.position.set( appState.cameraPosition.x, appState.cameraPosition.y, appState.cameraPosition.z ); renderer.backend.device.lost.then((info) => { if (info.reason === 'unknown') { saveState(); renderer.dispose(); initWebGPU(); } }); } ``` -------------------------------- ### Log WebGPU adapter limits Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/limits-and-features.md Iterates through and logs all available WebGPU adapter limits to the console to help diagnose buffer size or validation issues. ```javascript // Log all adapter limits const adapter = await navigator.gpu?.requestAdapter(); for (const [key, value] of Object.entries(Object.getPrototypeOf(adapter.limits))) { if (typeof value !== 'function') { console.log(`${key}: ${adapter.limits[key]}`); } } ``` -------------------------------- ### Instanced Mesh with Computed Positions Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/compute-shaders.md Demonstrates how to apply computed position and color nodes to a MeshStandardNodeMaterial for an InstancedMesh. ```javascript // Create instanced mesh const geometry = new THREE.SphereGeometry(0.1, 16, 16); const material = new THREE.MeshStandardNodeMaterial(); // Use computed positions material.positionNode = positions.element(instanceIndex); // Optionally use computed colors material.colorNode = colors.element(instanceIndex); const mesh = new THREE.InstancedMesh(geometry, material, count); scene.add(mesh); ``` -------------------------------- ### Compute Shader Implementation Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/REFERENCE.md Covers storage buffer creation, compute shader definition, execution via the renderer, and setting workgroup sizes. ```javascript // Storage buffers (read-write) const positions = instancedArray(count, 'vec3'); const values = instancedArray(count, 'float'); // Read-only storage buffers const lookupTable = attributeArray(data, 'float'); // Compute shader const compute = Fn(() => { const pos = positions.element(instanceIndex); pos.addAssign(vec3(0.01, 0, 0)); })().compute(count); // Execute (after await renderer.init()) renderer.compute(compute); // Workgroup size const compute2 = Fn(() => { /* ... */ })().compute(count, [64]); ``` -------------------------------- ### Compute Shader Execution Methods Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/compute-shaders.md Illustrates the synchronous compute execution pattern required since r181, emphasizing the need to initialize the renderer first. ```javascript // IMPORTANT: Always initialize the renderer first await renderer.init(); // Synchronous compute (preferred since r181) renderer.compute(computeShader); // Multiple computes renderer.compute(computeInit); renderer.compute(computePhysics); renderer.compute(computeCollisions); // Note: computeAsync() is deprecated since r181. // Use await renderer.init() at startup, then renderer.compute() synchronously. ``` -------------------------------- ### Handle GPUDeviceLostInfo reason Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/device-loss.md Check the GPUDeviceLostInfo reason property to distinguish between intentional destruction and unexpected device loss. ```javascript device.lost.then((info) => { if (info.reason === 'unknown') { // Unexpected loss - attempt recovery handleUnexpectedDeviceLoss(); } else { // Intentional destruction - expected behavior } }); ``` -------------------------------- ### Initialize Particles with Compute Shader Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/compute-shaders.md Sets random positions for particles using a hash function and zeroes their velocities. Run once at startup after renderer.init(). ```javascript const computeInit = Fn(() => { const position = positions.element(instanceIndex); const velocity = velocities.element(instanceIndex); // Random positions using hash position.x.assign(hash(instanceIndex).mul(10).sub(5)); position.y.assign(hash(instanceIndex.add(1)).mul(10).sub(5)); position.z.assign(hash(instanceIndex.add(2)).mul(10).sub(5)); // Zero velocity velocity.assign(vec3(0)); })().compute(count); // Run once at startup (after await renderer.init()) renderer.compute(computeInit); ``` -------------------------------- ### Safe Pattern: Check Before Requesting Limits Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/limits-and-features.md Safely request increased limits by checking adapter support first. Only adds the limit if the adapter supports it, otherwise warns and uses the default. This avoids requestDevice() or renderer.init() failing due to unsupported limits. ```javascript const adapter = await navigator.gpu?.requestAdapter(); const desiredBufferSize = 1024 * 1024 * 1024; // 1 GiB const requiredLimits = {}; if (adapter.limits.maxBufferSize >= desiredBufferSize) { requiredLimits.maxBufferSize = desiredBufferSize; } else { console.warn('Adapter does not support 1 GiB buffers, using default'); } const renderer = new THREE.WebGPURenderer({ requiredLimits }); await renderer.init(); ``` -------------------------------- ### Configuring Physical Properties Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/materials.md Setting advanced physical properties for MeshPhysicalNodeMaterial, such as clearcoat, transmission, and iridescence. ```javascript const material = new THREE.MeshPhysicalNodeMaterial(); // Clearcoat (car paint effect) material.clearcoatNode = float(1.0); material.clearcoatRoughnessNode = float(0.1); material.clearcoatNormalNode = normalMap(texture(clearcoatNormalMap)); // Transmission (glass/translucency) material.transmissionNode = float(0.9); material.thicknessNode = float(0.5); material.attenuationDistanceNode = float(1.0); material.attenuationColorNode = color(0xffffff); // Iridescence (soap bubble effect) material.iridescenceNode = float(1.0); material.iridescenceIORNode = float(1.3); material.iridescenceThicknessNode = float(400); // Sheen (fabric effect) material.sheenNode = float(1.0); material.sheenRoughnessNode = float(0.5); material.sheenColorNode = color(0xffffff); // Anisotropy (brushed metal) material.anisotropyNode = float(1.0); material.anisotropyRotationNode = float(0); // Specular material.specularIntensityNode = float(1.0); material.specularColorNode = color(0xffffff); // Index of Refraction material.iorNode = float(1.5); // Dispersion (rainbow effect in glass) material.dispersionNode = float(0.0); ``` -------------------------------- ### Synchronize workgroups with barriers Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/compute-shaders.md Imports workgroupBarrier, storageBarrier, textureBarrier from 'three/tsl'. Shows how to use workgroupBarrier to ensure all threads reach a point before reading shared data. ```javascript import { workgroupBarrier, storageBarrier, textureBarrier } from 'three/tsl'; const computeShader = Fn(() => { // Write data sharedData.element(localIndex).assign(value); // Ensure all workgroup threads reach this point workgroupBarrier(); // Now safe to read data written by other threads const neighborValue = sharedData.element(localIndex.add(1)); })().compute(count); ``` -------------------------------- ### Points with Computed Positions Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/compute-shaders.md Shows how to configure a PointsNodeMaterial with computed position, color, and size nodes for a Points object. ```javascript const geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array(count * 3), 3)); const material = new THREE.PointsNodeMaterial(); material.positionNode = positions.element(instanceIndex); material.colorNode = colors.element(instanceIndex); material.sizeNode = float(5.0); const points = new THREE.Points(geometry, material); scene.add(points); ``` -------------------------------- ### Project Directory Structure for Cursor Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/README.md Required directory structure for using the rules in a custom project, ensuring the .cursor/rules/ and skills/ directories are preserved. ```text your-project/ ├── .cursor/rules/ # from this repo └── skills/webgpu-threejs-tsl/ # from this repo (referenced by the rules) ``` -------------------------------- ### Query Adapter Limits Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/limits-and-features.md Query the adapter's limits to see the maximum supported values for maxBufferSize and maxStorageBufferBindingSize. Use this to determine what limits you can request when creating a device. ```javascript const adapter = await navigator.gpu?.requestAdapter(); console.log(adapter.limits.maxBufferSize); console.log(adapter.limits.maxStorageBufferBindingSize); ``` -------------------------------- ### Request Increased Limits with Raw WebGPU Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/limits-and-features.md Request increased limits when creating a WebGPU device. If the adapter doesn't support a requested limit, requestDevice() will fail. ```javascript const adapter = await navigator.gpu?.requestAdapter(); const device = await adapter.requestDevice({ requiredLimits: { maxBufferSize: 1024 * 1024 * 1024, // 1 GiB maxStorageBufferBindingSize: 1024 * 1024 * 512, // 512 MiB }, }); ``` -------------------------------- ### Solution 3: Use .toVar() for mutable variables Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/compute-shaders.md Shows how to create a mutable GPU variable with .toVar() that can be reassigned using .assign() inside If() blocks. ```javascript // ✅ CORRECT - Use .toVar() for variables that need mutation const computeShader = Fn(() => { // .toVar() creates a proper GPU variable that can be reassigned const value = buffer.element(index).toFloat().toVar(); If(condition, () => { value.assign(value.add(1.0)); // This works with .toVar()! }); buffer.element(index).assign(value); })().compute(count); ``` -------------------------------- ### Use vertexColor, instanceIndex, and vertexIndex nodes Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/materials.md Imports and uses other geometry attribute nodes: vertexColor() for vertex colors, instanceIndex for instanced meshes, and vertexIndex for the current vertex index. ```javascript import { vertexColor, instanceIndex, vertexIndex } from 'three/tsl'; vertexColor() // Vertex colors (if present) instanceIndex // Index for instanced meshes vertexIndex // Current vertex index ``` -------------------------------- ### Transitions with TSL Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/post-processing.md Shows how to create a transition between two scene passes using the `transition` node. Includes an optional transition texture and an animation loop that updates the progress uniform. ```javascript import { transition } from 'three/addons/tsl/display/TransitionNode.js'; const scenePassA = pass(sceneA, camera); const scenePassB = pass(sceneB, camera); const transitionProgress = uniform(0); const transitionPass = transition( scenePassA.getTextureNode('output'), scenePassB.getTextureNode('output'), transitionProgress, texture(transitionTexture) // Optional transition texture ); renderPipeline.outputNode = transitionPass; // Animate transition function animate() { transitionProgress.value = Math.sin(time) * 0.5 + 0.5; renderPipeline.render(); } ``` -------------------------------- ### Hybrid WGSL and TSL Approach Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/wgsl-integration.md Demonstrates a hybrid approach: complex math (blending, rotation, normalization) is implemented in WGSL via wgslFn, while simpler logic (texture sampling, mixing) is done in TSL with Fn. The final result is assigned to material.colorNode. ```javascript // Complex math in WGSL const complexMath = wgslFn(` fn complexOperation(a: vec3, b: vec3, t: f32) -> vec3 { let blended = mix(a, b, t); let rotated = vec3( blended.x * cos(t) - blended.y * sin(t), blended.x * sin(t) + blended.y * cos(t), blended.z ); return normalize(rotated); } `); // Simple logic in TSL const finalColor = Fn(() => { const base = texture(diffuseMap).rgb; const processed = complexMath(base, vec3(1, 0, 0), time); return mix(base, processed, oscSine(time)); }); material.colorNode = finalColor(); ``` -------------------------------- ### Listen for WebGPU device loss Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/device-loss.md Attach a callback to the device.lost promise to detect when the GPU device becomes unusable. Do not await this promise directly as it will block indefinitely if no loss occurs. ```javascript const adapter = await navigator.gpu.requestAdapter(); if (!adapter) { return; } const device = await adapter.requestDevice(); device.lost.then((info) => { console.error('WebGPU device lost:', info.message); // Handle recovery }); ``` -------------------------------- ### Define TSL functions with default parameters Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/core-concepts.md Demonstrates how to provide default values for function arguments, allowing for flexible call signatures. ```javascript const oscillate = Fn(([frequency = 1.0, amplitude = 1.0]) => { return time.mul(frequency).sin().mul(amplitude); }); // Call variations oscillate(); // Uses defaults oscillate(2.0); // frequency = 2.0 oscillate(2.0, 0.5); // frequency = 2.0, amplitude = 0.5 ``` -------------------------------- ### Correct stamp/fade pattern using select() Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/compute-shaders.md Shows a complete compute shader implementation for conditional stamping with falloff, using select() to handle the radius check and combining with max() and clamp(). ```javascript // ✅ CORRECT implementation of conditional stamping const computeShader = Fn(() => { const currentFoam = foamBuffer.element(index).toFloat(); // Calculate distance const dist = worldPos.distance(stampPos); const radius = float(50.0); // Calculate falloff (will be 0 outside radius due to select) const falloff = float(1.0).sub(dist.div(radius)); // Use select() - returns falloff if inside radius, 0 if outside const foamToAdd = select(dist.lessThan(radius), falloff, float(0.0)); // Combine and write const newFoam = max(currentFoam, foamToAdd); foamBuffer.element(index).assign(clamp(newFoam, 0.0, 1.0)); })().compute(bufferSize); ``` -------------------------------- ### Import Screen Space Nodes from three/tsl Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/materials.md Imports screen-space nodes from 'three/tsl' for shader node materials. Provides screen UV, pixel coordinates, screen size, viewport UV, viewport dimensions, and fragment depth. ```javascript import { screenUV, // Screen UV (0-1) screenCoordinate, // Pixel coordinates screenSize, // Screen dimensions viewportUV, // Viewport UV viewport, // Viewport dimensions depth // Fragment depth } from 'three/tsl'; ``` -------------------------------- ### Control Flow Syntax Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/REFERENCE.md Demonstrates conditional logic, ternary selection, loops, and flow control statements like Break, Continue, and Discard. ```javascript // If-Else If(condition, () => { // true }).ElseIf(other, () => { // other true }).Else(() => { // false }); // Select (ternary) const result = select(condition, trueVal, falseVal); // Loop Loop(10, ({ i }) => { // i = 0 to 9 }); // Loop control Break(); Continue(); Discard(); // Fragment only ``` -------------------------------- ### Simulate device loss with destroy() Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/device-loss.md Use device.destroy() to trigger the device.lost promise for testing recovery logic. Note that this method unmaps buffers immediately and always allows recovery. ```javascript let simulatedLoss = false; function simulateDeviceLoss() { simulatedLoss = true; renderer.backend.device.destroy(); } // In your device.lost handler: device.lost.then((info) => { if (info.reason === 'unknown' || simulatedLoss) { simulatedLoss = false; // Treat as unexpected loss for testing handleDeviceLoss(); } }); // Add debug keybinding window.addEventListener('keydown', (e) => { if (e.key === 'L' && e.ctrlKey && e.shiftKey) { simulateDeviceLoss(); } }); ``` -------------------------------- ### Import normal attribute nodes from three/tsl Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/materials.md Imports the normal attribute nodes from three/tsl. Use normalWorld for lighting calculations, as it provides the normal in world space. ```javascript import { normalGeometry, // Original mesh normal normalLocal, // Normal in model space normalWorld, // Normal in world space (use for lighting) normalView // Normal in camera space } from 'three/tsl'; ``` -------------------------------- ### Solution 1: Use select() for conditional values Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/compute-shaders.md Shows how to use the select() function to choose between two values based on a condition, avoiding the need for variable reassignment. Requires importing select from 'three/tsl'. ```javascript // ✅ CORRECT - Use select() for inline conditionals import { select } from 'three/tsl'; const computeShader = Fn(() => { const currentValue = buffer.element(index).toFloat(); // select(condition, valueIfTrue, valueIfFalse) const newValue = select( condition, currentValue.add(1.0), // If true currentValue // If false ); buffer.element(index).assign(newValue); })().compute(count); ``` -------------------------------- ### Set up MRT with color, normal, and depth targets Source: https://github.com/dgreenheck/webgpu-claude-skill/blob/main/skills/webgpu-threejs-tsl/docs/post-processing.md Configures a scene pass to output multiple render targets (color, normal, depth) using the mrt() function from 'three/tsl'. After setting up, individual targets are accessed via getTextureNode(). ```javascript import { mrt, output } from 'three/tsl'; const scenePass = pass(scene, camera); // Set up MRT scenePass.setMRT(mrt({ output: output, // Color output normal: normalView, // View-space normals depth: depth // Depth buffer })); // Access individual targets const colorTexture = scenePass.getTextureNode('output'); const normalTexture = scenePass.getTextureNode('normal'); const depthTexture = scenePass.getTextureNode('depth'); ```