Skip to content

Instantly share code, notes, and snippets.

@vidyesh95
Created October 11, 2025 05:08
Show Gist options
  • Save vidyesh95/8b819c74b445d75e3cfccb05f5487318 to your computer and use it in GitHub Desktop.
Save vidyesh95/8b819c74b445d75e3cfccb05f5487318 to your computer and use it in GitHub Desktop.
Hybrid shader for svelte
<script lang="ts">
import HybridShader from '$lib/components/landing/HybridShader.svelte';
import { onMount } from 'svelte';
import type { Component } from 'svelte';
let HybridShaderComp = $state<Component | null>(null);
onMount(async () => {
const m = await import('$lib/components/landing/HybridShader.svelte');
HybridShaderComp = m.default;
});
</script>
<section
id="home"
class="md:pt-16 relative flex flex-col items-center md:gap-4 px-4 py-8"
>
<!-- page decoration -->
<div
class="bg-black absolute inset-0 flex justify-center items-start md:items-center z-[-3] pointer-events-none"
>
{#if HybridShaderComp}
<HybridShader
class="w-full h-full object-cover"
u_color={[0.3137254901960784, 0, 1]}
u_background={[0,0,0,1]}
u_speed={0.9}
u_detail={0.4}
/>
{/if}
</div>
</section>
<!-- WebGPU type declarations at file level -->
<script lang="ts" module>
// WebGPU type declarations
declare global {
interface Navigator {
gpu?: GPU;
}
interface GPU {
requestAdapter(options?: GPURequestAdapterOptions): Promise<GPUAdapter | null>;
getPreferredCanvasFormat(): GPUTextureFormat;
}
interface GPUAdapter {
requestDevice(descriptor?: GPUDeviceDescriptor): Promise<GPUDevice>;
}
interface GPUDevice {
createBuffer(descriptor: GPUBufferDescriptor): GPUBuffer;
createBindGroupLayout(descriptor: GPUBindGroupLayoutDescriptor): GPUBindGroupLayout;
createBindGroup(descriptor: GPUBindGroupDescriptor): GPUBindGroup;
createRenderPipeline(descriptor: GPURenderPipelineDescriptor): GPURenderPipeline;
createPipelineLayout(descriptor: GPUPipelineLayoutDescriptor): GPUPipelineLayout;
createShaderModule(descriptor: GPUShaderModuleDescriptor): GPUShaderModule;
createCommandEncoder(descriptor?: GPUCommandEncoderDescriptor): GPUCommandEncoder;
queue: GPUQueue;
}
interface GPUQueue {
writeBuffer(buffer: GPUBuffer, bufferOffset: number, data: ArrayBufferView): void;
submit(commandBuffers: GPUCommandBuffer[]): void;
}
interface GPUCanvasContext {
configure(configuration: GPUCanvasConfiguration): void;
getCurrentTexture(): GPUTexture;
}
interface HTMLCanvasElement {
getContext(contextId: 'webgpu'): GPUCanvasContext | null;
}
}
// WebGPU interface definitions
interface GPUBuffer {
destroy(): void;
}
interface GPUBindGroup {}
interface GPURenderPipeline {}
interface GPUBindGroupLayout {}
interface GPUPipelineLayout {}
interface GPUShaderModule {}
interface GPUCommandEncoder {
beginRenderPass(descriptor: GPURenderPassDescriptor): GPURenderPassEncoder;
finish(): GPUCommandBuffer;
}
interface GPUCommandBuffer {}
interface GPUTexture {
createView(): GPUTextureView;
}
interface GPUTextureView {}
interface GPURenderPassEncoder {
setPipeline(pipeline: GPURenderPipeline): void;
setBindGroup(index: number, bindGroup: GPUBindGroup): void;
draw(vertexCount: number): void;
end(): void;
}
// WebGPU descriptor interfaces
interface GPURequestAdapterOptions {}
interface GPUDeviceDescriptor {}
interface GPUBufferDescriptor {
size: number;
usage: GPUBufferUsageFlags;
}
interface GPUBindGroupLayoutDescriptor {
entries: GPUBindGroupLayoutEntry[];
}
interface GPUBindGroupLayoutEntry {
binding: number;
visibility: GPUShaderStageFlags;
buffer?: { type: string };
}
interface GPUBindGroupDescriptor {
layout: GPUBindGroupLayout;
entries: GPUBindGroupEntry[];
}
interface GPUBindGroupEntry {
binding: number;
resource: { buffer: GPUBuffer };
}
interface GPUPipelineLayoutDescriptor {
bindGroupLayouts: GPUBindGroupLayout[];
}
interface GPUShaderModuleDescriptor {
code: string;
}
interface GPURenderPipelineDescriptor {
layout: GPUPipelineLayout;
vertex: GPUVertexState;
fragment?: GPUFragmentState;
primitive?: GPUPrimitiveState;
}
interface GPUVertexState {
module: GPUShaderModule;
entryPoint: string;
}
interface GPUFragmentState {
module: GPUShaderModule;
entryPoint: string;
targets: GPUColorTargetState[];
}
interface GPUColorTargetState {
format: GPUTextureFormat;
}
interface GPUPrimitiveState {
topology: string;
}
interface GPUCanvasConfiguration {
device: GPUDevice;
format: GPUTextureFormat;
alphaMode: string;
}
interface GPUCommandEncoderDescriptor {}
interface GPURenderPassDescriptor {
colorAttachments: GPURenderPassColorAttachment[];
}
interface GPURenderPassColorAttachment {
view: GPUTextureView;
clearValue: { r: number; g: number; b: number; a: number };
loadOp: string;
storeOp: string;
}
// Type aliases
type GPUTextureFormat = string;
type GPUBufferUsageFlags = number;
type GPUShaderStageFlags = number;
// Constants
const GPUBufferUsage = {
UNIFORM: 0x40,
COPY_DST: 0x08
} as const;
const GPUShaderStage = {
FRAGMENT: 0x2
} as const;
</script>
<script lang="ts">
/**
* Public component properties.
*
* @property {string} class CSS class applied to the <canvas>. Default: ''.
* @property {[number, number, number]} u_color Foreground RGB in 0–1. Default: [0.3137254901960784, 0, 1].
* @property {[number, number, number, number]} u_background Background RGBA in 0–1. Default: [0, 0, 0, 1].
* @property {number} u_speed Animation speed multiplier (>=0). Default: 0.1.
* @property {number} u_detail Ray-march iteration factor in [0,1]; controls cost/quality. Default: 0.4.
* @property {number} maxDpr Upper bound for devicePixelRatio used for rendering resolution. Default: 2.
*
* Behavior:
* Props influence uniforms for both the WebGPU and WebGL paths.
* Values are read each frame; updates are reflected without reinitialization.
*/
let {
class: klass = '',
u_color = [0.3137254901960784, 0, 1],
u_background = [0, 0, 0, 1],
u_speed = 0.1,
u_detail = 0.4,
maxDpr = 2
} = $props();
/** Canvas reference; bound to the root <canvas>. */
let canvas: HTMLCanvasElement | undefined = $state();
// WebGPU state
let device: GPUDevice | null = $state(null);
let gpuContext: GPUCanvasContext | null = $state(null);
let pipeline: GPURenderPipeline | null = $state(null);
let uniformBuffer: GPUBuffer | null = $state(null);
let bindGroup: GPUBindGroup | null = $state(null);
// WebGL state
let gl: WebGLRenderingContext | null = $state(null);
let glProgram: WebGLProgram | null = $state(null);
let positionBuf: WebGLBuffer | null = $state(null);
let a_position = $state(-1);
let uTime: WebGLUniformLocation | null = $state(null);
let uMouse: WebGLUniformLocation | null = $state(null);
let uRes: WebGLUniformLocation | null = $state(null);
let uColor: WebGLUniformLocation | null = $state(null);
let uBg: WebGLUniformLocation | null = $state(null);
let uSpeed: WebGLUniformLocation | null = $state(null);
let uDetail: WebGLUniformLocation | null = $state(null);
// Common state
let isWebGPU = $state(false);
let start = $state(0);
let raf = $state(0);
let mouseX = $state(0);
let mouseY = $state(0);
/**
* WebGPU full-screen triangle vertex shader (WGSL).
* Emits clip-space positions covering the viewport with two triangles.
*/
const WEBGPU_VERTEX_SHADER = `
@vertex
fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> @builtin(position) vec4f {
var pos = array<vec2f, 6>(
vec2f(-1.0, -1.0),
vec2f( 1.0, -1.0),
vec2f(-1.0, 1.0),
vec2f( 1.0, -1.0),
vec2f( 1.0, 1.0),
vec2f(-1.0, 1.0)
);
return vec4f(pos[vertexIndex], 0.0, 1.0);
}
`;
/**
* WebGPU fragment shader (WGSL).
*
* Uniform layout (std140-like tightly packed float32 array written by JS):
* [0]=resolution.x, [1]=resolution.y, [2]=time(s), [3]=speed, [4]=detail,
* [8..10]=color.rgb, [12..15]=background.rgba, [16]=mouse.x, [17]=mouse.y.
*
* Performs simple ray-marching to synthesize a dynamic field and composites foreground on background.
*/
const WEBGPU_FRAGMENT_SHADER = `
struct Uniforms {
resolution: vec2f,
time: f32,
speed: f32,
detail: f32,
padding1: f32,
padding2: f32,
padding3: f32,
color: vec3f,
padding4: f32,
background: vec4f,
mouse: vec2f,
padding5: f32,
padding6: f32,
}
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
fn rotate2D(a: f32) -> mat2x2f {
let c = cos(a);
let s = sin(a);
return mat2x2f(c, -s, s, c);
}
fn map(p: vec3f) -> f32 {
let t = uniforms.time * uniforms.speed;
var pos = p;
pos = vec3f(rotate2D(t * 0.4) * pos.xz, pos.y).xzy;
pos = vec3f(rotate2D(t * 0.1) * pos.xy, pos.z);
let q = pos * 2.0 + t;
return length(pos + vec3f(sin(t * uniforms.speed * 0.1)))
* log(length(pos) + 0.9)
+ cos(q.x + sin(q.z + cos(q.y))) * 0.5 - 1.0;
}
@fragment
fn fs_main(@builtin(position) fragCoord: vec4f) -> @location(0) vec4f {
let a = (fragCoord.xy - 0.6 * uniforms.resolution) / min(uniforms.resolution.x, uniforms.resolution.y);
var cl = vec3f(0.0);
var d = 2.5;
let maxSteps = i32(1.0 + 20.0 * uniforms.detail);
for (var i = 0; i < 64; i++) {
if (i >= maxSteps) { break; }
let p = vec3f(0.0, 0.0, 4.0) + normalize(vec3f(a, -1.0)) * d;
let rz = map(p);
let f = clamp((rz - map(p + 0.1)) * 0.5, -0.1, 1.0);
let l = vec3f(0.1, 0.3, 0.4) + vec3f(5.0, 2.5, 3.0) * f;
cl = cl * l + smoothstep(2.5, 0.0, rz) * 0.6 * l;
d += min(rz, 1.0);
}
var color = vec4f(min(uniforms.color, cl), 1.0);
color = vec4f(
max(uniforms.background.r, color.r),
max(uniforms.background.g, color.g),
max(uniforms.background.b, color.b),
1.0
);
return color;
}
`;
/**
* WebGL vertex shader (GLSL ES 1.00). Outputs a full-screen triangle strip.
*/
const WEBGL_VERTEX_SHADER = `
precision highp float;
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
}
`;
/**
* WebGL fragment shader (GLSL ES 1.00).
* Mirrors the WGSL shader’s math. Uniforms:
* - u_resolution(vec2 px), u_time(seconds), u_color(vec3 0–1), u_background(vec4 0–1),
* u_speed(scalar), u_detail([0,1]), u_mouse(vec2 0–1 normalized window coords).
*/
const WEBGL_FRAGMENT_SHADER = `
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
uniform vec3 u_color;
uniform vec4 u_background;
uniform float u_speed;
uniform float u_detail;
uniform vec2 u_mouse;
mat2 m(float a){
float c = cos(a), s = sin(a);
return mat2(c,-s,s,c);
}
float map(vec3 p){
float t = u_time * u_speed;
p.xz *= m(t * 0.4);
p.xy *= m(t * 0.1);
vec3 q = p * 2.0 + t;
return length(p + vec3(sin((t*u_speed) * 0.1)))
* log(length(p) + 0.9)
+ cos(q.x + sin(q.z + cos(q.y))) * 0.5 - 1.0;
}
void main(){
vec2 a = (gl_FragCoord.xy - 0.6 * u_resolution) / min(u_resolution.x, u_resolution.y);
vec3 cl = vec3(0.0);
float d = 2.5;
int maxSteps = int(1.0 + 20.0 * u_detail);
for (int i = 0; i < 64; ++i) {
if (i >= maxSteps) break;
vec3 p = vec3(0.0, 0.0, 4.0) + normalize(vec3(a, -1.0)) * d;
float rz = map(p);
float f = clamp((rz - map(p + 0.1)) * 0.5, -0.1, 1.0);
vec3 l = vec3(0.1, 0.3, 0.4) + vec3(5.0, 2.5, 3.0) * f;
cl = cl * l + smoothstep(2.5, 0.0, rz) * 0.6 * l;
d += min(rz, 1.0);
}
vec4 color = vec4(min(u_color, cl), 1.0);
color.r = max(u_background.r, color.r);
color.g = max(u_background.g, color.g);
color.b = max(u_background.b, color.b);
gl_FragColor = color;
}
`;
/**
* Initialize WebGPU rendering pipeline and resources.
*
* Returns:
* - Promise<boolean>: true if WebGPU successfully initialized and configured for the canvas; false otherwise.
*
* Behavior:
* - Checks support, requests adapter/device, configures canvas context with the preferred format.
* - Creates an 80-byte uniform buffer, bind group/layout, and a render pipeline using the WGSL shaders.
* - Catches and logs failures; leaves state null on failure.
*
* Side effects:
* - Mutates `device`, `gpuContext`, `pipeline`, `uniformBuffer`, `bindGroup`.
* - Configures the GPUCanvasContext with `alphaMode: 'premultiplied'`.
*/
async function initWebGPU(): Promise<boolean> {
if (!canvas || !('gpu' in navigator) || !navigator.gpu) return false;
try {
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) return false;
device = await adapter.requestDevice();
gpuContext = canvas.getContext('webgpu') as GPUCanvasContext;
if (!gpuContext) return false;
const format = navigator.gpu.getPreferredCanvasFormat();
gpuContext.configure({
device,
format,
alphaMode: 'premultiplied'
});
uniformBuffer = device.createBuffer({
size: 80,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});
const bindGroupLayout = device.createBindGroupLayout({
entries: [{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
buffer: { type: 'uniform' }
}]
});
bindGroup = device.createBindGroup({
layout: bindGroupLayout,
entries: [{
binding: 0,
resource: { buffer: uniformBuffer }
}]
});
pipeline = device.createRenderPipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayout]
}),
vertex: {
module: device.createShaderModule({ code: WEBGPU_VERTEX_SHADER }),
entryPoint: 'vs_main'
},
fragment: {
module: device.createShaderModule({ code: WEBGPU_FRAGMENT_SHADER }),
entryPoint: 'fs_main',
targets: [{ format }]
},
primitive: {
topology: 'triangle-list'
}
});
return true;
} catch (error) {
console.warn('WebGPU initialization failed:', error);
return false;
}
}
/**
* Compile a WebGL shader from source.
*
* @param {number} type gl.VERTEX_SHADER or gl.FRAGMENT_SHADER.
* @param {string} src GLSL ES 1.00 source.
* @returns {WebGLShader|null} Compiled shader or null on error/unsupported context.
*
* Behavior:
* - Requires `gl` to be non-null.
* - Logs compile errors via `console.error` and clean up failed shader objects.
*/
function createShader(type: number, src: string): WebGLShader | null {
if (!gl) return null;
const sh = gl.createShader(type);
if (!sh) return null;
gl.shaderSource(sh, src);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(sh));
gl.deleteShader(sh);
return null;
}
return sh;
}
/**
* Link a WebGL program from vertex and fragment sources.
*
* @param {string} vsSrc Vertex shader source (GLSL ES 1.00).
* @param {string} fsSrc Fragment shader source (GLSL ES 1.00).
* @returns {WebGLProgram|null} Linked program or null on failure.
*
* Behavior:
* - Compiles both shaders, links program, deletes shader objects after a link.
* - Logs link errors via `console.error` and deletes the program on failure.
*/
function createProgram(vsSrc: string, fsSrc: string): WebGLProgram | null {
if (!gl) return null;
const vs = createShader(gl.VERTEX_SHADER, vsSrc);
const fs = createShader(gl.FRAGMENT_SHADER, fsSrc);
if (!vs || !fs) return null;
const prog = gl.createProgram();
if (!prog) return null;
gl.attachShader(prog, vs);
gl.attachShader(prog, fs);
gl.linkProgram(prog);
gl.deleteShader(vs);
gl.deleteShader(fs);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(prog));
gl.deleteProgram(prog);
return null;
}
return prog;
}
/**
* Initialize WebGL rendering pipeline and buffers.
*
* @returns {boolean} true if WebGL is available and initialized; false otherwise.
*
* Behavior:
* - Creates a WebGL context, compiles/link shaders, sets up a fullscreen quad VBO.
* - Locates uniforms/attributes, and disables depth/cull for 2D rendering.
* - Configures clear color to opaque black.
*
* Side effects:
* - Mutates `gl`, `glProgram`, `positionBuf`, attribute/uniform locations.
*/
function initWebGL(): boolean {
if (!canvas) return false;
gl = canvas.getContext('webgl', {
antialias: true,
alpha: true,
preserveDrawingBuffer: false,
powerPreference: 'high-performance'
});
if (!gl) return false;
gl.clearColor(0, 0, 0, 1);
glProgram = createProgram(WEBGL_VERTEX_SHADER, WEBGL_FRAGMENT_SHADER);
if (!glProgram) return false;
gl.useProgram(glProgram);
positionBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1, -1, 1, -1, -1, 1,
1, -1, 1, 1, -1, 1
]), gl.STATIC_DRAW);
a_position = gl.getAttribLocation(glProgram, 'a_position');
gl.enableVertexAttribArray(a_position);
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
uTime = gl.getUniformLocation(glProgram, 'u_time');
uMouse = gl.getUniformLocation(glProgram, 'u_mouse');
uRes = gl.getUniformLocation(glProgram, 'u_resolution');
uColor = gl.getUniformLocation(glProgram, 'u_color');
uBg = gl.getUniformLocation(glProgram, 'u_background');
uSpeed = gl.getUniformLocation(glProgram, 'u_speed');
uDetail = gl.getUniformLocation(glProgram, 'u_detail');
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.CULL_FACE);
return true;
}
/**
* Resize the canvas to match CSS size * devicePixelRatio (clamped by `maxDpr`).
* Updates WebGL viewport and `u_resolution` uniform; WebGPU uses swapchain size implicitly.
*/
function setSize() {
if (!canvas) return;
const dpr = Math.min((window.devicePixelRatio || 1), maxDpr);
const rect = canvas.getBoundingClientRect();
const w = Math.max(1, Math.floor(rect.width * dpr));
const h = Math.max(1, Math.floor(rect.height * dpr));
if (canvas.width !== w || canvas.height !== h) {
canvas.width = w;
canvas.height = h;
}
if (isWebGPU) {
// WebGPU doesn't need explicit viewport setting
} else if (gl && uRes) {
gl.viewport(0, 0, w, h);
gl.uniform2f(uRes, w, h);
}
}
/**
* Render a frame via WebGPU.
*
* Inputs:
* - Reads `u_speed`, `u_detail`, `u_color`, `u_background`, `mouseX`, `mouseY`, current time.
*
* Behavior:
* - Packs uniforms into a Float32Array and writes to `uniformBuffer`.
* - Records a single-pass render command, draws 6 vertices (two triangles).
* - Submits the command buffer to `device.queue`.
*
* Preconditions:
* - `device`, `gpuContext`, `pipeline`, `bindGroup`, `uniformBuffer`, `canvas` are non-null.
*/
function renderWebGPU() {
if (!device || !gpuContext || !pipeline || !bindGroup || !uniformBuffer || !canvas) return;
const rect = canvas.getBoundingClientRect();
const dpr = Math.min((window.devicePixelRatio || 1), maxDpr);
const w = rect.width * dpr;
const h = rect.height * dpr;
const uniformData = new Float32Array(20);
uniformData[0] = w;
uniformData[1] = h;
uniformData[2] = (performance.now() - start) * 0.001;
uniformData[3] = u_speed;
uniformData[4] = u_detail;
uniformData[8] = u_color[0];
uniformData[9] = u_color[1];
uniformData[10] = u_color[2];
uniformData[12] = u_background[0];
uniformData[13] = u_background[1];
uniformData[14] = u_background[2];
uniformData[15] = u_background[3];
uniformData[16] = mouseX;
uniformData[17] = mouseY;
device.queue.writeBuffer(uniformBuffer, 0, uniformData);
const commandEncoder = device.createCommandEncoder();
const textureView = gpuContext.getCurrentTexture().createView();
const renderPass = commandEncoder.beginRenderPass({
colorAttachments: [{
view: textureView,
clearValue: { r: 0, g: 0, b: 0, a: 1 },
loadOp: 'clear',
storeOp: 'store'
}]
});
renderPass.setPipeline(pipeline);
renderPass.setBindGroup(0, bindGroup);
renderPass.draw(6);
renderPass.end();
device.queue.submit([commandEncoder.finish()]);
}
/**
* Render a frame via WebGL.
*
* Behavior:
* - Computes elapsed time in seconds, binds program, clears color buffer.
* - Updates all active uniforms, and draws 6 vertices forming two triangles.
*
* Preconditions:
* - `gl` and `glProgram` are initialized by `initWebGL()`.
*/
function renderWebGL() {
if (!gl || !glProgram) return;
const sec = (performance.now() - start) * 0.001;
gl.useProgram(glProgram);
gl.clear(gl.COLOR_BUFFER_BIT);
if (uTime) gl.uniform1f(uTime, sec);
if (uMouse) gl.uniform2f(uMouse, mouseX, mouseY);
if (uColor) gl.uniform3f(uColor, u_color[0], u_color[1], u_color[2]);
if (uBg) gl.uniform4f(uBg, u_background[0], u_background[1], u_background[2], u_background[3]);
if (uSpeed) gl.uniform1f(uSpeed, u_speed);
if (uDetail) gl.uniform1f(uDetail, u_detail);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
/**
* Main animation loop.
*
* Behavior:
* - Lazily sets `start` time.
* - Renders one frame using the active backend (WebGPU/WebGL).
* - Schedules the next frame via `requestAnimationFrame`, storing the id in `raf`.
*/
function loop() {
if (!start) start = performance.now();
if (isWebGPU) {
renderWebGPU();
} else {
renderWebGL();
}
raf = requestAnimationFrame(loop);
}
/**
* Initialize and setup.Lifecycle effect.
*
* Behavior:
* - On mount: attempts WebGPU, falls back to WebGL; logs which path is active.
* - Registers `mousemove` and `resize` listeners; normalizes mouse to [0,1] in canvas space.
* - Calls `setSize()` and starts the animation `loop()`.
* - On destroy: cancels RAF, removes listeners, releases GPU/WebGL resources.
*
* Side effects:
* - Mutates `isWebGPU`, scheduling, and GPU/WebGL global state.
*/
$effect(() => {
if (typeof window === 'undefined' || !canvas) return;
const init = async () => {
// Try WebGPU first
if (await initWebGPU()) {
isWebGPU = true;
console.log('Using WebGPU renderer');
} else if (initWebGL()) {
isWebGPU = false;
console.log('Using WebGL renderer (WebGPU fallback)');
} else {
console.error('Neither WebGPU nor WebGL are supported');
return;
}
/**
* Mouse-move handler.
*
* @param {MouseEvent} ev Pointer event.
* Behavior: updates `mouseX`/`mouseY` to normalized [0,1] coords in canvas space (Y flipped).
*/
const onMove = (ev: MouseEvent) => {
if (!canvas) return;
const r = canvas.getBoundingClientRect();
mouseX = (ev.clientX - r.left) / r.width;
mouseY = 1.0 - (ev.clientY - r.top) / r.height;
};
/** Resize handler; recomputes backing store size and GL viewport/uniforms. */
const onResize = () => setSize();
if (canvas) {
canvas.addEventListener('mousemove', onMove, { passive: true });
}
window.addEventListener('resize', onResize, { passive: true });
setSize();
raf = requestAnimationFrame(loop);
return () => {
cancelAnimationFrame(raf);
window.removeEventListener('resize', onResize);
if (canvas) {
canvas.removeEventListener('mousemove', onMove);
}
// Cleanup WebGPU
if (uniformBuffer) {
uniformBuffer.destroy();
}
// Cleanup WebGL
if (gl && glProgram) {
gl.deleteProgram(glProgram);
}
if (gl && positionBuf) {
gl.deleteBuffer(positionBuf);
}
};
};
let cleanup: (() => void) | undefined;
init().then(fn => cleanup = fn);
return () => cleanup?.();
});
</script>
<canvas bind:this={canvas} class={klass}></canvas>
<style>
:global(canvas){ display:block; }
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment