Update ImageCompositor.ts

This commit is contained in:
syuilo 2025-11-03 16:25:07 +09:00
parent 4d51ed7faf
commit 30f5387b3b
1 changed files with 21 additions and 19 deletions

View File

@ -5,15 +5,14 @@
import { createTexture, initShaderProgram } from '../webgl.js'; import { createTexture, initShaderProgram } from '../webgl.js';
export type ImageCompositorFxParamDefs = Record<string, any>; export type ImageCompositorProgramParamDefs = Record<string, any>;
export function defineImageCompositorFx<ID extends string, PS extends ImageCompositorFxParamDefs, US extends string[]>(fx: ImageCompositorFx<ID, PS, US>) { export function defineImageCompositorFx<PS extends ImageCompositorProgramParamDefs, US extends string[]>(program: ImageCompositorProgram<PS, US>) {
return fx; return program;
} }
export type ImageCompositorFx<ID extends string = string, PS extends ImageCompositorFxParamDefs = ImageCompositorFxParamDefs, US extends string[] = string[]> = { export type ImageCompositorProgram<PS extends ImageCompositorProgramParamDefs = ImageCompositorProgramParamDefs, US extends string[] = string[]> = {
id: ID; id: string;
name: string;
shader: string; shader: string;
uniforms: US; uniforms: US;
params: PS, params: PS,
@ -30,10 +29,12 @@ export type ImageCompositorFx<ID extends string = string, PS extends ImageCompos
export type ImageCompositorNode = { export type ImageCompositorNode = {
id: string; id: string;
fxId: string; programId: string;
params: Record<string, any>; params: Record<string, any>;
}; };
// TODO: per node cache
export class ImageCompositor { export class ImageCompositor {
private gl: WebGL2RenderingContext; private gl: WebGL2RenderingContext;
private canvas: HTMLCanvasElement | null = null; private canvas: HTMLCanvasElement | null = null;
@ -45,6 +46,7 @@ export class ImageCompositor {
private perLayerResultFrameBuffers: Map<string, WebGLFramebuffer> = new Map(); private perLayerResultFrameBuffers: Map<string, WebGLFramebuffer> = new Map();
private nopProgram: WebGLProgram; private nopProgram: WebGLProgram;
private registeredTextures: Map<string, { texture: WebGLTexture; width: number; height: number; }> = new Map(); private registeredTextures: Map<string, { texture: WebGLTexture; width: number; height: number; }> = new Map();
private programs: ImageCompositorProgram[] = [];
constructor(options: { constructor(options: {
canvas: HTMLCanvasElement; canvas: HTMLCanvasElement;
@ -117,10 +119,10 @@ export class ImageCompositor {
private renderNode(node: ImageCompositorNode, preTexture: WebGLTexture, invert = false) { private renderNode(node: ImageCompositorNode, preTexture: WebGLTexture, invert = false) {
const gl = this.gl; const gl = this.gl;
const fx = this.fxs.find(fx => fx.id === node.fxId); const program = this.programs.find(p => p.id === node.programId);
if (fx == null) return; if (program == null) return;
const cachedShader = this.shaderCache.get(fx.id); const cachedShader = this.shaderCache.get(program.id);
const shaderProgram = cachedShader ?? initShaderProgram(this.gl, `#version 300 es const shaderProgram = cachedShader ?? initShaderProgram(this.gl, `#version 300 es
in vec2 position; in vec2 position;
uniform bool u_invert; uniform bool u_invert;
@ -130,9 +132,9 @@ export class ImageCompositor {
in_uv = (position + 1.0) / 2.0; in_uv = (position + 1.0) / 2.0;
gl_Position = u_invert ? vec4(position * vec2(1.0, -1.0), 0.0, 1.0) : vec4(position, 0.0, 1.0); gl_Position = u_invert ? vec4(position * vec2(1.0, -1.0), 0.0, 1.0) : vec4(position, 0.0, 1.0);
} }
`, fx.shader); `, program.shader);
if (cachedShader == null) { if (cachedShader == null) {
this.shaderCache.set(fx.id, shaderProgram); this.shaderCache.set(program.id, shaderProgram);
} }
gl.useProgram(shaderProgram); gl.useProgram(shaderProgram);
@ -148,11 +150,11 @@ export class ImageCompositor {
const in_texture = gl.getUniformLocation(shaderProgram, 'in_texture'); const in_texture = gl.getUniformLocation(shaderProgram, 'in_texture');
gl.uniform1i(in_texture, 0); gl.uniform1i(in_texture, 0);
fx.main({ program.main({
gl: gl, gl: gl,
program: shaderProgram, program: shaderProgram,
params: node.params, params: node.params,
u: Object.fromEntries(fx.uniforms.map(u => [u, gl.getUniformLocation(shaderProgram, 'u_' + u)!])), u: Object.fromEntries(program.uniforms.map(u => [u, gl.getUniformLocation(shaderProgram, 'u_' + u)!])),
width: this.renderWidth, width: this.renderWidth,
height: this.renderHeight, height: this.renderHeight,
textures: this.registeredTextures, textures: this.registeredTextures,
@ -194,7 +196,7 @@ export class ImageCompositor {
gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null);
} else { } else {
const cachedResultFrameBuffer = this.perLayerResultFrameBuffers.get(layer.id); const cachedResultFrameBuffer = this.perLayerResultFrameBuffers.get(layer.id);
const resultFrameBuffer = cachedResultFrameBuffer ?? gl.createFramebuffer()!; const resultFrameBuffer = cachedResultFrameBuffer ?? gl.createFramebuffer();
if (cachedResultFrameBuffer == null) { if (cachedResultFrameBuffer == null) {
this.perLayerResultFrameBuffers.set(layer.id, resultFrameBuffer); this.perLayerResultFrameBuffers.set(layer.id, resultFrameBuffer);
} }
@ -208,15 +210,15 @@ export class ImageCompositor {
} }
} }
public registerFx(fx: ImageCompositorFx) { public registerProgram(program: ImageCompositorProgram) {
this.fxs.push(fx); this.programs.push(program);
} }
public registerTexture(key: string, image: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement) { public registerTexture(key: string, image: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement) {
const gl = this.gl; const gl = this.gl;
if (this.registeredTextures.has(key)) { const existing = this.registeredTextures.get(key);
const existing = this.registeredTextures.get(key)!; if (existing != null) {
gl.deleteTexture(existing.texture); gl.deleteTexture(existing.texture);
this.registeredTextures.delete(key); this.registeredTextures.delete(key);
} }