Shader breaking when used with createGraphics

Hello :smiling_face:

I’ve ran into some trouble trying to use shaders in conjunction with createGraphics. The shader works fine drawing to the main canvas, and can be used within a createGraphics canvas once, however after that it breaks and can’t be used anywhere (within the same canvas, another createGraphics canvas, the main canvas – even if I loop() the sketch).

I’ve pasted the code below. For testing purposes it draws 4 circles onto the canvas, which the fragment shader should turn red. In my example I have illustrated the problem by initialising two identical shaders (‘shaderTest’ and ‘shaderTest2’) which each draw 2 circles. You can see that, ‘shaderTest’ stops working after it is used once, whereas ‘shaderTest2’ is used twice with no issue (therefore, only 3 out of the 4 circles are drawn). The only difference I can see is that I use ‘shaderTest’ within a createGraphics canvas, and I use ‘shaderTest2’ within the main canvas.

let shaderTest;
let shaderTest2;

function preload() {
  //These are the same shader (turns what image red)
  shaderTest = loadShader("basic.vert", "basic.frag");
  shaderTest2 = loadShader("basic.vert", "basic.frag");
}

function setup() {
  createCanvas(400, 400, WEBGL);
  noLoop();
}

function draw() {
  translate(-width / 2, -height / 2); //Put origin back to 0,0
  fill(0);
  noStroke();
  background(220);

  //Draw first circle
  cnv = createGraphics(width, height, WEBGL);
  cnv.translate(-width / 2, -height / 2);
  cnv.fill(0);
  cnv.noStroke();
  cnv.shader(shaderTest); //Uses first shader
  cnv.ellipse(200, 50, 50, 50);
  cnv.resetShader();

  image(cnv, 0, 0);

  //Draw second circle
  cnv2 = createGraphics(width, height, WEBGL);
  cnv2.translate(-width / 2, -height / 2);
  cnv.fill(0);
  cnv2.noStroke();
  cnv2.shader(shaderTest); //Uses first shader again, but...
  cnv2.ellipse(200, 150, 50, 50);
  cnv2.resetShader();

  image(cnv2, 0, 0); //This doesn't show up!!

  //Draw third circle
  shader(shaderTest2);
  ellipse(200, 250, 50, 50);
  resetShader();

  //Draw fourth circle
  shader(shaderTest2);
  ellipse(200, 350, 50, 50);
  resetShader();
}

function mouseClicked() {
  loop();
  noLoop();
}

Here is the vertex shader:

#ifdef GL_ES
precision highp float;
#endif

attribute vec3 aPosition;
attribute vec2 aTexCoord;
attribute vec4 aVertexColor;

uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;

varying vec2 vTexCoord;
varying vec4 vVertexColor;

void main() {
    // Apply the camera transform
    vec4 viewModelPosition = uModelViewMatrix * vec4(aPosition, 1.0);

    // Tell WebGL where the vertex goes
    gl_Position = uProjectionMatrix * viewModelPosition;  

    // Pass along data to the fragment shader
    vTexCoord = aTexCoord;
    vVertexColor = aVertexColor;
}

Here is the fragment shader:

#ifdef GL_ES
precision highp float;
#endif

void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

I’ve tried a few things (using different shaders, clearing the canvases, initialising them in setup rather than draw, using the shader within the same createGraphics canvas, etc), but after messing around with this for hours, reading all the p5 js documentation I can find and searching the web for similar issues, I’ve come up blank.

I’m relatively new to using shaders, so there’s probably something obvious I’ve missed. Or I may be trying to do something that is not possible without realising it. Either way I’d be extremely grateful for some guidance on this. If it would help to have a p5 js editor version up and running, you can find a link here: p5.js Web Editor. Likewise, if I’ve not explained this issue well, I’m happy to rephrase if it helps (or to explain what I’m trying to achieve in my main project).

Thanks for your time!

Hi @slone,

There are several issues in your code. I would recommend to create the Graphics once in setup and re-use it, instead always create new ones in draw.

The major issue regarding the shaders comes from the restriction, that a shader belongs to either the main graphic context or an individual one and can’t be shared.
Read the reference for p5.Shader.

If you want to use the same shader in two graphic context you can use the method copyToContext(ctx) described in the reference. To use this you need to use p5 version 1.8.0, instead the currently 1.7.0 used in your example code above.

Cheers
— mnse

3 Likes

Hi @mnse,

Thank you so much for the helpful response! I’m a bit embarrassed to have missed this reference page, but it makes complete sense why my code doesn’t work now.

Best,

Slone

1 Like