From e714125fb69fb579e0f39a0ba76d58dfb62a12bf Mon Sep 17 00:00:00 2001 From: Perminder Date: Wed, 11 Dec 2024 05:06:45 +0530 Subject: [PATCH] suggestions-fixes --- src/core/p5.Renderer2D.js | 4 ++ src/image/filterRenderer2D.js | 80 ++++++++++++++++++++++------------- src/image/pixels.js | 30 ++++++------- src/webgl/material.js | 6 +-- src/webgl/p5.Shader.js | 6 ++- 5 files changed, 71 insertions(+), 55 deletions(-) diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index 75cf1b953d..b6707d21a5 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -5,6 +5,7 @@ import { Graphics } from './p5.Graphics'; import { Image } from '../image/p5.Image'; import { Element } from '../dom/p5.Element'; import { MediaElement } from '../dom/p5.MediaElement'; +import FilterRenderer2D from '../image/filterRenderer2D'; const styleEmpty = 'rgba(0,0,0,0)'; // const alphaThreshold = 0.00125; // minimum visible @@ -66,6 +67,9 @@ class Renderer2D extends Renderer { } this.scale(this._pixelDensity, this._pixelDensity); + if(!this.filterRenderer){ + this.filterRenderer = new FilterRenderer2D(this); + } // Set and return p5.Element this.wrappedElt = new Element(this.elt, this._pInst); } diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js index 0f896de6a4..c5a732e687 100644 --- a/src/image/filterRenderer2D.js +++ b/src/image/filterRenderer2D.js @@ -17,17 +17,9 @@ class FilterRenderer2D { /** * Creates a new FilterRenderer2D instance. * @param {p5} pInst - The p5.js instance. - * @param {string} operation - The filter operation type (e.g., constants.BLUR). - * @param {string} filterParameter - The strength of applying filter. - * @param {p5.Shader} customShader - Optional custom shader; if provided, ignore operation-based loading. */ - constructor(pInst, operation, filterParameter, customShader) { + constructor(pInst) { this.pInst = pInst; - this.filterParameter = filterParameter; - this.operation = operation; - this.customShader = customShader; - - // Create a canvas for applying WebGL-based filters this.canvas = document.createElement('canvas'); this.canvas.width = pInst.width; @@ -39,7 +31,6 @@ class FilterRenderer2D { console.error("WebGL not supported, cannot apply filter."); return; } - // Minimal renderer object required by p5.Shader and p5.Texture this._renderer = { GL: this.gl, @@ -62,8 +53,8 @@ class FilterRenderer2D { }, }; - // Fragment shaders mapped to filter operations - this.filterShaders = { + // Store the fragment shader sources + this.filterShaderSources = { [constants.BLUR]: filterBlurFrag, [constants.INVERT]: filterInvertFrag, [constants.THRESHOLD]: filterThresholdFrag, @@ -74,8 +65,14 @@ class FilterRenderer2D { [constants.OPAQUE]: filterOpaqueFrag, }; + // Store initialized shaders for each operation + this.filterShaders = {}; + + // These will be set by setOperation + this.operation = null; + this.filterParameter = 1; + this.customShader = null; this._shader = null; - this._initializeShader(); // Create buffers once this.vertexBuffer = this.gl.createBuffer(); @@ -90,32 +87,54 @@ class FilterRenderer2D { // Upload texcoord data once this._bindBufferData(this.texcoordBuffer, this.gl.ARRAY_BUFFER, this.texcoords); - } - updateFilterParameter(newFilterParameter) { - // Operation is the same, just update parameter if changed - this.filterParameter = newFilterParameter; + /** + * Set the current filter operation and parameter. If a customShader is provided, + * that overrides the operation-based shader. + * @param {string} operation - The filter operation type (e.g., constants.BLUR). + * @param {number} filterParameter - The strength of the filter. + * @param {p5.Shader} customShader - Optional custom shader. + */ + setOperation(operation, filterParameter, customShader = null) { + this.operation = operation; + this.filterParameter = filterParameter; + this.customShader = customShader; + this._initializeShader(); } /** - * Initializes the shader program if it hasn't been already. + * Initializes or retrieves the shader program for the current operation. + * If a customShader is provided, that is used. + * Otherwise, returns a cached shader if available, or creates a new one, caches it, and sets it as current. */ _initializeShader() { - if (this._shader) return; // Already initialized - if (this.customShader) { this._shader = this.customShader; return; } - const fragShaderSrc = this.filterShaders[this.operation]; + if (!this.operation) { + console.error("No operation set for FilterRenderer2D, cannot initialize shader."); + return; + } + + // If we already have a compiled shader for this operation, reuse it + if (this.filterShaders[this.operation]) { + this._shader = this.filterShaders[this.operation]; + return; + } + + const fragShaderSrc = this.filterShaderSources[this.operation]; if (!fragShaderSrc) { console.error("No shader available for this operation:", this.operation); return; } - this._shader = new Shader(this._renderer, filterShaderVert, fragShaderSrc); + // Create and store the new shader + const newShader = new Shader(this._renderer, filterShaderVert, fragShaderSrc); + this.filterShaders[this.operation] = newShader; + this._shader = newShader; } /** @@ -151,36 +170,37 @@ class FilterRenderer2D { this._shader.setUniform('canvasSize', [this.pInst.width, this.pInst.height]); this._shader.setUniform('radius', Math.max(1, this.filterParameter)); - const identityMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + const identityMatrix = [1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]; this._shader.setUniform('uModelViewMatrix', identityMatrix); this._shader.setUniform('uProjectionMatrix', identityMatrix); // Bind and enable vertex attributes gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); this._shader.enableAttrib(this._shader.attributes.aPosition, 2); - + gl.bindBuffer(gl.ARRAY_BUFFER, this.texcoordBuffer); this._shader.enableAttrib(this._shader.attributes.aTexCoord, 2); - + // Draw the quad gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - // Unbind the shader this._shader.unbindShader(); } /** - * Applies the filter operation. If the filter requires multiple passes (e.g. blur), - * it handles those internally. + * Applies the current filter operation. If the filter requires multiple passes (e.g. blur), + * it handles those internally. Make sure setOperation() has been called before applyFilter(). */ applyFilter() { if (!this._shader) { console.error("Cannot apply filter: shader not initialized."); return; } - // For blur, we typically do two passes: one horizontal, one vertical. - if (this.operation === constants.BLUR && !this.customShader) { + if (this.operation === constants.BLUR && !this.customShader) { // Horizontal pass this._shader.setUniform('direction', [1, 0]); this._renderPass(); diff --git a/src/image/pixels.js b/src/image/pixels.js index f245e80362..9cd2f9e4de 100644 --- a/src/image/pixels.js +++ b/src/image/pixels.js @@ -750,29 +750,23 @@ function pixels(p5, fn){ if (this._renderer.isP3D) { this._renderer.filter(operation, value); } - + // when this is P2D renderer, create/use hidden webgl renderer else { + if (!this.filterRenderer) { + this.filterRenderer = new FilterRenderer2D(this); + this._renderer.filterRenderer = this.filterRenderer; + } if (shader) { - const customFilterRenderer = new FilterRenderer2D(this, operation, value, shader); - customFilterRenderer.applyFilter(); + this.filterRenderer.setOperation(operation, value, shader); } else { - if (!this._filterRenderers) { - this._filterRenderers = {}; - } - - // Check if we have a cached renderer for this operation - if (!this._filterRenderers[operation]) { - // If no cached renderer for this filter, create and cache it - this._filterRenderers[operation] = new FilterRenderer2D(this, operation, value); - } else { - // If we already have a renderer for this operation and value is different, update it. - this._filterRenderers[operation].updateFilterParameter(value); - } - // Use the currently requested operation's renderer - this.filterRenderer = this._filterRenderers[operation]; - this.filterRenderer.applyFilter(); + // No custom shader, just a built-in filter + this.filterRenderer.setOperation(operation, value); } + + // Apply the current filter + this.filterRenderer.applyFilter(); + } }; diff --git a/src/webgl/material.js b/src/webgl/material.js index 288d83f822..187dd08949 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -647,11 +647,7 @@ function material(p5, fn){ `; let vertSrc = fragSrc.includes('#version 300 es') ? defaultVertV2 : defaultVertV1; const shader = new Shader(this._renderer, vertSrc, fragSrc); - if (this._renderer.GL) { - shader.ensureCompiledOnContext(this._renderer); - } else { - shader.ensureCompiledOnContext(this._renderer.getFilterGraphicsLayer()._renderer); - } + shader.ensureCompiledOnContext(this); return shader; }; diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js index 2e3330ff0a..75859f056b 100644 --- a/src/webgl/p5.Shader.js +++ b/src/webgl/p5.Shader.js @@ -625,11 +625,13 @@ class Shader { 'The shader being run is attached to a different context. Do you need to copy it to this context first with .copyToContext()?' ); } else if (this._glProgram === 0) { - this._renderer = context; + this._renderer = context._renderer.filterRenderer + ? context._renderer.filterRenderer._renderer + : context._renderer; this.init(); } } - + /** * Queries the active attributes for this shader and loads * their names and locations into the attributes array.