From d71efff57cc56b3f27c9f08d015c61b71c9f5775 Mon Sep 17 00:00:00 2001 From: Perminder Date: Fri, 6 Dec 2024 03:39:04 +0530 Subject: [PATCH 01/17] 2dFilterRenderer still a work in progress PR, needs to do some todo tasks --- src/image/filter-2d-renderer.js | 149 ++++++++++++++++++++++++++++++++ src/image/pixels.js | 68 +++++++++++---- 2 files changed, 198 insertions(+), 19 deletions(-) create mode 100644 src/image/filter-2d-renderer.js diff --git a/src/image/filter-2d-renderer.js b/src/image/filter-2d-renderer.js new file mode 100644 index 0000000000..8a3ef0f2b2 --- /dev/null +++ b/src/image/filter-2d-renderer.js @@ -0,0 +1,149 @@ +import { Shader } from "../webgl/p5.Shader"; +import { Texture } from "../webgl/p5.Texture"; +import { Image } from "./p5.Image"; +import * as constants from '../core/constants'; +import filterGrayFrag from '../webgl/shaders/filters/gray.frag'; +import filterErodeFrag from '../webgl/shaders/filters/erode.frag'; +import filterDilateFrag from '../webgl/shaders/filters/dilate.frag'; +import filterBlurFrag from '../webgl/shaders/filters/blur.frag'; +import filterPosterizeFrag from '../webgl/shaders/filters/posterize.frag'; +import filterOpaqueFrag from '../webgl/shaders/filters/opaque.frag'; +import filterInvertFrag from '../webgl/shaders/filters/invert.frag'; +import filterThresholdFrag from '../webgl/shaders/filters/threshold.frag'; + +class FilterRenderer2D { + constructor(pInst, operation) { + this.pInst = pInst; + this.operation = operation; + // creating webgl context + this.canvas = document.createElement('canvas'); + this.canvas.width = pInst.width; + this.canvas.height = pInst.height; + this.gl = this.canvas.getContext('webgl'); + + // if not able to create, return + if (!this.gl) { + console.error("WebGL not supported"); + return; + } + + // Set up the minimal renderer required by p5.Shader and p5.Texture + this._renderer = { + GL: this.gl, + registerEnabled : new Set(), + _curShader: null, + _emptyTexture: null, + webglVersion: 'WEBGL', + states: { + textureWrapX: this.gl.CLAMP_TO_EDGE, + textureWrapY: this.gl.CLAMP_TO_EDGE, + }, + _arraysEqual: function(a, b) { + return JSON.stringify(a) === JSON.stringify(b); + }, + _getEmptyTexture: () => { + if (!this._emptyTexture) { + const im = new Image(1, 1); + im.set(0, 0, 255); + this._emptyTexture = new Texture(this._renderer, im); + } + return this._emptyTexture; + }, + }; + } + + filterShaders = { + [constants.BLUR] : filterBlurFrag, + [constants.INVERT]: filterInvertFrag, + [constants.THRESHOLD]: filterThresholdFrag, + [constants.ERODE]: filterErodeFrag, + [constants.GRAY] : filterGrayFrag, + [constants.DILATE]: filterDilateFrag, + [constants.POSTERIZE]: filterPosterizeFrag, + [constants.OPAQUE]: filterOpaqueFrag, + }; + + + vertSrc() { + return ` + attribute vec2 aPosition; + attribute vec2 aTexCoord; + varying vec2 vTexCoord; + void main() { + gl_Position = vec4(aPosition, 0.0, 1.0); + vTexCoord = aTexCoord; + } + `; + } + + // binding buffer + _bindBuffer(buffer, target, values, type, usage) { + const gl = this.gl; + if (!target) target = gl.ARRAY_BUFFER; + gl.bindBuffer(target, buffer); + if (values !== undefined) { + let data = values; + if (!(data instanceof (type || Float32Array))) { + data = new (type || Float32Array)(data); + } + gl.bufferData(target, data, usage || gl.STATIC_DRAW); + } + } + + render() { + // console.log(this.pInst._renderer.pixelDensity()); + const gl = this.gl; + // console.log(this.pInst.width); + let texelSize = [ + 1 / (this.pInst.width * this.pInst._renderer.pixelDensity()), + 1 / (this.pInst.height * this.pInst._renderer.pixelDensity()) + ]; + + // console.log(texelSize); + // Create and initialize the shader + if (!this._shader) { + // console.log(this.filterShaders['invert']); + this._shader = new Shader(this._renderer, this.vertSrc(), this.filterShaders[this.operation]); + } + this._shader.bindShader(); + + // Create a texture from the main p5.js canvas + // console.log(this.pInst._renderer.wrappedElt) + const canvasTexture = new Texture(this._renderer, this.pInst._renderer.wrappedElt); + // canvasTexture.update(); // Ensure the texture is updated with the latest canvas content + const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]); + const texcoords = new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]); + + const vertexBuffer = gl.createBuffer(); + this._bindBuffer(vertexBuffer, gl.ARRAY_BUFFER, vertices); + + this._shader.enableAttrib(this._shader.attributes.aPosition, 2); + // Create and bind the vertex buffer for positions + const texcoordBuffer = gl.createBuffer(); + + this._bindBuffer(texcoordBuffer, gl.ARRAY_BUFFER, texcoords); + // Create and bind the texture coordinate buffer + this._shader.enableAttrib(this._shader.attributes.aTexCoord, 2); + + // Set the texture uniform + this._shader.setUniform('tex0', canvasTexture); + this._shader.setUniform('texelSize', texelSize); + this._shader.setUniform('canvasSize', [this.pInst.width, this.pInst.height]); + this._shader.setUniform('direction', [1, 0]); + this._shader.setUniform('radius', 5); + + // Clear the canvas and draw the quad + gl.viewport(0, 0, this.canvas.width, this.canvas.height); + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + // Draw the quad (two triangles) + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + + // Unbind the shader and texture + this._shader.unbindShader(); + canvasTexture.unbindTexture(); + } +} + +export default FilterRenderer2D; diff --git a/src/image/pixels.js b/src/image/pixels.js index 78df6bd877..c06c5368df 100644 --- a/src/image/pixels.js +++ b/src/image/pixels.js @@ -6,6 +6,7 @@ */ import Filters from './filters'; +import FilterRenderer2D from './filter-2d-renderer'; function pixels(p5, fn){ /** @@ -722,7 +723,7 @@ function pixels(p5, fn){ */ fn.filter = function(...args) { p5._validateParameters('filter', args); - + let { shader, operation, value, useWebGL } = parseFilterArgs(...args); // when passed a shader, use it directly @@ -749,37 +750,60 @@ function pixels(p5, fn){ if (this._renderer.isP3D) { this._renderer.filter(operation, value); } - + // when this is P2D renderer, create/use hidden webgl renderer else { + + // TODO-1: 2 PASS BLUR (currently only works in horizontal blurring) + // TODO-2: ADDING CONDITIONS FOR shifting rendererGL filter for webgl and filterREnderer2D for 2d build filters + // TODO-3: PASSING p5.Shader, p5.Texture and fragment shader to expose an addon function that sets p5.Renderer2D.prototype.filter + // TODO-4: ADDING projection, modelView and necessary matrix + // TODO-5: code cleanups, adjusting the peices where they belong to. + + + + + // console.log(this._renderer.wrappedElt); + // this._renderer.clear(); + if (!this.filterRenderer) { + // console.log(this._renderer._pInst.canvas); + this.filterRenderer = new FilterRenderer2D(this, operation); + } + const filterGraphicsLayer = this.getFilterGraphicsLayer(); // copy p2d canvas contents to secondary webgl renderer // dest filterGraphicsLayer.copy( - // src this._renderer, - // src coods 0, 0, this.width, this.height, - // dest coords - -this.width/2, -this.height/2, this.width, this.height + 0, 0, this.width, this.height ); //clearing the main canvas - this._renderer.clear(); + this.filterRenderer.render(); + this.drawingContext.drawImage(this.filterRenderer.canvas, 0, 0, this.width, this.height); + // this._renderer.clear(); + // this.filterRenderer.renderAgain(); + // this.drawingContext.drawImage(this.filterRenderer.canvas, 0, 0, this.width, this.height); + + // this._pInst.image(filterRenderer.canvas, 0, 0, width, height); - this._renderer.resetMatrix(); - // filter it with shaders - filterGraphicsLayer.filter(...args); + // this._renderer.resetMatrix(); + // filter it with shaders + // filterGraphicsLayer.filter(...args); + // console.log(this._renderer.image); + // console.log(this.filterRenderer.canvas); + // this._renderer.image(this.filterRenderer, 0, 0, this.width, this.height); // copy secondary webgl renderer back to original p2d canvas - this.copy( - // src - filterGraphicsLayer._renderer, - // src coods - 0, 0, this.width, this.height, - // dest coords - 0, 0, this.width, this.height - ); - filterGraphicsLayer.clear(); // prevent feedback effects on p2d canvas + // this.copy( + // // src + // filterGraphicsLayer, + // // src coods + // 0, 0, this.width, this.height, + // // dest coords + // 0, 0, this.width, this.height + // ); + // filterGraphicsLayer.clear(); // prevent feedback effects on p2d canvas } }; @@ -796,6 +820,12 @@ function pixels(p5, fn){ useWebGL: true }; + // console.log("hi"); + // if(args[0] instanceof FilterRenderer2D){ + // console.log("hii"); + // } + // console.log(args); + // console.log(this); if (args[0] instanceof p5.Shader) { result.shader = args[0]; return result; From 7b5b88997123ce6bfb97d1ce40366c1e555cb852 Mon Sep 17 00:00:00 2001 From: Perminder Date: Sat, 7 Dec 2024 19:04:48 +0530 Subject: [PATCH 02/17] code cleanups and minor fixes --- src/image/filter-2d-renderer.js | 149 ------------------------- src/image/filterRenderer2D.js | 189 ++++++++++++++++++++++++++++++++ src/image/index.js | 4 + src/image/pixels.js | 71 ++---------- 4 files changed, 203 insertions(+), 210 deletions(-) delete mode 100644 src/image/filter-2d-renderer.js create mode 100644 src/image/filterRenderer2D.js diff --git a/src/image/filter-2d-renderer.js b/src/image/filter-2d-renderer.js deleted file mode 100644 index 8a3ef0f2b2..0000000000 --- a/src/image/filter-2d-renderer.js +++ /dev/null @@ -1,149 +0,0 @@ -import { Shader } from "../webgl/p5.Shader"; -import { Texture } from "../webgl/p5.Texture"; -import { Image } from "./p5.Image"; -import * as constants from '../core/constants'; -import filterGrayFrag from '../webgl/shaders/filters/gray.frag'; -import filterErodeFrag from '../webgl/shaders/filters/erode.frag'; -import filterDilateFrag from '../webgl/shaders/filters/dilate.frag'; -import filterBlurFrag from '../webgl/shaders/filters/blur.frag'; -import filterPosterizeFrag from '../webgl/shaders/filters/posterize.frag'; -import filterOpaqueFrag from '../webgl/shaders/filters/opaque.frag'; -import filterInvertFrag from '../webgl/shaders/filters/invert.frag'; -import filterThresholdFrag from '../webgl/shaders/filters/threshold.frag'; - -class FilterRenderer2D { - constructor(pInst, operation) { - this.pInst = pInst; - this.operation = operation; - // creating webgl context - this.canvas = document.createElement('canvas'); - this.canvas.width = pInst.width; - this.canvas.height = pInst.height; - this.gl = this.canvas.getContext('webgl'); - - // if not able to create, return - if (!this.gl) { - console.error("WebGL not supported"); - return; - } - - // Set up the minimal renderer required by p5.Shader and p5.Texture - this._renderer = { - GL: this.gl, - registerEnabled : new Set(), - _curShader: null, - _emptyTexture: null, - webglVersion: 'WEBGL', - states: { - textureWrapX: this.gl.CLAMP_TO_EDGE, - textureWrapY: this.gl.CLAMP_TO_EDGE, - }, - _arraysEqual: function(a, b) { - return JSON.stringify(a) === JSON.stringify(b); - }, - _getEmptyTexture: () => { - if (!this._emptyTexture) { - const im = new Image(1, 1); - im.set(0, 0, 255); - this._emptyTexture = new Texture(this._renderer, im); - } - return this._emptyTexture; - }, - }; - } - - filterShaders = { - [constants.BLUR] : filterBlurFrag, - [constants.INVERT]: filterInvertFrag, - [constants.THRESHOLD]: filterThresholdFrag, - [constants.ERODE]: filterErodeFrag, - [constants.GRAY] : filterGrayFrag, - [constants.DILATE]: filterDilateFrag, - [constants.POSTERIZE]: filterPosterizeFrag, - [constants.OPAQUE]: filterOpaqueFrag, - }; - - - vertSrc() { - return ` - attribute vec2 aPosition; - attribute vec2 aTexCoord; - varying vec2 vTexCoord; - void main() { - gl_Position = vec4(aPosition, 0.0, 1.0); - vTexCoord = aTexCoord; - } - `; - } - - // binding buffer - _bindBuffer(buffer, target, values, type, usage) { - const gl = this.gl; - if (!target) target = gl.ARRAY_BUFFER; - gl.bindBuffer(target, buffer); - if (values !== undefined) { - let data = values; - if (!(data instanceof (type || Float32Array))) { - data = new (type || Float32Array)(data); - } - gl.bufferData(target, data, usage || gl.STATIC_DRAW); - } - } - - render() { - // console.log(this.pInst._renderer.pixelDensity()); - const gl = this.gl; - // console.log(this.pInst.width); - let texelSize = [ - 1 / (this.pInst.width * this.pInst._renderer.pixelDensity()), - 1 / (this.pInst.height * this.pInst._renderer.pixelDensity()) - ]; - - // console.log(texelSize); - // Create and initialize the shader - if (!this._shader) { - // console.log(this.filterShaders['invert']); - this._shader = new Shader(this._renderer, this.vertSrc(), this.filterShaders[this.operation]); - } - this._shader.bindShader(); - - // Create a texture from the main p5.js canvas - // console.log(this.pInst._renderer.wrappedElt) - const canvasTexture = new Texture(this._renderer, this.pInst._renderer.wrappedElt); - // canvasTexture.update(); // Ensure the texture is updated with the latest canvas content - const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]); - const texcoords = new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]); - - const vertexBuffer = gl.createBuffer(); - this._bindBuffer(vertexBuffer, gl.ARRAY_BUFFER, vertices); - - this._shader.enableAttrib(this._shader.attributes.aPosition, 2); - // Create and bind the vertex buffer for positions - const texcoordBuffer = gl.createBuffer(); - - this._bindBuffer(texcoordBuffer, gl.ARRAY_BUFFER, texcoords); - // Create and bind the texture coordinate buffer - this._shader.enableAttrib(this._shader.attributes.aTexCoord, 2); - - // Set the texture uniform - this._shader.setUniform('tex0', canvasTexture); - this._shader.setUniform('texelSize', texelSize); - this._shader.setUniform('canvasSize', [this.pInst.width, this.pInst.height]); - this._shader.setUniform('direction', [1, 0]); - this._shader.setUniform('radius', 5); - - // Clear the canvas and draw the quad - gl.viewport(0, 0, this.canvas.width, this.canvas.height); - gl.clearColor(0.0, 0.0, 0.0, 1.0); - gl.clear(gl.COLOR_BUFFER_BIT); - - // Draw the quad (two triangles) - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - - // Unbind the shader and texture - this._shader.unbindShader(); - canvasTexture.unbindTexture(); - } -} - -export default FilterRenderer2D; diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js new file mode 100644 index 0000000000..87c2ca90ab --- /dev/null +++ b/src/image/filterRenderer2D.js @@ -0,0 +1,189 @@ +import { Shader } from "../webgl/p5.Shader"; +import { Texture } from "../webgl/p5.Texture"; +import { Image } from "./p5.Image"; +import * as constants from '../core/constants'; + +import filterGrayFrag from '../webgl/shaders/filters/gray.frag'; +import filterErodeFrag from '../webgl/shaders/filters/erode.frag'; +import filterDilateFrag from '../webgl/shaders/filters/dilate.frag'; +import filterBlurFrag from '../webgl/shaders/filters/blur.frag'; +import filterPosterizeFrag from '../webgl/shaders/filters/posterize.frag'; +import filterOpaqueFrag from '../webgl/shaders/filters/opaque.frag'; +import filterInvertFrag from '../webgl/shaders/filters/invert.frag'; +import filterThresholdFrag from '../webgl/shaders/filters/threshold.frag'; +import filterShaderVert from '../webgl/shaders/filters/default.vert'; + +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. + */ + constructor(pInst, operation, filterParameter) { + this.pInst = pInst; + this.filterParameter = filterParameter; + this.operation = operation; + + // Create a canvas for applying WebGL-based filters + this.canvas = document.createElement('canvas'); + this.canvas.width = pInst.width; + this.canvas.height = pInst.height; + + // Initialize the WebGL context + this.gl = this.canvas.getContext('webgl'); + if (!this.gl) { + console.error("WebGL not supported, cannot apply filter."); + return; + } + + // Minimal renderer object required by p5.Shader and p5.Texture + this._renderer = { + GL: this.gl, + registerEnabled: new Set(), + _curShader: null, + _emptyTexture: null, + webglVersion: 'WEBGL', + states: { + textureWrapX: this.gl.CLAMP_TO_EDGE, + textureWrapY: this.gl.CLAMP_TO_EDGE, + }, + _arraysEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b), + _getEmptyTexture: () => { + if (!this._emptyTexture) { + const im = new Image(1, 1); + im.set(0, 0, 255); + this._emptyTexture = new Texture(this._renderer, im); + } + return this._emptyTexture; + }, + }; + + // Fragment shaders mapped to filter operations + this.filterShaders = { + [constants.BLUR]: filterBlurFrag, + [constants.INVERT]: filterInvertFrag, + [constants.THRESHOLD]: filterThresholdFrag, + [constants.ERODE]: filterErodeFrag, + [constants.GRAY]: filterGrayFrag, + [constants.DILATE]: filterDilateFrag, + [constants.POSTERIZE]: filterPosterizeFrag, + [constants.OPAQUE]: filterOpaqueFrag, + }; + + this._shader = null; + this._initializeShader(); + } + + /** + * Initializes the shader program if it hasn't been already. + */ + _initializeShader() { + if (this._shader) return; // Already initialized + + const fragShaderSrc = this.filterShaders[this.operation]; + if (!fragShaderSrc) { + console.error("No shader available for this operation:", this.operation); + return; + } + + this._shader = new Shader(this._renderer, filterShaderVert, fragShaderSrc); + } + + /** + * Binds a buffer to the drawing context + * when passed more than two arguments it also updates or initializes + * the data associated with the buffer + */ + _bindBufferData(buffer, target, values, type, usage) { + const gl = this.gl; + gl.bindBuffer(target, buffer); + let data = values instanceof (type || Float32Array) ? values : new (type || Float32Array)(values); + gl.bufferData(target, data, usage || gl.STATIC_DRAW); + } + + /** + * Prepares and runs the full-screen quad draw call. + */ + _renderPass() { + const gl = this.gl; + this._shader.bindShader(); + + const pixelDensity = this.pInst._renderer.pixelDensity ? this.pInst._renderer.pixelDensity() : 1; + + const texelSize = [ + 1 / (this.pInst.width * pixelDensity), + 1 / (this.pInst.height * pixelDensity) + ]; + + const canvasTexture = new Texture(this._renderer, this.pInst._renderer.wrappedElt); + + // Set uniforms for the shader + this._shader.setUniform('tex0', canvasTexture); + this._shader.setUniform('texelSize', texelSize); + this._shader.setUniform('canvasSize', [this.pInst.width, this.pInst.height]); + this._shader.setUniform('radius', Math.max(1, this.filterParameter)); + + // Identity matrices for projection/model-view (unsure) + + // TODO: FIX IT + 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); + + // Set up the vertices and texture coordinates for a full-screen quad + const vertices = [-1, -1, 1, -1, -1, 1, 1, 1]; + const texcoords = [0, 1, 1, 1, 0, 0, 1, 0]; + + // Create and bind buffers + const vertexBuffer = gl.createBuffer(); + this._bindBufferData(vertexBuffer, gl.ARRAY_BUFFER, vertices, Float32Array, gl.STATIC_DRAW); + this._shader.enableAttrib(this._shader.attributes.aPosition, 2); + + const texcoordBuffer = gl.createBuffer(); + this._bindBufferData(texcoordBuffer, gl.ARRAY_BUFFER, texcoords, Float32Array, gl.STATIC_DRAW); + this._shader.enableAttrib(this._shader.attributes.aTexCoord, 2); + + // Draw the quad + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + + // Unbind the shader and texture + this._shader.unbindShader(); + } + + /** + * Applies the filter operation. If the filter requires multiple passes (e.g. blur), + * it handles those internally. + */ + 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) { + // Horizontal pass + this._renderPass(); + this._shader.setUniform('direction', [0,1]); + + // Draw the result onto itself + this.pInst.clear(); + this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height); + + // Vertical pass + this._renderPass(); + this._shader.setUniform('direction', [1,0]); + + this.pInst.clear(); + this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height); + } else { + // Single-pass filters + this._renderPass(); + this.pInst.clear(); + this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height); + } + } +} + +export default FilterRenderer2D; diff --git a/src/image/index.js b/src/image/index.js index b017b05544..ce63a52bd4 100644 --- a/src/image/index.js +++ b/src/image/index.js @@ -2,10 +2,14 @@ import image from './image.js'; import loadingDisplaying from './loading_displaying.js'; import p5image from './p5.Image.js'; import pixels from './pixels.js'; +import shader from '../webgl/p5.Shader.js'; +import texture from '../webgl/p5.Texture.js'; export default function(p5){ p5.registerAddon(image); p5.registerAddon(loadingDisplaying); p5.registerAddon(p5image); p5.registerAddon(pixels); + p5.registerAddon(shader); + p5.registerAddon(texture); } diff --git a/src/image/pixels.js b/src/image/pixels.js index c06c5368df..5c137fd45e 100644 --- a/src/image/pixels.js +++ b/src/image/pixels.js @@ -6,7 +6,7 @@ */ import Filters from './filters'; -import FilterRenderer2D from './filter-2d-renderer'; +import FilterRenderer2D from './filterRenderer2D'; function pixels(p5, fn){ /** @@ -723,7 +723,7 @@ function pixels(p5, fn){ */ fn.filter = function(...args) { p5._validateParameters('filter', args); - + let { shader, operation, value, useWebGL } = parseFilterArgs(...args); // when passed a shader, use it directly @@ -750,60 +750,15 @@ function pixels(p5, fn){ if (this._renderer.isP3D) { this._renderer.filter(operation, value); } - + // when this is P2D renderer, create/use hidden webgl renderer else { - - // TODO-1: 2 PASS BLUR (currently only works in horizontal blurring) - // TODO-2: ADDING CONDITIONS FOR shifting rendererGL filter for webgl and filterREnderer2D for 2d build filters - // TODO-3: PASSING p5.Shader, p5.Texture and fragment shader to expose an addon function that sets p5.Renderer2D.prototype.filter - // TODO-4: ADDING projection, modelView and necessary matrix - // TODO-5: code cleanups, adjusting the peices where they belong to. - - - - - // console.log(this._renderer.wrappedElt); - // this._renderer.clear(); - if (!this.filterRenderer) { - // console.log(this._renderer._pInst.canvas); - this.filterRenderer = new FilterRenderer2D(this, operation); - } - - const filterGraphicsLayer = this.getFilterGraphicsLayer(); - // copy p2d canvas contents to secondary webgl renderer - // dest - filterGraphicsLayer.copy( - this._renderer, - 0, 0, this.width, this.height, - 0, 0, this.width, this.height - ); - //clearing the main canvas - this.filterRenderer.render(); - this.drawingContext.drawImage(this.filterRenderer.canvas, 0, 0, this.width, this.height); - // this._renderer.clear(); - // this.filterRenderer.renderAgain(); - // this.drawingContext.drawImage(this.filterRenderer.canvas, 0, 0, this.width, this.height); - - // this._pInst.image(filterRenderer.canvas, 0, 0, width, height); - - - // this._renderer.resetMatrix(); - // filter it with shaders - // filterGraphicsLayer.filter(...args); - // console.log(this._renderer.image); - // console.log(this.filterRenderer.canvas); - // this._renderer.image(this.filterRenderer, 0, 0, this.width, this.height); - // copy secondary webgl renderer back to original p2d canvas - // this.copy( - // // src - // filterGraphicsLayer, - // // src coods - // 0, 0, this.width, this.height, - // // dest coords - // 0, 0, this.width, this.height - // ); - // filterGraphicsLayer.clear(); // prevent feedback effects on p2d canvas + + if (!this.filterRenderer) { + this.filterRenderer = new FilterRenderer2D(this, operation, value); + } + + this.filterRenderer.applyFilter(); } }; @@ -820,12 +775,6 @@ function pixels(p5, fn){ useWebGL: true }; - // console.log("hi"); - // if(args[0] instanceof FilterRenderer2D){ - // console.log("hii"); - // } - // console.log(args); - // console.log(this); if (args[0] instanceof p5.Shader) { result.shader = args[0]; return result; @@ -1211,4 +1160,4 @@ export default pixels; if(typeof p5 !== 'undefined'){ pixels(p5, p5.prototype); -} +} \ No newline at end of file From ff999037247b4e1a2d5449fc1bd2e4a90f60e648 Mon Sep 17 00:00:00 2001 From: Perminder Date: Sun, 8 Dec 2024 04:50:51 +0530 Subject: [PATCH 03/17] cache buffers --- src/image/filterRenderer2D.js | 36 ++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js index 87c2ca90ab..a5aaa00738 100644 --- a/src/image/filterRenderer2D.js +++ b/src/image/filterRenderer2D.js @@ -73,6 +73,21 @@ class FilterRenderer2D { this._shader = null; this._initializeShader(); + + // Create buffers once + this.vertexBuffer = this.gl.createBuffer(); + this.texcoordBuffer = this.gl.createBuffer(); + + // Set up the vertices and texture coordinates for a full-screen quad + this.vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]); + this.texcoords = new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]); + + // Upload vertex data once + this._bindBufferData(this.vertexBuffer, this.gl.ARRAY_BUFFER, this.vertices, Float32Array, this.gl.STATIC_DRAW); + + // Upload texcoord data once + this._bindBufferData(this.texcoordBuffer, this.gl.ARRAY_BUFFER, this.texcoords, Float32Array, this.gl.STATIC_DRAW); + } /** @@ -124,30 +139,21 @@ class FilterRenderer2D { this._shader.setUniform('canvasSize', [this.pInst.width, this.pInst.height]); this._shader.setUniform('radius', Math.max(1, this.filterParameter)); - // Identity matrices for projection/model-view (unsure) - - // TODO: FIX IT 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); - // Set up the vertices and texture coordinates for a full-screen quad - const vertices = [-1, -1, 1, -1, -1, 1, 1, 1]; - const texcoords = [0, 1, 1, 1, 0, 0, 1, 0]; - - // Create and bind buffers - const vertexBuffer = gl.createBuffer(); - this._bindBufferData(vertexBuffer, gl.ARRAY_BUFFER, vertices, Float32Array, gl.STATIC_DRAW); + // Bind and enable vertex attributes + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); this._shader.enableAttrib(this._shader.attributes.aPosition, 2); - const texcoordBuffer = gl.createBuffer(); - this._bindBufferData(texcoordBuffer, gl.ARRAY_BUFFER, texcoords, Float32Array, gl.STATIC_DRAW); + 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 and texture + // Unbind the shader this._shader.unbindShader(); } @@ -164,16 +170,16 @@ class FilterRenderer2D { // For blur, we typically do two passes: one horizontal, one vertical. if (this.operation === constants.BLUR) { // Horizontal pass + this._shader.setUniform('direction', [1,0]); this._renderPass(); - this._shader.setUniform('direction', [0,1]); // Draw the result onto itself this.pInst.clear(); this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height); // Vertical pass + this._shader.setUniform('direction', [0,1]); this._renderPass(); - this._shader.setUniform('direction', [1,0]); this.pInst.clear(); this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height); From 9aee0d0b46f2461950b5290f7970fcfca5972342 Mon Sep 17 00:00:00 2001 From: Perminder Date: Mon, 9 Dec 2024 02:47:14 +0530 Subject: [PATCH 04/17] fixes --- src/image/filterRenderer2D.js | 40 +++++++++++++++++++++++------------ src/image/pixels.js | 26 +++++++++++++++++------ 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js index a5aaa00738..0f896de6a4 100644 --- a/src/image/filterRenderer2D.js +++ b/src/image/filterRenderer2D.js @@ -19,11 +19,14 @@ class FilterRenderer2D { * @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) { + constructor(pInst, operation, filterParameter, customShader) { this.pInst = pInst; - this.filterParameter = filterParameter; + this.filterParameter = filterParameter; this.operation = operation; + this.customShader = customShader; + // Create a canvas for applying WebGL-based filters this.canvas = document.createElement('canvas'); @@ -83,19 +86,29 @@ class FilterRenderer2D { this.texcoords = new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]); // Upload vertex data once - this._bindBufferData(this.vertexBuffer, this.gl.ARRAY_BUFFER, this.vertices, Float32Array, this.gl.STATIC_DRAW); + this._bindBufferData(this.vertexBuffer, this.gl.ARRAY_BUFFER, this.vertices); // Upload texcoord data once - this._bindBufferData(this.texcoordBuffer, this.gl.ARRAY_BUFFER, this.texcoords, Float32Array, this.gl.STATIC_DRAW); + this._bindBufferData(this.texcoordBuffer, this.gl.ARRAY_BUFFER, this.texcoords); + + } + updateFilterParameter(newFilterParameter) { + // Operation is the same, just update parameter if changed + this.filterParameter = newFilterParameter; } - + /** * Initializes the shader program if it hasn't been already. */ _initializeShader() { if (this._shader) return; // Already initialized + if (this.customShader) { + this._shader = this.customShader; + return; + } + const fragShaderSrc = this.filterShaders[this.operation]; if (!fragShaderSrc) { console.error("No shader available for this operation:", this.operation); @@ -110,11 +123,10 @@ class FilterRenderer2D { * when passed more than two arguments it also updates or initializes * the data associated with the buffer */ - _bindBufferData(buffer, target, values, type, usage) { + _bindBufferData(buffer, target, values) { const gl = this.gl; gl.bindBuffer(target, buffer); - let data = values instanceof (type || Float32Array) ? values : new (type || Float32Array)(values); - gl.bufferData(target, data, usage || gl.STATIC_DRAW); + gl.bufferData(target, values, gl.STATIC_DRAW); } /** @@ -139,7 +151,7 @@ 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); @@ -168,19 +180,19 @@ class FilterRenderer2D { } // For blur, we typically do two passes: one horizontal, one vertical. - if (this.operation === constants.BLUR) { + if (this.operation === constants.BLUR && !this.customShader) { // Horizontal pass - this._shader.setUniform('direction', [1,0]); + this._shader.setUniform('direction', [1, 0]); this._renderPass(); - + // Draw the result onto itself this.pInst.clear(); this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height); // Vertical pass - this._shader.setUniform('direction', [0,1]); + this._shader.setUniform('direction', [0, 1]); this._renderPass(); - + this.pInst.clear(); this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height); } else { diff --git a/src/image/pixels.js b/src/image/pixels.js index 5c137fd45e..f245e80362 100644 --- a/src/image/pixels.js +++ b/src/image/pixels.js @@ -753,12 +753,26 @@ function pixels(p5, fn){ // when this is P2D renderer, create/use hidden webgl renderer else { - - if (!this.filterRenderer) { - this.filterRenderer = new FilterRenderer2D(this, operation, value); - } - - this.filterRenderer.applyFilter(); + if (shader) { + const customFilterRenderer = new FilterRenderer2D(this, operation, value, shader); + customFilterRenderer.applyFilter(); + } 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(); + } } }; From e714125fb69fb579e0f39a0ba76d58dfb62a12bf Mon Sep 17 00:00:00 2001 From: Perminder Date: Wed, 11 Dec 2024 05:06:45 +0530 Subject: [PATCH 05/17] 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. From a5b097356c036ddee776dd078bc86f5f0052620d Mon Sep 17 00:00:00 2001 From: Perminder Date: Thu, 12 Dec 2024 02:59:03 +0530 Subject: [PATCH 06/17] minor-changes testing still left --- src/image/pixels.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/image/pixels.js b/src/image/pixels.js index 9cd2f9e4de..5759215689 100644 --- a/src/image/pixels.js +++ b/src/image/pixels.js @@ -752,21 +752,14 @@ function pixels(p5, fn){ } // when this is P2D renderer, create/use hidden webgl renderer - else { - if (!this.filterRenderer) { - this.filterRenderer = new FilterRenderer2D(this); - this._renderer.filterRenderer = this.filterRenderer; - } + else { if (shader) { - this.filterRenderer.setOperation(operation, value, shader); + this._renderer.filterRenderer.setOperation(operation, value, shader); } else { - // No custom shader, just a built-in filter - this.filterRenderer.setOperation(operation, value); + this._renderer.filterRenderer.setOperation(operation, value); } - - // Apply the current filter - this.filterRenderer.applyFilter(); - + + this._renderer.filterRenderer.applyFilter(); } }; From 6978551f5cafae8adacda733b907489e6e60a435 Mon Sep 17 00:00:00 2001 From: Perminder Date: Thu, 12 Dec 2024 03:07:13 +0530 Subject: [PATCH 07/17] minor-fixes --- src/image/pixels.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/image/pixels.js b/src/image/pixels.js index 5759215689..254beac8fc 100644 --- a/src/image/pixels.js +++ b/src/image/pixels.js @@ -6,7 +6,6 @@ */ import Filters from './filters'; -import FilterRenderer2D from './filterRenderer2D'; function pixels(p5, fn){ /** From d45c50f099fb808632fbb15ebc5d8dd35dec6cb5 Mon Sep 17 00:00:00 2001 From: Perminder Date: Thu, 12 Dec 2024 03:30:28 +0530 Subject: [PATCH 08/17] minor-fixes --- src/image/filterRenderer2D.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js index c5a732e687..1e421d449c 100644 --- a/src/image/filterRenderer2D.js +++ b/src/image/filterRenderer2D.js @@ -154,15 +154,14 @@ class FilterRenderer2D { _renderPass() { const gl = this.gl; this._shader.bindShader(); - - const pixelDensity = this.pInst._renderer.pixelDensity ? this.pInst._renderer.pixelDensity() : 1; + const pixelDensity = this.pInst.pixelDensity ? this.pInst.pixelDensity() : 1; const texelSize = [ 1 / (this.pInst.width * pixelDensity), 1 / (this.pInst.height * pixelDensity) ]; - const canvasTexture = new Texture(this._renderer, this.pInst._renderer.wrappedElt); + const canvasTexture = new Texture(this._renderer, this.pInst.wrappedElt); // Set uniforms for the shader this._shader.setUniform('tex0', canvasTexture); From 64adf2298f7fa5ab2f28bc78917bf786c6ede9a0 Mon Sep 17 00:00:00 2001 From: Perminder Date: Thu, 12 Dec 2024 03:36:44 +0530 Subject: [PATCH 09/17] adding filterParamter --- src/image/filterRenderer2D.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js index 1e421d449c..3af676e7f7 100644 --- a/src/image/filterRenderer2D.js +++ b/src/image/filterRenderer2D.js @@ -168,6 +168,7 @@ class FilterRenderer2D { this._shader.setUniform('texelSize', texelSize); this._shader.setUniform('canvasSize', [this.pInst.width, this.pInst.height]); this._shader.setUniform('radius', Math.max(1, this.filterParameter)); + this._shader.setUniform('filterParameter', this.filterParameter); const identityMatrix = [1, 0, 0, 0, 0, 1, 0, 0, From 9755b1df97cdae1e03a677e33e563f77f11c9374 Mon Sep 17 00:00:00 2001 From: Perminder Date: Thu, 12 Dec 2024 03:46:19 +0530 Subject: [PATCH 10/17] for-testing --- src/webgl/p5.Shader.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js index 75859f056b..7ae60d7b01 100644 --- a/src/webgl/p5.Shader.js +++ b/src/webgl/p5.Shader.js @@ -625,13 +625,12 @@ 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._renderer.filterRenderer - ? context._renderer.filterRenderer._renderer - : context._renderer; + this._renderer = 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. From 3a1df0a9a5f7b83799a88ae1eda3dc80e52ebee7 Mon Sep 17 00:00:00 2001 From: Perminder Date: Thu, 12 Dec 2024 03:55:49 +0530 Subject: [PATCH 11/17] removing-filterGraphicsLayer-tests --- preview/index.html | 31 +++++++++++++++++-------------- test/unit/webgl/p5.RendererGL.js | 12 ------------ 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/preview/index.html b/preview/index.html index ac5bedefcc..d87cbcaf86 100644 --- a/preview/index.html +++ b/preview/index.html @@ -20,25 +20,28 @@ import p5 from '../src/app.js'; const sketch = function (p) { - let g, f; + let g, f,s,fragSrc; p.setup = function () { p.createCanvas(200, 200); g = p.createGraphics(200, 200); f = p.createGraphics(200, 200, p.WEBGL); - }; - - p.draw = function () { - p.background(0, 50, 50); - p.circle(100, 100, 50); - - p.fill('white'); - p.textSize(30); - p.text('hello', 10, 30); - - // f.fill('red'); - f.sphere(); - p.image(f, 0, 0); + fragSrc = ` + precision highp float; + void main() { + gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0); + }`; + s = p.createFilterShader(fragSrc); + p.fill('red'); + p.rect(0,0,100,100) + p.filter(s); + p.filter(p.POSTERIZE, 64); + // p.filter(p.POSTERIZE,64); + // p.filter(s); + }; + + p.draw = function () { + p.image(f,0,0); }; }; diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 8fd8b0fa06..5843e36d2f 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -412,18 +412,6 @@ suite('p5.RendererGL', function() { assert.doesNotThrow(testDefaultParams, 'this should not throw'); }); - test('filter() uses WEBGL implementation behind main P2D canvas', function() { - let renderer = myp5.createCanvas(3,3); - myp5.filter(myp5.BLUR); - assert.isDefined(renderer.filterGraphicsLayer); - }); - - test('filter() can opt out of WEBGL implementation', function() { - let renderer = myp5.createCanvas(3,3); - myp5.filter(myp5.BLUR, false); - assert.isUndefined(renderer.filterGraphicsLayer); - }); - test('filters make changes to canvas', function() { myp5.createCanvas(20,20); myp5.circle(10,10,12); From 5db9a7f9e1c33f81f51c34deead10bafc143ec3b Mon Sep 17 00:00:00 2001 From: Perminder Date: Thu, 12 Dec 2024 04:00:55 +0530 Subject: [PATCH 12/17] revert-index.html --- preview/index.html | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/preview/index.html b/preview/index.html index d87cbcaf86..ac5bedefcc 100644 --- a/preview/index.html +++ b/preview/index.html @@ -20,28 +20,25 @@ import p5 from '../src/app.js'; const sketch = function (p) { - let g, f,s,fragSrc; + let g, f; p.setup = function () { p.createCanvas(200, 200); g = p.createGraphics(200, 200); f = p.createGraphics(200, 200, p.WEBGL); - fragSrc = ` - precision highp float; - void main() { - gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0); - }`; - s = p.createFilterShader(fragSrc); - p.fill('red'); - p.rect(0,0,100,100) - p.filter(s); - p.filter(p.POSTERIZE, 64); - // p.filter(p.POSTERIZE,64); - // p.filter(s); - }; - - p.draw = function () { - p.image(f,0,0); + }; + + p.draw = function () { + p.background(0, 50, 50); + p.circle(100, 100, 50); + + p.fill('white'); + p.textSize(30); + p.text('hello', 10, 30); + + // f.fill('red'); + f.sphere(); + p.image(f, 0, 0); }; }; From beaff232ea05ffbcf71dd57125220e2af44d85ea Mon Sep 17 00:00:00 2001 From: Perminder Date: Fri, 13 Dec 2024 01:23:15 +0530 Subject: [PATCH 13/17] some-minor-test-fixes --- src/webgl/material.js | 6 +++++- src/webgl/p5.Shader.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/webgl/material.js b/src/webgl/material.js index 187dd08949..89c14de19a 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -647,7 +647,11 @@ function material(p5, fn){ `; let vertSrc = fragSrc.includes('#version 300 es') ? defaultVertV2 : defaultVertV1; const shader = new Shader(this._renderer, vertSrc, fragSrc); - shader.ensureCompiledOnContext(this); + if (this._renderer.GL) { + shader.ensureCompiledOnContext(this._renderer); + } else { + shader.ensureCompiledOnContext(this); + } return shader; }; diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js index 7ae60d7b01..01256011df 100644 --- a/src/webgl/p5.Shader.js +++ b/src/webgl/p5.Shader.js @@ -625,7 +625,7 @@ 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?._renderer?.filterRenderer?._renderer || context?._renderer; + this._renderer = context?._renderer?.filterRenderer?._renderer || context; this.init(); } } From 8a27e7927b0fbbe9a18ba4aaeb56f4b31220989a Mon Sep 17 00:00:00 2001 From: Perminder Date: Fri, 13 Dec 2024 02:46:44 +0530 Subject: [PATCH 14/17] for tests --- src/image/filterRenderer2D.js | 12 ++++++++++++ src/image/pixels.js | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js index 3af676e7f7..8823beb86a 100644 --- a/src/image/filterRenderer2D.js +++ b/src/image/filterRenderer2D.js @@ -170,6 +170,12 @@ class FilterRenderer2D { this._shader.setUniform('radius', Math.max(1, this.filterParameter)); this._shader.setUniform('filterParameter', this.filterParameter); + this.pInst.states.rectMode = constants.CORNER; + this.pInst.states.imageMode = constants.CORNER; + this.pInst.blendMode(constants.BLEND); + this.pInst.resetMatrix(); + + const identityMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, @@ -188,6 +194,7 @@ class FilterRenderer2D { gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // Unbind the shader this._shader.unbindShader(); + } /** @@ -217,8 +224,13 @@ class FilterRenderer2D { this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height); } else { // Single-pass filters + this._renderPass(); this.pInst.clear(); + // con + this.pInst.blendMode(constants.BLEND); + + this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height); } } diff --git a/src/image/pixels.js b/src/image/pixels.js index 254beac8fc..64f9764d96 100644 --- a/src/image/pixels.js +++ b/src/image/pixels.js @@ -752,6 +752,9 @@ function pixels(p5, fn){ // when this is P2D renderer, create/use hidden webgl renderer else { + + this._renderer.resetMatrix(); + if (shader) { this._renderer.filterRenderer.setOperation(operation, value, shader); } else { From 9f340fe19fa46f0fbcc1ba3ea8956f51c9b6c5ad Mon Sep 17 00:00:00 2001 From: Perminder Date: Sun, 15 Dec 2024 17:00:53 +0530 Subject: [PATCH 15/17] fixing-webgl-modes --- src/webgl/p5.RendererGL.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index b695e0d967..afce7c41ea 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1092,6 +1092,11 @@ class RendererGL extends Renderer { 1 / (target.height * target.pixelDensity()) ]; + this.blendMode(constants.BLEND); + this.states.rectMode = constants.CORNER; + this.states.imageMode = constants.CORNER; + + // apply blur shader with multiple passes. if (operation === constants.BLUR) { // Treating 'tmp' as a framebuffer. @@ -1101,7 +1106,6 @@ class RendererGL extends Renderer { // setup this.push(); this.states.doStroke = false; - this.blendMode(constants.BLEND); // draw main to temp buffer this.shader(this.states.filterShader); @@ -1135,7 +1139,6 @@ class RendererGL extends Renderer { else { fbo.draw(() => { this.states.doStroke = false; - this.blendMode(constants.BLEND); this.shader(this.states.filterShader); this.states.filterShader.setUniform('tex0', target); this.states.filterShader.setUniform('texelSize', texelSize); @@ -1153,8 +1156,6 @@ class RendererGL extends Renderer { this.states.doStroke = false; this.clear(); this.push(); - this.states.imageMode = constants.CORNER; - this.blendMode(constants.BLEND); target.filterCamera._resize(); this.setCamera(target.filterCamera); this.resetMatrix(); From e4e020eb52c623cf9f2f081c2fe33c603f9c0b33 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Mon, 16 Dec 2024 20:04:04 -0500 Subject: [PATCH 16/17] Handle default parameters --- src/image/const.js | 6 ++++++ src/image/filterRenderer2D.js | 29 ++++++++++++++++++++++++----- src/image/pixels.js | 10 ++++------ src/webgl/p5.RendererGL.js | 19 +++++++------------ src/webgl/p5.Texture.js | 2 +- 5 files changed, 42 insertions(+), 24 deletions(-) create mode 100644 src/image/const.js diff --git a/src/image/const.js b/src/image/const.js new file mode 100644 index 0000000000..535ce66c31 --- /dev/null +++ b/src/image/const.js @@ -0,0 +1,6 @@ +import * as constants from '../core/constants'; +export const filterParamDefaults = { + [constants.BLUR]: 3, + [constants.POSTERIZE]: 4, + [constants.THRESHOLD]: 0.5, +}; diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js index 8823beb86a..6a3f194466 100644 --- a/src/image/filterRenderer2D.js +++ b/src/image/filterRenderer2D.js @@ -12,6 +12,7 @@ import filterOpaqueFrag from '../webgl/shaders/filters/opaque.frag'; import filterInvertFrag from '../webgl/shaders/filters/invert.frag'; import filterThresholdFrag from '../webgl/shaders/filters/threshold.frag'; import filterShaderVert from '../webgl/shaders/filters/default.vert'; +import { filterParamDefaults } from "./const"; class FilterRenderer2D { /** @@ -99,6 +100,12 @@ class FilterRenderer2D { setOperation(operation, filterParameter, customShader = null) { this.operation = operation; this.filterParameter = filterParameter; + + let useDefaultParam = operation in filterParamDefaults && filterParameter === undefined; + if (useDefaultParam) { + this.filterParameter = filterParamDefaults[operation]; + } + this.customShader = customShader; this._initializeShader(); } @@ -148,6 +155,13 @@ class FilterRenderer2D { gl.bufferData(target, values, gl.STATIC_DRAW); } + get canvasTexture() { + if (!this._canvasTexture) { + this._canvasTexture = new Texture(this._renderer, this.pInst.wrappedElt); + } + return this._canvasTexture; + } + /** * Prepares and runs the full-screen quad draw call. */ @@ -161,7 +175,7 @@ class FilterRenderer2D { 1 / (this.pInst.height * pixelDensity) ]; - const canvasTexture = new Texture(this._renderer, this.pInst.wrappedElt); + const canvasTexture = this.canvasTexture; // Set uniforms for the shader this._shader.setUniform('tex0', canvasTexture); @@ -186,15 +200,17 @@ class FilterRenderer2D { // 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); - + + this._shader.bindTextures(); + this._shader.disableRemainingAttributes(); + // Draw the quad gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // Unbind the shader this._shader.unbindShader(); - } /** @@ -206,6 +222,8 @@ class FilterRenderer2D { console.error("Cannot apply filter: shader not initialized."); return; } + this.pInst.push(); + this.pInst.resetMatrix(); // For blur, we typically do two passes: one horizontal, one vertical. if (this.operation === constants.BLUR && !this.customShader) { // Horizontal pass @@ -230,9 +248,10 @@ class FilterRenderer2D { // con this.pInst.blendMode(constants.BLEND); - + this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height); } + this.pInst.pop(); } } diff --git a/src/image/pixels.js b/src/image/pixels.js index 64f9764d96..c2e2e10c6b 100644 --- a/src/image/pixels.js +++ b/src/image/pixels.js @@ -749,18 +749,16 @@ function pixels(p5, fn){ if (this._renderer.isP3D) { this._renderer.filter(operation, value); } - - // when this is P2D renderer, create/use hidden webgl renderer - else { - this._renderer.resetMatrix(); + // when this is P2D renderer, create/use hidden webgl renderer + else { if (shader) { this._renderer.filterRenderer.setOperation(operation, value, shader); } else { this._renderer.filterRenderer.setOperation(operation, value); } - + this._renderer.filterRenderer.applyFilter(); } }; @@ -1163,4 +1161,4 @@ export default pixels; if(typeof p5 !== 'undefined'){ pixels(p5, p5.prototype); -} \ No newline at end of file +} diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 06d5be4b2a..b929542869 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -14,6 +14,7 @@ import { Graphics } from "../core/p5.Graphics"; import { Element } from "../dom/p5.Element"; import { ShapeBuilder } from "./ShapeBuilder"; import { GeometryBufferCache } from "./GeometryBufferCache"; +import { filterParamDefaults } from '../image/const'; import lightingShader from "./shaders/lighting.glsl"; import webgl2CompatibilityShader from "./shaders/webgl2Compatibility.glsl"; @@ -1150,13 +1151,8 @@ class RendererGL extends Renderer { let operation = undefined; if (typeof args[0] === "string") { operation = args[0]; - let defaults = { - [constants.BLUR]: 3, - [constants.POSTERIZE]: 4, - [constants.THRESHOLD]: 0.5, - }; - let useDefaultParam = operation in defaults && args[1] === undefined; - filterParameter = useDefaultParam ? defaults[operation] : args[1]; + let useDefaultParam = operation in filterParamDefaults && args[1] === undefined; + filterParameter = useDefaultParam ? filterParamDefaults[operation] : args[1]; // Create and store shader for constants once on initial filter call. // Need to store multiple in case user calls different filters, @@ -1189,11 +1185,6 @@ class RendererGL extends Renderer { 1 / (target.height * target.pixelDensity()), ]; - this.blendMode(constants.BLEND); - this.states.rectMode = constants.CORNER; - this.states.imageMode = constants.CORNER; - - // apply blur shader with multiple passes. if (operation === constants.BLUR) { // Treating 'tmp' as a framebuffer. @@ -1203,6 +1194,7 @@ class RendererGL extends Renderer { // setup this.push(); this.states.strokeColor = null; + this.blendMode(constants.BLEND); // draw main to temp buffer this.shader(this.states.filterShader); @@ -1242,6 +1234,7 @@ class RendererGL extends Renderer { else { fbo.draw(() => { this.states.strokeColor = null; + this.blendMode(constants.BLEND); this.shader(this.states.filterShader); this.states.filterShader.setUniform("tex0", target); this.states.filterShader.setUniform("texelSize", texelSize); @@ -1261,6 +1254,8 @@ class RendererGL extends Renderer { this.states.strokeColor = null; this.clear(); this.push(); + this.states.imageMode = constants.CORNER; + this.blendMode(constants.BLEND); target.filterCamera._resize(); this.setCamera(target.filterCamera); this.resetMatrix(); diff --git a/src/webgl/p5.Texture.js b/src/webgl/p5.Texture.js index 7be2831076..11d5b85fe6 100644 --- a/src/webgl/p5.Texture.js +++ b/src/webgl/p5.Texture.js @@ -207,7 +207,7 @@ class Texture { // flag for update in a future frame. // if we don't do this, a paused video, for example, may not // send the first frame to texture memory. - data.setModified(true); + data.setModified && data.setModified(true); } } else if (this.isSrcP5Image) { // for an image, we only update if the modified field has been set, From 80a5c5d9e8900002a54a27e4f08aaffddf38dce1 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Mon, 16 Dec 2024 20:04:11 -0500 Subject: [PATCH 17/17] Add visual tests --- test/unit/visual/cases/webgl.js | 17 +++++++++++++++++ .../000.png | Bin 0 -> 335 bytes .../metadata.json | 3 +++ .../000.png | Bin 0 -> 339 bytes .../metadata.json | 3 +++ test/unit/webgl/p5.RendererGL.js | 1 + 6 files changed, 24 insertions(+) create mode 100644 test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/000.png create mode 100644 test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/metadata.json create mode 100644 test/unit/visual/screenshots/WebGL/filter/In webgl mode/It can combine multiple filter passes/000.png create mode 100644 test/unit/visual/screenshots/WebGL/filter/In webgl mode/It can combine multiple filter passes/metadata.json diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 91bc52ddd2..fe676a0e8c 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -70,6 +70,23 @@ visualSuite('WebGL', function() { screenshot(); } ); + + for (const mode of ['webgl', '2d']) { + visualSuite(`In ${mode} mode`, function() { + visualTest('It can combine multiple filter passes', function(p5, screenshot) { + p5.createCanvas(50, 50, mode === 'webgl' ? p5.WEBGL : p5.P2D); + if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2); + p5.background(255); + p5.fill(0); + p5.noStroke(); + p5.circle(15, 15, 20); + p5.circle(30, 30, 20); + p5.filter(p5.BLUR, 5); + p5.filter(p5.THRESHOLD); + screenshot(); + }); + }); + } }); visualSuite('Lights', function() { diff --git a/test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/000.png b/test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/000.png new file mode 100644 index 0000000000000000000000000000000000000000..9679aa86ddef9de37307dba4aebf912d7d1cdcd0 GIT binary patch literal 335 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^To$A&Gg48jF~mTjwlTGf(hHRe#Q8kFd&%-r3q7 zY-*|#dmhgyQ~YPe6LI!Gho$(ID#JQw*Jd99F||!k&P;eGA-=@;81K(D-~IM(X4x5V zQQ?UKN27t)xo*y8mt-%+i4u-$?u$<=6g(S0&m*LgGf7kO{EVxgvL>E*<@(Zf&*_&)8x@uF-zWciKEU8Q5UTv#Q_S*FKwH2xMnJIY|1`k>D97x_lez8G1&UjT`E=E TRa4FYLx{oC)z4*}Q$iB}ASQ@Q literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/metadata.json b/test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGL/filter/In webgl mode/It can combine multiple filter passes/000.png b/test/unit/visual/screenshots/WebGL/filter/In webgl mode/It can combine multiple filter passes/000.png new file mode 100644 index 0000000000000000000000000000000000000000..ed3273a6a6f7b76d8719603db4a8090bd88d1403 GIT binary patch literal 339 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^T-(@SkU3a)Ko>ae{7oTgdSp3R4a9PVBv|efFHt z6Q_la6Pp>B61U6|XKr3#7_I2hqi|2Zaq)A(!~1OoL#jEOa&%p^RD~kmfR zgln5!(*ox`CtUv~)yF eCkveYhn0VkO4aVCNzA}dV(@hJb6Mw<&;$ShC53 {}], ['corner rectMode', () => myp5.rectMode(myp5.CORNER)], ['corners rectMode', () => myp5.rectMode(myp5.CORNERS)], ['center rectMode', () => myp5.rectMode(myp5.CENTER)],