Pixi.js: PIXI fails to create a Filter from custom shader code

Created on 22 Nov 2017  ·  30Comments  ·  Source: pixijs/pixi.js

I am struggling to get a custom Filter with my own shader code working with this error message:

VertexArrayObject.js:171 Uncaught TypeError: Cannot read property 'location' of undefined
    at VertexArrayObject.addAttribute (VM1006 pixi.js:2348)
    at Quad.initVao (VM1006 pixi.js:19874)
    at FilterManager.applyFilter (VM1006 pixi.js:18947)
    at Filter.apply (VM1006 pixi.js:18420)
    at FilterManager.popFilter (VM1006 pixi.js:18877)
    at Container.renderAdvancedWebGL (VM1006 pixi.js:9423)
    at Container.renderWebGL (VM1006 pixi.js:9360)
    at Container.renderWebGL (VM1006 pixi.js:9366)
    at WebGLRenderer.render (VM1006 pixi.js:17563)
    at Application.render (VM1006 pixi.js:8043)

Even the official example is failing with this bug:
http://pixijs.io/examples/#/filters/filter-mouse.js

This error message has been related to some compiler optimizations where glslify removed some unused uniforms, which pixi still tried to access. But this even occurs with a completely static fragShader without any uniforms. gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0)

All 30 comments

The example works for me, what browser are you testing you in?

Google Chrome Version 62.0.3202.94 (Official Build) (64-bit)
2017-11-23-143829_667x172_scrot

Firefox Quantum 57.0 (64-bit)
2017-11-23-143957_741x236_scrot

OS: Ubuntu 17.10 artful

@doebi nice catch! Yeah, optimization removed something (I bet its sampler) and we forgot to check it.

For now, are you sure that you need filter without usage of sampler? In some cases its better to make renderer plugin like https://github.com/pixijs/pixi-plugin-example ?

Second time this day, Im sorry for the mess that shader and filters are in v4. We'll fix it in v5.

To be honest, i haven't really dug deep into this topic yet. Just wanted to experiment with that feature the other day. I will do some testing with the renderer plugin approach. Thanks for the hint.

Is there already some ETA for v5?

2 months or so :)

There are many tricks about filters, that's why I made that article: https://github.com/pixijs/pixi.js/wiki/v4-Creating-Filters

@ivanpopelyshev Following your tutorial, i ran into the same issue.
I applied a really hacky patch to pixi.js to make it work for me.

diff --git a/js/pixi.js b/js/pixi.js
index 363f09c..d0a321b 100644
--- a/js/pixi.js
+++ b/js/pixi.js
@@ -2344,6 +2344,9 @@ VertexArrayObject.prototype.activate = function()
  */
 VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start)
 {
+  if (!attribute) {
+    return this;
+  }
     this.attributes.push({
         buffer:     buffer,
         attribute:  attribute,

I know this is not a solution, but it enables me to play with shader codes in v4 until v5 is out. (with possibly a better fix) :)

That's a good hack!

You can move it into separate js file:

PIXI.glCore.VertexArrayObject.prototype.addAttribute = ...

I got the same problem. Also with Ubuntu 17.10 artful. Are shaders at all stable in Pixi.js? Can I use them in production? How do I import a pixel shader?

Either as filter either as renderer plugin. They are stable but require serious knowledge both about webgl and about pixi architecture.

https://github.com/pixijs/pixi.js/wiki/v4-Creating-Filters
https://github.com/pixijs/pixi-plugin-example/

For N-th time I assure people that it'll be easier in v5.

const filterCode = `void main(){
   gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}`;
const filter = new PIXI.Filter(null, filterCode);
someSprite.filters = [filter];

This much code leads to this error. Maybe I should go for a different constructor of the Filter? Or should I substitute null with something?

For a filter, you have to use texture coord and sampler , otherwise FilterManager fails to get location of attribute.

Renderer plugin doesnt have that requirement, try it. Yeah, its big boilerplate.

Try that thing: https://github.com/TazOen/createShaderPlugin . However it ignores the texture, there's no bindTexture there. If you need texture take whole pixi-plugin-example.

You say it need texture coords and "sampler". Was sample or sampler? A typo?
I managed to make it working with the help of example. Thank you but. Why doesn't this page mention these details?
http://pixijs.io/examples/#/basics/custom-filter.js

Sampler. However its uniform and I dont think somebody will miss it. Our problem is about attributes.

Thanks to you, it does :) https://github.com/pixijs/pixi.js/wiki/v4-Creating-Filters#cannot-read-property-location-of-undefined

Oh, so that's where it was. I missed the wiki.

Thanks to you, it does

I've added it just now.

Also added notice in filter-mouse demo. We cant fix the issue in v4. We'll do something about it in v5.

That appears when some of properties that are required by FilterManager are not used in the shader.

How about to list these properties in there?

only the attribute, aTextureCoord -> vTextureCoord

This is getting very strange.
This code works fine:

varying vec2 vTextureCoord;
varying vec4 vColor;

uniform sampler2D uSampler;
uniform vec4 uTextureClamp;
uniform vec4 uColor;

void main(void)
{
    gl_FragColor = texture2D(uSampler, vTextureCoord);
    gl_FragColor.r = 0.0;
    gl_FragColor.g = 0.0;
    gl_FragColor.b = 0.0;
}

But if I add gl_FragColor.a = 0.0; to the end then it says Cannot read property 'location' of undefined. It seems as though I can only change 3 coords out of 4 at most. What is this? What is wrong?

@germansokolov13 this behaviour makes total sense. Just think about it:
As i mentioned in the original post this error occurs due to glsify optimizing the shader code.

If you add gl_FragColor.a = 0.0; it can optimize your call to texture2D, cause it gets fully overwritten, but until then it takes alpha channel from texture2D

If glsify optimizes the call to texture2D, webgl does not allocate memory for uSampler, hence when pixi wants to upload data to that uniform it fails, cause there is simply no space for that allocated.

You got some options:

  • Apply the same hack as i did (for now), which basically checks if an attribute is allocated before uploading data to it.
  • Use uSampler, so glsify doesn't optimize it away.
  • Or just wait for v5.

Hopefully, this helps.

Nice catch, @doebi!

Or use renderer plugin.

Oh, now I get it! Maybe we should add Doebi's patch to the next minor release of Pixi.js? Should I create a PR?

Hi there,
I'm experiencing a similar problem with my shader code. It is a variation of the default BlurYFilter shader code just with a bigger kernel and a little variation on the gaussian variable, so I'm not entirely sure why it's not working.

It's working fine on Windows, MacOS and Android as far as I tested I only have issues in iOS.
Here's the shader code:
vertex:

attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
uniform float strength;
uniform mat3 projectionMatrix;
varying vec2 vBlurTexCoords[15];
void main(void){
   gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);
   vBlurTexCoords[0] = aTextureCoord + vec2(0.0, -7.0 * strength);
   vBlurTexCoords[1] = aTextureCoord + vec2(0.0, -6.0 * strength);
   vBlurTexCoords[2] = aTextureCoord + vec2(0.0, -5.0 * strength);
   vBlurTexCoords[3] = aTextureCoord + vec2(0.0, -4.0 * strength);
   vBlurTexCoords[4] = aTextureCoord + vec2(0.0, -3.0 * strength);
   vBlurTexCoords[5] = aTextureCoord + vec2(0.0, -2.0 * strength);
   vBlurTexCoords[6] = aTextureCoord + vec2(0.0, -1.0 * strength);
   vBlurTexCoords[7] = aTextureCoord + vec2(0.0, 0.0 * strength);
   vBlurTexCoords[8] = aTextureCoord + vec2(0.0, 1.0 * strength);
   vBlurTexCoords[9] = aTextureCoord + vec2(0.0, 2.0 * strength);
   vBlurTexCoords[10] = aTextureCoord + vec2(0.0, 3.0 * strength);
   vBlurTexCoords[11] = aTextureCoord + vec2(0.0, 4.0 * strength);
   vBlurTexCoords[12] = aTextureCoord + vec2(0.0, 5.0 * strength);
   vBlurTexCoords[13] = aTextureCoord + vec2(0.0, 6.0 * strength);
   vBlurTexCoords[14] = aTextureCoord + vec2(0.0, 7.0 * strength);
}

