gre/gl-react

Android shader compilation failure [Android 8, Android 9]

Shamash2014 opened this issue · 7 comments

Bug reports

[Sat Sep 19 2020 08:41:43.614]  ERROR    [GLError: gl-shader: Error compiling shader:
Compile failed.
ERROR: 0:1: '' :   Invalid directive 14
ERROR: 1 compilation errors. No code generated.

] 

Also Invalid directive changes from 10 to 14
Example code:

//@flow
import React, { PureComponent, Component } from "react";
import { Shaders, Node, GLSL, Bus } from "gl-react";
import { Surface } from "../../gl-react-implementation";
import marioPNG from "../../images/pixeleditor-mario.png";
import respondToTouchPosition from "../../HOC/respondToTouchPosition";

type Vec2 = [number, number];

const shaders = Shaders.create({
  paint: {
    frag: GLSL`
precision highp float;
varying vec2 uv;
uniform vec4 color;
uniform vec2 size, mouse;
uniform float brushRadius;
uniform bool drawing;
void main() {
  vec2 p = floor(uv * size) / size;
  if (drawing) {
    vec2 d = uv - mouse;
    if (length(d) < brushRadius) {
      gl_FragColor = color;
    }
    else {
      discard;
    }
  }
  else {
    discard;
  }
}`
  },
  initTexture: {
    frag: GLSL`
precision highp float;
varying vec2 uv;
uniform sampler2D t;
void main(){
  gl_FragColor=texture2D(t,uv);
}`
  },
  pixelEditor: {
    frag: GLSL`
precision highp float;
varying vec2 uv;
uniform vec4 color;
uniform vec2 size, mouse, gridBorder;
uniform float brushRadius;
uniform sampler2D t;
void main() {
  vec2 p = floor(uv * size) / size;
  vec2 remain = mod(uv * size, vec2(1.0));
  float m =
    step(remain.x, 1.0 - gridBorder.x) *
    step(remain.y, 1.0 - gridBorder.y);
  float inBrushSize =
    step(length(p + (0.5 / size) - mouse), brushRadius);
  vec4 c = mix(texture2D(t, uv), color, 0.6 * inBrushSize);
  gl_FragColor = vec4(
    m * c.rgb,
    mix(1.0, c.a, m));
}`
  }
});

class Paint extends PureComponent {
  state = {
    initialized: false
  };
  onDraw = () => {
    if (!this.state.initialized) {
      this.setState({ initialized: true });
    }
  };
  render() {
    const { initialTexture, onPaintNodeRef, ...rest } = this.props;
    const { initialized } = this.state;
    return (
      <Node
        ref={onPaintNodeRef}
        sync={!initialized}
        shader={!initialized ? shaders.initTexture : shaders.paint}
        width={rest.size[0]}
        height={rest.size[1]}
        uniforms={!initialized ? { t: initialTexture } : rest}
        clear={null}
        onDraw={this.onDraw}
      />
    );
  }
}

class PixelEditor extends PureComponent {
  render() {
    const { gridBorder, ...rest } = this.props;
    const { size, brushRadius, mouse, color } = rest;
    return (
      <Node
        shader={shaders.pixelEditor}
        uniformsOptions={{
          t: { interpolation: "nearest" }
        }}
        uniforms={{
          size,
          gridBorder,
          brushRadius,
          mouse,
          color
        }}
      >
        <Bus uniform="t">
          <Paint {...rest} />
        </Bus>
      </Node>
    );
  }
}

const size = [16, 16];
const gridBorder = [1 / 8, 1 / 8];
const tools = {
  "brush-1": { brushRadius: 0.55 / 16 },
  "brush-2": { brushRadius: 1.1 / 16 },
  "brush-4": { brushRadius: 2.2 / 16 },
  rubber: { brushRadius: 4 / 16, forceColor: [0, 0, 0, 0] },
  "color-picker": { colorPick: true }
};

const Example = respondToTouchPosition(
  class extends Component {
    render() {
      const { color, toolKey, touching, touchPosition, width } = this.props;
      const tool = tools[toolKey];
      return (
        <Surface
          style={{ width, height: width }}
          preload={[marioPNG]}
          webglContextAttributes={{ preserveDrawingBuffer: true }}
        >
          <PixelEditor
            gridBorder={gridBorder}
            initialTexture={marioPNG}
            drawing={touching}
            color={tool.forceColor || color}
            mouse={[touchPosition.x, touchPosition.y]}
            brushRadius={tool.brushRadius || 0}
            size={size}
            onPaintNodeRef={this.onPaintNodeRef}
          />
        </Surface>
      );
    }

    onColorChange = rgb => {
      this.props.setToolState({ color: rgb.concat([1]) });
    };

    paintNode: Node;
    onPaintNodeRef = (ref: Node) => {
      this.paintNode = ref;
    };

    colorPick = ([x, y]: Vec2) => {
      x = Math.floor(x * size[0]);
      y = Math.floor(y * size[1]);
      const ndarray = this.paintNode.capture(x, y, 1, 1);
      this.props.setToolState({
        color: Array.from(ndarray.data).map(n => n / 255)
      });
    };
  }
);

