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

createFilterShader docs + fixes #6530

Closed
Closed
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
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
Loading