diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-03-27 00:55:15 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-03-29 15:56:48 +0200 |
| commit | 4d127a038bf768eded68aae3cfa5c1b12241bdac (patch) | |
| tree | 16bbb801caf6770546cff4fdb37b6408ec9ead98 /apps/web/src/services/renderer/webgl/webgl-utils.ts | |
| parent | 82817cb50cb0487350573d6129130aec705f924b (diff) | |
| download | opencut-4d127a038bf768eded68aae3cfa5c1b12241bdac.tar.gz | |
chore: normalize line endings
Diffstat (limited to 'apps/web/src/services/renderer/webgl/webgl-utils.ts')
| -rw-r--r-- | apps/web/src/services/renderer/webgl/webgl-utils.ts | 596 |
1 files changed, 298 insertions, 298 deletions
diff --git a/apps/web/src/services/renderer/webgl/webgl-utils.ts b/apps/web/src/services/renderer/webgl/webgl-utils.ts index 834c652e..ec207546 100644 --- a/apps/web/src/services/renderer/webgl/webgl-utils.ts +++ b/apps/web/src/services/renderer/webgl/webgl-utils.ts @@ -1,298 +1,298 @@ -import VERTEX_SHADER_SOURCE from "@/lib/effects/effect.vert.glsl"; - -export interface EffectPassData { - fragmentShader: string; - uniforms: Record<string, number | number[]>; -} - -export const QUAD_POSITIONS = new Float32Array([ - -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -]); - -export function compileProgram({ - context, - fragmentShaderSource, - programCache, -}: { - context: WebGLRenderingContext; - fragmentShaderSource: string; - programCache: Map<string, WebGLProgram>; -}): WebGLProgram { - const cached = programCache.get(fragmentShaderSource); - if (cached) { - return cached; - } - const vertexShader = compileShader({ - context, - source: VERTEX_SHADER_SOURCE, - type: context.VERTEX_SHADER, - }); - const fragmentShader = compileShader({ - context, - source: fragmentShaderSource, - type: context.FRAGMENT_SHADER, - }); - const program = context.createProgram(); - if (!program) { - throw new Error("Failed to create WebGL program"); - } - context.attachShader(program, vertexShader); - context.attachShader(program, fragmentShader); - context.linkProgram(program); - if (!context.getProgramParameter(program, context.LINK_STATUS)) { - const info = context.getProgramInfoLog(program); - context.deleteProgram(program); - throw new Error(`WebGL program link failed: ${info}`); - } - context.deleteShader(vertexShader); - context.deleteShader(fragmentShader); - programCache.set(fragmentShaderSource, program); - return program; -} - -export function compileShader({ - context, - source, - type, -}: { - context: WebGLRenderingContext; - source: string; - type: number; -}): WebGLShader { - const shader = context.createShader(type); - if (!shader) { - throw new Error("Failed to create WebGL shader"); - } - context.shaderSource(shader, source); - context.compileShader(shader); - if (!context.getShaderParameter(shader, context.COMPILE_STATUS)) { - const info = context.getShaderInfoLog(shader); - context.deleteShader(shader); - throw new Error(`WebGL shader compile failed: ${info}`); - } - return shader; -} - -export function createTexture({ - context, - source, -}: { - context: WebGLRenderingContext; - source: CanvasImageSource; -}): WebGLTexture { - const texture = context.createTexture(); - if (!texture) { - throw new Error("Failed to create WebGL texture"); - } - context.activeTexture(context.TEXTURE0); - context.bindTexture(context.TEXTURE_2D, texture); - context.pixelStorei(context.UNPACK_FLIP_Y_WEBGL, 1); - context.texParameteri( - context.TEXTURE_2D, - context.TEXTURE_WRAP_S, - context.CLAMP_TO_EDGE, - ); - context.texParameteri( - context.TEXTURE_2D, - context.TEXTURE_WRAP_T, - context.CLAMP_TO_EDGE, - ); - context.texParameteri( - context.TEXTURE_2D, - context.TEXTURE_MIN_FILTER, - context.LINEAR, - ); - context.texParameteri( - context.TEXTURE_2D, - context.TEXTURE_MAG_FILTER, - context.LINEAR, - ); - context.texImage2D( - context.TEXTURE_2D, - 0, - context.RGBA, - context.RGBA, - context.UNSIGNED_BYTE, - source as TexImageSource, - ); - return texture; -} - -export function setUniforms({ - context, - program, - uniforms, -}: { - context: WebGLRenderingContext; - program: WebGLProgram; - uniforms: Record<string, number | number[]>; -}): void { - for (const [name, value] of Object.entries(uniforms)) { - const location = context.getUniformLocation(program, name); - if (location === null) continue; - - if (typeof value === "number") { - context.uniform1f(location, value); - } else if (Array.isArray(value)) { - if (value.length === 2) { - context.uniform2fv(location, new Float32Array(value)); - } else if (value.length === 3) { - context.uniform3fv(location, new Float32Array(value)); - } else if (value.length === 4) { - context.uniform4fv(location, new Float32Array(value)); - } - } - } -} - -export function drawFullscreenQuad({ - context, - program, - width, - height, -}: { - context: WebGLRenderingContext; - program: WebGLProgram; - width: number; - height: number; -}): void { - const positionLocation = context.getAttribLocation(program, "a_position"); - const buffer = context.createBuffer(); - context.bindBuffer(context.ARRAY_BUFFER, buffer); - context.bufferData(context.ARRAY_BUFFER, QUAD_POSITIONS, context.STATIC_DRAW); - context.enableVertexAttribArray(positionLocation); - context.vertexAttribPointer(positionLocation, 2, context.FLOAT, false, 0, 0); - - context.viewport(0, 0, width, height); - context.clearColor(0, 0, 0, 0); - context.clear(context.COLOR_BUFFER_BIT); - context.drawArrays(context.TRIANGLES, 0, 6); -} - -export function createFramebufferTexture({ - context, - width, - height, -}: { - context: WebGLRenderingContext; - width: number; - height: number; -}): { texture: WebGLTexture; framebuffer: WebGLFramebuffer } { - const texture = context.createTexture(); - if (!texture) throw new Error("Failed to create framebuffer texture"); - context.bindTexture(context.TEXTURE_2D, texture); - context.texImage2D( - context.TEXTURE_2D, - 0, - context.RGBA, - width, - height, - 0, - context.RGBA, - context.UNSIGNED_BYTE, - null, - ); - context.texParameteri( - context.TEXTURE_2D, - context.TEXTURE_WRAP_S, - context.CLAMP_TO_EDGE, - ); - context.texParameteri( - context.TEXTURE_2D, - context.TEXTURE_WRAP_T, - context.CLAMP_TO_EDGE, - ); - context.texParameteri( - context.TEXTURE_2D, - context.TEXTURE_MIN_FILTER, - context.LINEAR, - ); - context.texParameteri( - context.TEXTURE_2D, - context.TEXTURE_MAG_FILTER, - context.LINEAR, - ); - context.bindTexture(context.TEXTURE_2D, null); - - const framebuffer = context.createFramebuffer(); - if (!framebuffer) throw new Error("Failed to create framebuffer"); - context.bindFramebuffer(context.FRAMEBUFFER, framebuffer); - context.framebufferTexture2D( - context.FRAMEBUFFER, - context.COLOR_ATTACHMENT0, - context.TEXTURE_2D, - texture, - 0, - ); - context.bindFramebuffer(context.FRAMEBUFFER, null); - - return { texture, framebuffer }; -} - -export function applyMultiPassEffect({ - context, - source, - width, - height, - passes, - programCache, -}: { - context: WebGLRenderingContext; - source: CanvasImageSource; - width: number; - height: number; - passes: EffectPassData[]; - programCache: Map<string, WebGLProgram>; -}): void { - const sourceTexture = createTexture({ context, source }); - let currentTexture: WebGLTexture = sourceTexture; - - const intermediates: Array<{ - texture: WebGLTexture; - framebuffer: WebGLFramebuffer; - }> = []; - for (let i = 0; i < passes.length - 1; i++) { - intermediates.push(createFramebufferTexture({ context, width, height })); - } - - for (let i = 0; i < passes.length; i++) { - const pass = passes[i]; - const program = compileProgram({ - context, - fragmentShaderSource: pass.fragmentShader, - programCache, - }); - const isLastPass = i === passes.length - 1; - const targetFramebuffer = isLastPass ? null : intermediates[i].framebuffer; - - context.bindFramebuffer(context.FRAMEBUFFER, targetFramebuffer); - // biome-ignore lint/correctness/useHookAtTopLevel: WebGL API method, not a React hook - context.useProgram(program); - context.activeTexture(context.TEXTURE0); - context.bindTexture(context.TEXTURE_2D, currentTexture); - - const uTextureLocation = context.getUniformLocation(program, "u_texture"); - if (uTextureLocation) { - context.uniform1i(uTextureLocation, 0); - } - - setUniforms({ - context, - program, - uniforms: { ...pass.uniforms, u_resolution: [width, height] }, - }); - drawFullscreenQuad({ context, program, width, height }); - - if (!isLastPass) { - currentTexture = intermediates[i].texture; - } - } - - context.deleteTexture(sourceTexture); - for (const intermediate of intermediates) { - context.deleteTexture(intermediate.texture); - context.deleteFramebuffer(intermediate.framebuffer); - } - context.bindTexture(context.TEXTURE_2D, null); - context.bindFramebuffer(context.FRAMEBUFFER, null); -} +import VERTEX_SHADER_SOURCE from "@/lib/effects/effect.vert.glsl";
+
+export interface EffectPassData {
+ fragmentShader: string;
+ uniforms: Record<string, number | number[]>;
+}
+
+export const QUAD_POSITIONS = new Float32Array([
+ -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1,
+]);
+
+export function compileProgram({
+ context,
+ fragmentShaderSource,
+ programCache,
+}: {
+ context: WebGLRenderingContext;
+ fragmentShaderSource: string;
+ programCache: Map<string, WebGLProgram>;
+}): WebGLProgram {
+ const cached = programCache.get(fragmentShaderSource);
+ if (cached) {
+ return cached;
+ }
+ const vertexShader = compileShader({
+ context,
+ source: VERTEX_SHADER_SOURCE,
+ type: context.VERTEX_SHADER,
+ });
+ const fragmentShader = compileShader({
+ context,
+ source: fragmentShaderSource,
+ type: context.FRAGMENT_SHADER,
+ });
+ const program = context.createProgram();
+ if (!program) {
+ throw new Error("Failed to create WebGL program");
+ }
+ context.attachShader(program, vertexShader);
+ context.attachShader(program, fragmentShader);
+ context.linkProgram(program);
+ if (!context.getProgramParameter(program, context.LINK_STATUS)) {
+ const info = context.getProgramInfoLog(program);
+ context.deleteProgram(program);
+ throw new Error(`WebGL program link failed: ${info}`);
+ }
+ context.deleteShader(vertexShader);
+ context.deleteShader(fragmentShader);
+ programCache.set(fragmentShaderSource, program);
+ return program;
+}
+
+export function compileShader({
+ context,
+ source,
+ type,
+}: {
+ context: WebGLRenderingContext;
+ source: string;
+ type: number;
+}): WebGLShader {
+ const shader = context.createShader(type);
+ if (!shader) {
+ throw new Error("Failed to create WebGL shader");
+ }
+ context.shaderSource(shader, source);
+ context.compileShader(shader);
+ if (!context.getShaderParameter(shader, context.COMPILE_STATUS)) {
+ const info = context.getShaderInfoLog(shader);
+ context.deleteShader(shader);
+ throw new Error(`WebGL shader compile failed: ${info}`);
+ }
+ return shader;
+}
+
+export function createTexture({
+ context,
+ source,
+}: {
+ context: WebGLRenderingContext;
+ source: CanvasImageSource;
+}): WebGLTexture {
+ const texture = context.createTexture();
+ if (!texture) {
+ throw new Error("Failed to create WebGL texture");
+ }
+ context.activeTexture(context.TEXTURE0);
+ context.bindTexture(context.TEXTURE_2D, texture);
+ context.pixelStorei(context.UNPACK_FLIP_Y_WEBGL, 1);
+ context.texParameteri(
+ context.TEXTURE_2D,
+ context.TEXTURE_WRAP_S,
+ context.CLAMP_TO_EDGE,
+ );
+ context.texParameteri(
+ context.TEXTURE_2D,
+ context.TEXTURE_WRAP_T,
+ context.CLAMP_TO_EDGE,
+ );
+ context.texParameteri(
+ context.TEXTURE_2D,
+ context.TEXTURE_MIN_FILTER,
+ context.LINEAR,
+ );
+ context.texParameteri(
+ context.TEXTURE_2D,
+ context.TEXTURE_MAG_FILTER,
+ context.LINEAR,
+ );
+ context.texImage2D(
+ context.TEXTURE_2D,
+ 0,
+ context.RGBA,
+ context.RGBA,
+ context.UNSIGNED_BYTE,
+ source as TexImageSource,
+ );
+ return texture;
+}
+
+export function setUniforms({
+ context,
+ program,
+ uniforms,
+}: {
+ context: WebGLRenderingContext;
+ program: WebGLProgram;
+ uniforms: Record<string, number | number[]>;
+}): void {
+ for (const [name, value] of Object.entries(uniforms)) {
+ const location = context.getUniformLocation(program, name);
+ if (location === null) continue;
+
+ if (typeof value === "number") {
+ context.uniform1f(location, value);
+ } else if (Array.isArray(value)) {
+ if (value.length === 2) {
+ context.uniform2fv(location, new Float32Array(value));
+ } else if (value.length === 3) {
+ context.uniform3fv(location, new Float32Array(value));
+ } else if (value.length === 4) {
+ context.uniform4fv(location, new Float32Array(value));
+ }
+ }
+ }
+}
+
+export function drawFullscreenQuad({
+ context,
+ program,
+ width,
+ height,
+}: {
+ context: WebGLRenderingContext;
+ program: WebGLProgram;
+ width: number;
+ height: number;
+}): void {
+ const positionLocation = context.getAttribLocation(program, "a_position");
+ const buffer = context.createBuffer();
+ context.bindBuffer(context.ARRAY_BUFFER, buffer);
+ context.bufferData(context.ARRAY_BUFFER, QUAD_POSITIONS, context.STATIC_DRAW);
+ context.enableVertexAttribArray(positionLocation);
+ context.vertexAttribPointer(positionLocation, 2, context.FLOAT, false, 0, 0);
+
+ context.viewport(0, 0, width, height);
+ context.clearColor(0, 0, 0, 0);
+ context.clear(context.COLOR_BUFFER_BIT);
+ context.drawArrays(context.TRIANGLES, 0, 6);
+}
+
+export function createFramebufferTexture({
+ context,
+ width,
+ height,
+}: {
+ context: WebGLRenderingContext;
+ width: number;
+ height: number;
+}): { texture: WebGLTexture; framebuffer: WebGLFramebuffer } {
+ const texture = context.createTexture();
+ if (!texture) throw new Error("Failed to create framebuffer texture");
+ context.bindTexture(context.TEXTURE_2D, texture);
+ context.texImage2D(
+ context.TEXTURE_2D,
+ 0,
+ context.RGBA,
+ width,
+ height,
+ 0,
+ context.RGBA,
+ context.UNSIGNED_BYTE,
+ null,
+ );
+ context.texParameteri(
+ context.TEXTURE_2D,
+ context.TEXTURE_WRAP_S,
+ context.CLAMP_TO_EDGE,
+ );
+ context.texParameteri(
+ context.TEXTURE_2D,
+ context.TEXTURE_WRAP_T,
+ context.CLAMP_TO_EDGE,
+ );
+ context.texParameteri(
+ context.TEXTURE_2D,
+ context.TEXTURE_MIN_FILTER,
+ context.LINEAR,
+ );
+ context.texParameteri(
+ context.TEXTURE_2D,
+ context.TEXTURE_MAG_FILTER,
+ context.LINEAR,
+ );
+ context.bindTexture(context.TEXTURE_2D, null);
+
+ const framebuffer = context.createFramebuffer();
+ if (!framebuffer) throw new Error("Failed to create framebuffer");
+ context.bindFramebuffer(context.FRAMEBUFFER, framebuffer);
+ context.framebufferTexture2D(
+ context.FRAMEBUFFER,
+ context.COLOR_ATTACHMENT0,
+ context.TEXTURE_2D,
+ texture,
+ 0,
+ );
+ context.bindFramebuffer(context.FRAMEBUFFER, null);
+
+ return { texture, framebuffer };
+}
+
+export function applyMultiPassEffect({
+ context,
+ source,
+ width,
+ height,
+ passes,
+ programCache,
+}: {
+ context: WebGLRenderingContext;
+ source: CanvasImageSource;
+ width: number;
+ height: number;
+ passes: EffectPassData[];
+ programCache: Map<string, WebGLProgram>;
+}): void {
+ const sourceTexture = createTexture({ context, source });
+ let currentTexture: WebGLTexture = sourceTexture;
+
+ const intermediates: Array<{
+ texture: WebGLTexture;
+ framebuffer: WebGLFramebuffer;
+ }> = [];
+ for (let i = 0; i < passes.length - 1; i++) {
+ intermediates.push(createFramebufferTexture({ context, width, height }));
+ }
+
+ for (let i = 0; i < passes.length; i++) {
+ const pass = passes[i];
+ const program = compileProgram({
+ context,
+ fragmentShaderSource: pass.fragmentShader,
+ programCache,
+ });
+ const isLastPass = i === passes.length - 1;
+ const targetFramebuffer = isLastPass ? null : intermediates[i].framebuffer;
+
+ context.bindFramebuffer(context.FRAMEBUFFER, targetFramebuffer);
+ // biome-ignore lint/correctness/useHookAtTopLevel: WebGL API method, not a React hook
+ context.useProgram(program);
+ context.activeTexture(context.TEXTURE0);
+ context.bindTexture(context.TEXTURE_2D, currentTexture);
+
+ const uTextureLocation = context.getUniformLocation(program, "u_texture");
+ if (uTextureLocation) {
+ context.uniform1i(uTextureLocation, 0);
+ }
+
+ setUniforms({
+ context,
+ program,
+ uniforms: { ...pass.uniforms, u_resolution: [width, height] },
+ });
+ drawFullscreenQuad({ context, program, width, height });
+
+ if (!isLastPass) {
+ currentTexture = intermediates[i].texture;
+ }
+ }
+
+ context.deleteTexture(sourceTexture);
+ for (const intermediate of intermediates) {
+ context.deleteTexture(intermediate.texture);
+ context.deleteFramebuffer(intermediate.framebuffer);
+ }
+ context.bindTexture(context.TEXTURE_2D, null);
+ context.bindFramebuffer(context.FRAMEBUFFER, null);
+}
|