Example.defaultProps = {
  color: [1, 1, 1, 1],
  toolKey: "brush-4"
};

export default Example;

export { tools };

(Example is pixel editor from cookbook)

library version

Unimodules with expo is used on top of bare React Native

├── gl-react@4.0.1 
└─┬ gl-react-native@4.0.1 
  └── gl-react-expo@4.0.1 

Expected behavior

  1. Shaders successfully compiled

Actual behavior

  1. On certain mobile phones shaders are not compiled and causing a crash

NB!: Seems like GLImage from gl-react-image actually works correctly

Steps to reproduce the behavior

  1. Open the screen with Pixel Editor

In this repo watched?

gre commented

Sorry for the late response. I was a bit busy this year (and not a lot of contributions on gl-react yet 🙏 )
I will do a big pass on all github issues. Is this still happening to you? I fail to see where the error is in your GLSL code.
I plan to upgrade to GLSL ES 3.0 too which maybe will help resolving this.

gre commented

BTW this is dup with #248 , keeping the older issue opened.

Hi yes it still reproducible, but I found a workaround by not using a Shaders.create for creating a shader. With this call it may fail on android 8 or 9. but once this shader caching is removed - it actually works

gre commented

interesting. so you mean by creating the shader in the <Node> directly?

@gre Let me give you an example of the code which worked for me.

This one won`t work on certain phones.

const shaders = Shaders.create({
  paint: {
    frag: GLSL`
precision highp float;
varying vec2 uv;
uniform vec4 color;
uniform vec2 size, mouse;
uniform float brushRadius;
uniform bool drawing;
void main() {
  vec2 p = floor(uv * size) / size;
  if (drawing) {
    vec2 d = uv - mouse;
    if (length(d) < brushRadius) {
      gl_FragColor = color;
    }
    else {
      discard;
    }
  }
  else {
    discard;
  }
}`
  },
  initTexture: {
    frag: GLSL`
precision highp float;
varying vec2 uv;
uniform sampler2D t;
void main(){
  gl_FragColor=texture2D(t,uv);
}`
  },
  pixelEditor: {
    frag: GLSL`
precision highp float;
varying vec2 uv;
uniform vec4 color;
uniform vec2 size, mouse, gridBorder;
uniform float brushRadius;
uniform sampler2D t;
void main() {
  vec2 p = floor(uv * size) / size;
  vec2 remain = mod(uv * size, vec2(1.0));
  float m =
    step(remain.x, 1.0 - gridBorder.x) *
    step(remain.y, 1.0 - gridBorder.y);
  float inBrushSize =
    step(length(p + (0.5 / size) - mouse), brushRadius);
  vec4 c = mix(texture2D(t, uv), color, 0.6 * inBrushSize);
  gl_FragColor = vec4(
    m * c.rgb,
    mix(1.0, c.a, m));
}`
  }
});

This one is actually working fine, even on android 7 or some low tier tablets

const shaders = {
  paint: {
    frag: GLSL`
precision highp float;
varying vec2 uv;
uniform vec4 color;
uniform vec2 size, mouse;
uniform float brushRadius;
uniform bool drawing;
void main() {
  vec2 p = floor(uv * size) / size;
  if (drawing) {
    vec2 d = uv - mouse;
    if (length(d) < brushRadius) {
      gl_FragColor = color;
    }
    else {
      discard;
    }
  }
  else {
    discard;
  }
}`
  },
  initTexture: {
    frag: GLSL`
precision highp float;
varying vec2 uv;
uniform sampler2D t;
void main(){
  gl_FragColor=texture2D(t,uv);
}`
  },
  pixelEditor: {
    frag: GLSL`
precision highp float;
varying vec2 uv;
uniform vec4 color;
uniform vec2 size, mouse, gridBorder;
uniform float brushRadius;
uniform sampler2D t;
void main() {
  vec2 p = floor(uv * size) / size;
  vec2 remain = mod(uv * size, vec2(1.0));
  float m =
    step(remain.x, 1.0 - gridBorder.x) *
    step(remain.y, 1.0 - gridBorder.y);
  float inBrushSize =
    step(length(p + (0.5 / size) - mouse), brushRadius);
  vec4 c = mix(texture2D(t, uv), color, 0.6 * inBrushSize);
  gl_FragColor = vec4(
    m * c.rgb,
    mix(1.0, c.a, m));
}`
  }
};

And than basically shader is executed on the Node. I haven't found any significant performance problem with this approach as well :)