Skip to content
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

Auto-bind filter shaders to the filter graphic #6482

Merged
merged 1 commit into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions src/webgl/material.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,9 @@ p5.prototype.createFilterShader = function(fragSrc) {
}
`;
let vertSrc = fragSrc.includes('#version 300 es') ? defaultVertV2 : defaultVertV1;
return new p5.Shader(this._renderer, vertSrc, fragSrc);
const shader = new p5.Shader(this._renderer, vertSrc, fragSrc);
shader.ensureCompiledOnContext(this._renderer.getFilterGraphicsLayer());
return shader;
};

/**
Expand Down Expand Up @@ -393,11 +395,7 @@ p5.prototype.shader = function(s) {
this._assert3d('shader');
p5._validateParameters('shader', arguments);

if (s._renderer === undefined) {
s._renderer = this._renderer;
}

s.init();
s.ensureCompiledOnContext(this);

if (s.isStrokeShader()) {
this._renderer.userStrokeShader = s;
Expand Down
95 changes: 52 additions & 43 deletions src/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -975,10 +975,10 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
this.curStrokeJoin = join;
}

filter(...args) {
// Couldn't create graphics in RendererGL constructor
// (led to infinite loop)
// so it's just created here once on the initial filter call.
getFilterGraphicsLayer() {
// Lazily initialize the filter graphics layer. We only do this on demand
// because the graphics layer itself has a p5.RendererGL, which would then
// try to make its own filter layer, infinitely looping.
if (!this.filterGraphicsLayer) {
// the real _pInst is buried when this is a secondary p5.Graphics

Expand All @@ -996,17 +996,53 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
// geometries/borders on this layer should always be invisible
this.filterGraphicsLayer.noStroke();
}
else if(
if (
this.filterGraphicsLayer.width !== this.width ||
this.filterGraphicsLayer.height !== this.height ||
this.filterGraphicsLayer.pixelDensity() !== this._pInst.pixelDensity()
){
this.filterGraphicsLayer.height !== this.height
) {
// Resize the graphics layer
this.filterGraphicsLayer.resizeCanvas(this.width, this.height);
}
if (
this.filterGraphicsLayer.pixelDensity() !== this._pInst.pixelDensity()
) {
this.filterGraphicsLayer.pixelDensity(this._pInst.pixelDensity());
}
return this.filterGraphicsLayer;
}

let pg = this.filterGraphicsLayer;
getFilterGraphicsLayerTemp() {
// two-pass blur filter needs another graphics layer
if (!this.filterGraphicsLayerTemp) {
const pInst = this._pInst instanceof p5.Graphics ?
this._pInst._pInst : this._pInst;
// create secondary layer
this.filterGraphicsLayerTemp =
new p5.Graphics(
this.width,
this.height,
constants.WEBGL,
pInst
);
this.filterGraphicsLayerTemp.noStroke();
}
if (
this.filterGraphicsLayerTemp.width !== this.width ||
this.filterGraphicsLayerTemp.height !== this.height
) {
// Resize the graphics layer
this.filterGraphicsLayerTemp.resizeCanvas(this.width, this.height);
}
if (
this.filterGraphicsLayerTemp.pixelDensity() !== this._pInst.pixelDensity()
) {
this.filterGraphicsLayerTemp.pixelDensity(this._pInst.pixelDensity());
}
return this.filterGraphicsLayerTemp;
}

filter(...args) {
let pg = this.getFilterGraphicsLayer();

// use internal shader for filter constants BLUR, INVERT, etc
let filterParameter = undefined;
Expand All @@ -1030,41 +1066,13 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
filterShaderVert,
filterShaderFrags[operation]
);

// two-pass blur filter needs another graphics layer
if(!this.filterGraphicsLayerTemp) {
const pInst = this._pInst instanceof p5.Graphics ?
this._pInst._pInst : this._pInst;
// create secondary layer
this.filterGraphicsLayerTemp =
new p5.Graphics(
this.width,
this.height,
constants.WEBGL,
pInst
);
this.filterGraphicsLayerTemp.noStroke();
}
}
this.filterShader = this.defaultFilterShaders[operation];

}
// use custom user-supplied shader
else {
let userShader = args[0];

// Copy the user shader once on the initial filter call,
// since it has to be bound to pg and not main
let isSameUserShader = (
this.filterShader !== undefined &&
userShader._vertSrc === this.filterShader._vertSrc &&
userShader._fragSrc === this.filterShader._fragSrc
);
if (!isSameUserShader) {
this.filterShader =
new p5.Shader(pg._renderer, userShader._vertSrc, userShader._fragSrc);
this.filterShader.parentShader = userShader;
}
this.filterShader = args[0];
}

pg.clear(); // prevent undesirable feedback effects accumulating secretly
Expand All @@ -1075,14 +1083,15 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
// apply blur shader with multiple passes
if (operation === constants.BLUR) {

this.filterGraphicsLayerTemp.clear(); // prevent feedback effects here too
const tmp = this.getFilterGraphicsLayerTemp();
tmp.clear(); // prevent feedback effects here too

// setup
this._pInst.push();
this._pInst.noStroke();

// draw main to temp buffer
this.filterGraphicsLayerTemp.image(this, -this.width/2, -this.height/2);
tmp.image(this, -this.width/2, -this.height/2);

pg.shader(this.filterShader);
this.filterShader.setUniform('texelSize', texelSize);
Expand All @@ -1091,15 +1100,15 @@ p5.RendererGL = class RendererGL extends p5.Renderer {

// horiz pass
this.filterShader.setUniform('direction', [1, 0]);
this.filterShader.setUniform('tex0', this.filterGraphicsLayerTemp);
this.filterShader.setUniform('tex0', tmp);
pg.rect(-this.width/2, -this.height/2, this.width, this.height);

// read back to temp buffer
this.filterGraphicsLayerTemp.image(pg, -this.width/2, -this.height/2);
tmp.image(pg, -this.width/2, -this.height/2);

// vert pass
this.filterShader.setUniform('direction', [0, 1]);
this.filterShader.setUniform('tex0', this.filterGraphicsLayerTemp);
this.filterShader.setUniform('tex0', tmp);
pg.rect(-this.width/2, -this.height/2, this.width, this.height);

this._pInst.pop();
Expand Down
43 changes: 36 additions & 7 deletions src/webgl/p5.Shader.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,42 @@ p5.Shader = class {
return this;
}

/**
* Shaders belong to the main canvas or a p5.Graphics. Once they are compiled,
* they can only be used in the context they were compiled on.
*
* Use this method to make a new copy of a shader that gets compiled on a
* different context.
*
* @method copyToContext
* @param {p5|p5.Graphics} context The graphic or instance to copy this shader to.
* Pass `window` if you need to copy to the main canvas.
* @returns {p5.Shader} A new shader on the target context.
*/
copyToContext(context) {
const shader = new p5.Shader(
context._renderer,
this._vertSrc,
this._fragSrc
);
shader.ensureCompiledOnContext(context);
return shader;
}

/**
* @private
*/
ensureCompiledOnContext(context) {
if (this._glProgram !== 0 && this._renderer !== context._renderer) {
throw new Error(
'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;
this.init();
}
}

/**
* Queries the active attributes for this shader and loads
* their names and locations into the attributes array.
Expand Down Expand Up @@ -392,13 +428,6 @@ p5.Shader = class {
* canvas toggles between a circular gradient of orange and blue vertically. and a circular gradient of red and green moving horizontally when mouse is clicked/pressed.
*/
setUniform(uniformName, data) {
// detect when to set uniforms on duplicate filter shader copy
let other = this._renderer.filterShader;
if (other !== undefined && other.parentShader === this) {
other.setUniform(uniformName, data);
return;
}

const uniform = this.uniforms[uniformName];
if (!uniform) {
return;
Expand Down
Loading