fragment:

varying vec2 vBlurTexCoords[15];
uniform sampler2D uSampler;
void main(void){
   gl_FragColor = vec4(0.0);
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[0]) * 0.013068780984604511;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[1]) * 0.013907007172070673;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[2]) * 0.017439264394216315;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[3]) * 0.028762309061254498;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[4]) * 0.05603114255667656;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[5]) * 0.10421702583793174;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[6]) * 0.163461199220823;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[7]) * 0.2062265415448454;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[8]) * 0.163461199220823;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[9]) * 0.10421702583793174;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[10]) * 0.05603114255667656;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[11]) * 0.028762309061254498;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[12]) * 0.017439264394216315;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[13]) * 0.013907007172070673;
   gl_FragColor += texture2D(uSampler, vBlurTexCoords[14]) * 0.013068780984604511;
}

for comparision here are the shaders of the default BlurYFilter:
vert:

attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
uniform float strength;
uniform mat3 projectionMatrix;
varying vec2 vBlurTexCoords[5];
void main(void)
{
  gl_Position = vec4((projectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0);
  vBlurTexCoords[0] = aTextureCoord + vec2(0.0, -2.0 * strength);
  vBlurTexCoords[1] = aTextureCoord + vec2(0.0, -1.0 * strength);
  vBlurTexCoords[2] = aTextureCoord + vec2(0.0, 0.0 * strength);
  vBlurTexCoords[3] = aTextureCoord + vec2(0.0, 1.0 * strength);
  vBlurTexCoords[4] = aTextureCoord + vec2(0.0, 2.0 * strength);

}

frag:

varying vec2 vBlurTexCoords[5];
uniform sampler2D uSampler;
void main(void)
{
    gl_FragColor = vec4(0.0);
    gl_FragColor += texture2D(uSampler, vBlurTexCoords[0]) * 0.153388;
    gl_FragColor += texture2D(uSampler, vBlurTexCoords[1]) * 0.221461;
    gl_FragColor += texture2D(uSampler, vBlurTexCoords[2]) * 0.250301;
    gl_FragColor += texture2D(uSampler, vBlurTexCoords[3]) * 0.221461;
    gl_FragColor += texture2D(uSampler, vBlurTexCoords[4]) * 0.153388;

}

Does Anybody have any ideas why this could go wrong?

@tyleet are you sure its the same issue? What do you see in the console?

EDIT:
Nevermind, I found the issue, it seems kernel size of 15 is too much for iOS, if I reduce kernel size to 7 it works fine.

<-----Original post--------->
Hi,
sorry for the late response, I was not able to make some screenshots over the weekend. It looks to me as if it fails on the same line. "attribute" is undefined. Here are the screenshots from the remote console:
image
image

@ivanpopelyshev shall we close this issue to prevent others from warming it up with similar issues?
I wouldn't consider it an issue anymore, since i got your promise, that it will be fixed in v5.

@doebi closing.

@tyleet the number of samples taken from texture can be limited on different systems.

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings