Skip to content

Commit

Permalink
createFilterShader docs + fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
davepagurek committed Nov 6, 2023
1 parent 17304ce commit f0b1752
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 31 deletions.
22 changes: 22 additions & 0 deletions src/core/p5.Renderer2D.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@ class Renderer2D extends p5.Renderer{
this._pInst._setProperty('drawingContext', this.drawingContext);
}

getFilterGraphicsLayer() {
// create hidden webgl renderer if it doesn't exist
if (!this.filterGraphicsLayer) {
// the real _pInst is buried when this is a secondary p5.Graphics
const pInst =
this._pInst instanceof p5.Graphics ?
this._pInst._pInst :
this._pInst;

// create secondary layer
this.filterGraphicsLayer =
new p5.Graphics(
this.width,
this.height,
constants.WEBGL,
pInst
);
}

return this.filterGraphicsLayer;
}

_applyDefaults() {
this._cachedFillStyle = this._cachedStrokeStyle = undefined;
this._cachedBlendMode = constants.BLEND;
Expand Down
38 changes: 15 additions & 23 deletions src/image/pixels.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import p5 from '../core/main';
import Filters from './filters';
import '../color/p5.Color';
import * as constants from '../core/constants';

/**
* An array containing the color of each pixel on the canvas. Colors are
Expand Down Expand Up @@ -544,6 +543,15 @@ p5.prototype._copyHelper = (
* </div>
*/

/**
* @method getFilterGraphicsLayer
* @private
* @returns {p5.Graphics}
*/
p5.prototype.getFilterGraphicsLayer = function() {
return this._renderer.getFilterGraphicsLayer();
};

/**
* @method filter
* @param {Constant} filterType
Expand All @@ -560,7 +568,7 @@ p5.prototype.filter = function(...args) {
let { shader, operation, value, useWebGL } = parseFilterArgs(...args);

// when passed a shader, use it directly
if (shader) {
if (this._renderer.isP3D && shader) {
p5.RendererGL.prototype.filter.call(this._renderer, shader);
return;
}
Expand All @@ -586,27 +594,11 @@ p5.prototype.filter = function(...args) {

// when this is P2D renderer, create/use hidden webgl renderer
else {
// create hidden webgl renderer if it doesn't exist
if (!this.filterGraphicsLayer) {
// the real _pInst is buried when this is a secondary p5.Graphics
const pInst =
this._renderer._pInst instanceof p5.Graphics ?
this._renderer._pInst._pInst :
this._renderer._pInst;

// create secondary layer
this.filterGraphicsLayer =
new p5.Graphics(
this.width,
this.height,
constants.WEBGL,
pInst
);
}
const filterGraphicsLayer = this.getFilterGraphicsLayer();

// copy p2d canvas contents to secondary webgl renderer
// dest
this.filterGraphicsLayer.copy(
filterGraphicsLayer.copy(
// src
this._renderer,
// src coods
Expand All @@ -617,11 +609,11 @@ p5.prototype.filter = function(...args) {
//clearing the main canvas
this._renderer.clear();
// filter it with shaders
this.filterGraphicsLayer.filter(operation, value);
filterGraphicsLayer.filter(...args);

// copy secondary webgl renderer back to original p2d canvas
this._renderer._pInst.image(this.filterGraphicsLayer, 0, 0);
this.filterGraphicsLayer.clear(); // prevent feedback effects on p2d canvas
this._renderer._pInst.image(filterGraphicsLayer, 0, 0);
filterGraphicsLayer.clear(); // prevent feedback effects on p2d canvas
}
};

Expand Down
19 changes: 14 additions & 5 deletions src/webgl/material.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,13 @@ p5.prototype.createShader = function(vertSrc, fragSrc) {
* Creates a new <a href="#/p5.Shader">p5.Shader</a> using only a fragment shader, as a convenience method for creating image effects.
* It's like <a href="#/createShader">createShader()</a> but with a default vertex shader included.
*
* <a href="#/createFilterShader">createFilterShader()</a> is intended to be used along with <a href="#/filter">filter()</a> for filtering the contents of a canvas in WebGL mode.
* <a href="#/createFilterShader">createFilterShader()</a> is intended to be used along with <a href="#/filter">filter()</a> for filtering the contents of a canvas.
* A filter shader will not be applied to any geometries.
*
* The fragment shader receives some uniforms:
* - `sampler2D tex0`, which contains the canvas contents as a texture
* - `vec2 canvasSize`, which is the width and height of the canvas
* - `vec2 texelSize`, which is the size of a pixel (`1.0/width`, `1.0/height`)
* - `vec2 canvasSize`, which is the p5 width and height of the canvas (not including pixel density)
* - `vec2 texelSize`, which is the size of a physical pixel including pixel density (`1.0/(width*density)`, `1.0/(height*density)`)
*
* For more info about filters and shaders, see Adam Ferriss' <a href="https://github.com/aferriss/p5jsShaderExamples">repo of shader examples</a>
* or the <a href="https://p5js.org/learn/getting-started-in-webgl-shaders.html">introduction to shaders</a> page.
Expand Down Expand Up @@ -278,7 +278,6 @@ p5.prototype.createShader = function(vertSrc, fragSrc) {
* </div>
*/
p5.prototype.createFilterShader = function(fragSrc) {
this._assert3d('createFilterShader');
p5._validateParameters('createFilterShader', arguments);
let defaultVertV1 = `
uniform mat4 uModelViewMatrix;
Expand Down Expand Up @@ -322,7 +321,17 @@ p5.prototype.createFilterShader = function(fragSrc) {
`;
let vertSrc = fragSrc.includes('#version 300 es') ? defaultVertV2 : defaultVertV1;
const shader = new p5.Shader(this._renderer, vertSrc, fragSrc);
shader.ensureCompiledOnContext(this._renderer.getFilterGraphicsLayer());
if (this._renderer.isP3D) {
shader.ensureCompiledOnContext(this._renderer.getFilterGraphicsLayer());
} else {
// In 2D mode, the image is copied to a WebGL p5.Graphics, and then the
// filter is applied there, which has its own graphic for running the
// shader. This may be simplified in the future by using framebuffers on
// a WebGL canvas instead of separate graphics.
shader.ensureCompiledOnContext(
this._renderer.getFilterGraphicsLayer().getFilterGraphicsLayer()
);
}
return shader;
};

Expand Down
12 changes: 12 additions & 0 deletions src/webgl/p5.Shader.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,18 @@ p5.Shader = class {
* @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.
*
* @example
* <div class='norender notest'>
* <code>
* let graphic = createGraphics(200, 200, WEBGL);
* let graphicShader = graphic.createShader(vert, frag);
* graphic.shader(graphicShader); // Use graphicShader on the graphic
*
* let mainShader = graphicShader.copyToContext(window);
* shader(mainShader); // Use `mainShader` on the main canvas
* </code>
* </div>
*/
copyToContext(context) {
const shader = new p5.Shader(
Expand Down
44 changes: 41 additions & 3 deletions test/unit/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ suite('p5.RendererGL', function() {
});

teardown(function() {
//myp5.remove();
myp5.remove();
});

suite('createCanvas(w, h, WEBGL)', function() {
Expand Down Expand Up @@ -163,6 +163,44 @@ suite('p5.RendererGL', function() {
};
});

suite('custom shaders', function() {
function testFilterShader(target) {
const fragSrc = `precision highp float;
void main() {
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}`;
const s = target.createFilterShader(fragSrc);
target.filter(s);
target.loadPixels();
assert.deepEqual(
target.get(target.width/2, target.height/2),
[255, 255, 0, 255]
);
}

test('work with a 2D main canvas', function() {
myp5.createCanvas(10, 10);
testFilterShader(myp5);
});

test('work with a WebGL main canvas', function() {
myp5.createCanvas(10, 10, myp5.WEBGL);
testFilterShader(myp5);
});

test('work with a 2D graphic', function() {
myp5.createCanvas(10, 10);
const graphic = myp5.createGraphics(10, 10);
testFilterShader(graphic);
});

test('work with a WebGL graphic', function() {
myp5.createCanvas(10, 10);
const graphic = myp5.createGraphics(10, 10, myp5.WEBGL);
testFilterShader(graphic);
});
});

test('filter accepts correct params', function() {
myp5.createCanvas(5, 5, myp5.WEBGL);
let s = myp5.createShader(vert, frag);
Expand Down Expand Up @@ -293,13 +331,13 @@ suite('p5.RendererGL', function() {
test('filter() uses WEBGL implementation behind main P2D canvas', function() {
let renderer = myp5.createCanvas(3,3);
myp5.filter(myp5.BLUR);
assert.isDefined(renderer._pInst.filterGraphicsLayer);
assert.isDefined(renderer.filterGraphicsLayer);
});

test('filter() can opt out of WEBGL implementation', function() {
let renderer = myp5.createCanvas(3,3);
myp5.filter(myp5.BLUR, useWebGL=false);
assert.isUndefined(renderer._pInst.filterGraphicsLayer);
assert.isUndefined(renderer.filterGraphicsLayer);
});

test('filters make changes to canvas', function() {
Expand Down

0 comments on commit f0b1752

Please sign in to comment.