-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FilterRenderer2D for a 2d-Build #7409
Merged
davepagurek
merged 18 commits into
processing:dev-2.0
from
perminder-17:2d-build-filter
Dec 17, 2024
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
d71efff
2dFilterRenderer still a work in progress PR, needs to do some todo t…
perminder-17 7b5b889
code cleanups and minor fixes
perminder-17 ff99903
cache buffers
perminder-17 9aee0d0
fixes
perminder-17 e714125
suggestions-fixes
perminder-17 a5b0973
minor-changes testing still left
perminder-17 6978551
minor-fixes
perminder-17 d45c50f
minor-fixes
perminder-17 64adf22
adding filterParamter
perminder-17 9755b1d
for-testing
perminder-17 3a1df0a
removing-filterGraphicsLayer-tests
perminder-17 5db9a7f
revert-index.html
perminder-17 beaff23
some-minor-test-fixes
perminder-17 8a27e79
for tests
perminder-17 9f340fe
fixing-webgl-modes
perminder-17 61754a0
Merge branch 'dev-2.0' into 2d-build-filter
perminder-17 e4e020e
Handle default parameters
davepagurek 80a5c5d
Add visual tests
davepagurek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import * as constants from '../core/constants'; | ||
export const filterParamDefaults = { | ||
[constants.BLUR]: 3, | ||
[constants.POSTERIZE]: 4, | ||
[constants.THRESHOLD]: 0.5, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
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'; | ||
import { filterParamDefaults } from "./const"; | ||
|
||
class FilterRenderer2D { | ||
/** | ||
* Creates a new FilterRenderer2D instance. | ||
* @param {p5} pInst - The p5.js instance. | ||
*/ | ||
constructor(pInst) { | ||
this.pInst = pInst; | ||
// 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; | ||
}, | ||
}; | ||
|
||
// Store the fragment shader sources | ||
this.filterShaderSources = { | ||
[constants.BLUR]: filterBlurFrag, | ||
[constants.INVERT]: filterInvertFrag, | ||
[constants.THRESHOLD]: filterThresholdFrag, | ||
[constants.ERODE]: filterErodeFrag, | ||
[constants.GRAY]: filterGrayFrag, | ||
[constants.DILATE]: filterDilateFrag, | ||
[constants.POSTERIZE]: filterPosterizeFrag, | ||
[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; | ||
|
||
// 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); | ||
|
||
// Upload texcoord data once | ||
this._bindBufferData(this.texcoordBuffer, this.gl.ARRAY_BUFFER, this.texcoords); | ||
} | ||
|
||
/** | ||
* 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; | ||
|
||
let useDefaultParam = operation in filterParamDefaults && filterParameter === undefined; | ||
if (useDefaultParam) { | ||
this.filterParameter = filterParamDefaults[operation]; | ||
} | ||
|
||
this.customShader = customShader; | ||
this._initializeShader(); | ||
} | ||
|
||
/** | ||
* 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.customShader) { | ||
this._shader = this.customShader; | ||
return; | ||
} | ||
|
||
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; | ||
} | ||
|
||
// Create and store the new shader | ||
const newShader = new Shader(this._renderer, filterShaderVert, fragShaderSrc); | ||
this.filterShaders[this.operation] = newShader; | ||
this._shader = newShader; | ||
} | ||
|
||
/** | ||
* 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) { | ||
const gl = this.gl; | ||
gl.bindBuffer(target, buffer); | ||
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. | ||
*/ | ||
_renderPass() { | ||
const gl = this.gl; | ||
this._shader.bindShader(); | ||
const pixelDensity = this.pInst.pixelDensity ? this.pInst.pixelDensity() : 1; | ||
|
||
const texelSize = [ | ||
1 / (this.pInst.width * pixelDensity), | ||
1 / (this.pInst.height * pixelDensity) | ||
]; | ||
|
||
const canvasTexture = this.canvasTexture; | ||
|
||
// 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)); | ||
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, | ||
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); | ||
|
||
this._shader.bindTextures(); | ||
this._shader.disableRemainingAttributes(); | ||
|
||
// Draw the quad | ||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | ||
// Unbind the shader | ||
this._shader.unbindShader(); | ||
} | ||
|
||
/** | ||
* 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; | ||
} | ||
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 | ||
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._renderPass(); | ||
|
||
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(); | ||
// con | ||
this.pInst.blendMode(constants.BLEND); | ||
|
||
|
||
this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height); | ||
} | ||
this.pInst.pop(); | ||
} | ||
} | ||
|
||
export default FilterRenderer2D; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw @davepagurek, my custom filterShader works for 2d mode now but I need to add my
filterRenderer
here as well and in pixel.js as wellany idea why this works only when I have initialized filterRenderer in both the places and by removing any of them, it stops to work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
this
represents different things in both cases: in this file, it's ap5.Renderer2D
, but in the pixels.js file, it's aninstance ofp5
. In that file, to access the one on the renderer, I think you need to check forthis._renderer.filterRenderer
.