2

I'm trying webGL for the first time, thing is I am working on Expo with the expo-gl package, aiming to build a filter component for photo editing. So far, I have been able to create a context successfully. When creating the shaders everything works fine (I can render a triangle, two triangles, etc...) Problem comes when I try to load an image as a texture, I get no errors, but all I get is a black screen on the emulator and the phone (both android). I have done checks for powOf2 images and image.onload, and I did a console.log for gl.texImage2D but got undefined. I would really appreciate any insight or help for this issue. I divided into 4 parts what I think are the relevant pieces of code for this issue.

1.shaders(template literals):

    const vertexShaderSource = '
        attribute vec2 a_texCoord;
        attribute vec4 a_position;
        varying vec2 v_texCoord;
        void main() {
          gl_Position = a_position;
          v_texCoord = a_texCoord;
        }
        ';


    const fragmentShaderSource = '
        precision mediump float;
        uniform sampler2D u_image;
        varying vec2 v_texCoord;
        void main() {
          gl_FragColor = texture2D(u_image, v_texCoord);
        }
        ';

2.ReactNative(expo) state and lifecicle:

export default class App extends React.Component {

state = {
    ready: false,
    image: null,
};

componentDidMount() {
    (async () => {
        const image = Asset.fromModule(require('./bw.jpg'));
        await image.downloadAsync();
        this.setState({
            ready: true,
            image,
        });
    })();
}

render() {
    return (
        <View style={styles.container}>
            <Image source={require('./back.jpg')} style={{ width: '100%' }} />
            <GLView
                style={{ width: '100%', height: '100%', position: 'absolute' }}
                onContextCreate={this._onContextCreate}
            />
        </View>
    );
}

3.gl context:

_onContextCreate = gl => {
    if (_initialized) { return }
    function createShader(gl, type, source) {
      var shader = gl.createShader(type);
      gl.shaderSource(shader, source);
      gl.compileShader(shader);
      var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
      if (success) {
          return shader;
      }
      //on error
      console.log(gl.getShaderInfoLog(shader));
      gl.deleteShader(shader);
  }
  //get shaders
  var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
  var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

  function createProgram(gl, vertexShader, fragmentShader) {
      var program = gl.createProgram();
      gl.attachShader(program, vertexShader);
      gl.attachShader(program, fragmentShader);
      gl.linkProgram(program);
      //on error
      var success = gl.getProgramParameter(program, gl.LINK_STATUS);
      if (success) {
          return program;
      }
      console.log(gl.getProgramInfoLog(program));
      gl.deleteProgram(program);
  }
  //create program
  var program = createProgram(gl, vertexShader, fragmentShader);
  //get attributes
  var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
  //a_position buffer for fragment shader
  var positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  // three 2d points
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
      -1, -1,
      -1,  1,
       1, -1,
       1,  1,
       1, -1,
      -1,  1,
  ]), gl.STATIC_DRAW);

  //create the viewport
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
  // Clear the canvas
  gl.clearColor(0.0, 0.0, 0.0, 0.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  // use program 
  gl.useProgram(program);
  gl.enableVertexAttribArray(positionAttributeLocation);        
  gl.vertexAttribPointer(
      positionAttributeLocation, 2, gl.FLOAT, false, 0, 0)
  gl.drawArrays(gl.TRIANGLES, 0, 6);

4.Code for the texture, and end of _onContextCreate:

    //get image from state
    const image = this.state.image

    var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");

    var uSampler = gl.getUniformLocation(program, 'u_image');

    // provide texture coordinates for the rectangle.
    var texCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
    var positions = [
        -1,-1,
        -1, 1,
         1,-1,
         1, 1,
         1,-1,
        -1, 1,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
    gl.enableVertexAttribArray(texCoordLocation);
    gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

    //create texture
    var texture = gl.createTexture();
    //check if image is ready
    if (image.downloaded) loadTexture(texture, image)
    //get image width & height for pow2 check.
    const { width, height } = Image.resolveAssetSource(image);
    function loadTexture(texture, img) {
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.uniform1i(uSampler, 0);
        //pow2 check
        if (isPowerOf2(width) && isPowerOf2(height)) {
            gl.generateMipmap(gl.TEXTURE_2D);
        } else {
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
        }
        return texture
    }
    function isPowerOf2(value) {
        return (value & (value - 1)) == 0;
    }

    gl.flush();
    gl.endFrameEXP();
    _initialized = true;
};

Thanks in advance!

  • The only thing that sticks out in your code as is is you spelled `image` at the top but `img` at the bottom. Add `"use strict";` at the top of your JavaScript and it might help find that kind of error, assuming that's the issue. Otherwise you haven't shown enough code. – gman Sep 23 '19 at 18:02
  • I made the variable image and the param img to make the diference stand out, because when the image is not passed correctly to texImage2D, it throws an error. I created a pen to paste all the code in there, because there is a lot, hope it helps. https://codepen.io/valdezcervera/pen/rNBPjxr Thanks for your help! – César Valdez Sep 23 '19 at 19:40
  • Stack Overflow requires you to put a minimal reproducable example in the question itself. That means if we have to go cover to codepen to figure out the problem then your question is off topic for stack overflow. Can you try to make a minimal repo and maybe post it in a [snippet](https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/)? – gman Sep 23 '19 at 20:26
  • Hi @gman, sorry for the late reply. I edited the post to add what I think is the most relevant parts of the code. I apologize for the inconvenience. – César Valdez Sep 24 '19 at 08:30
  • @gman, also wanted to point out that all this time I have been following mdn docs, and https://webglfundamentals.org as well (amazing work by the way, it has been super helpful!!). Everything went well while I was in the 'webgl fundamentals' chapter, it is now that Im on the 'WebGL Image Processing' part, where I get the black screen when loading a texture. – César Valdez Sep 24 '19 at 09:31
  • Sorry the code above is just way too out of context. I have no idea what things like `Image.resolveAssetSource` do nor is it clear from the code you posted that `image` has actually finished loading when you called `loadImage`. Other random things, your code says its setting up texcoords but you have `positions` being passed into `bufferData`. Whether that's correct or not depends on the code but you don't show that code. Don't see where the positions are setup. If you have a typo from uniform names or attribute names that's a common source of silent error. – gman Sep 24 '19 at 10:20
  • Hi @gman, thank you so much for your comments. Image.resolveAssetSource is just getting the image dimensions so I can later use them in the pow2 check. Regarding the image,to avoid not having the image on time, I first get it into react state during the lifecicle (componentDidMount) to make sure its available before anything else. Like this, I can now simply point image to this.state.image and pass it. Sorry for not showing the buffer cord, it escaped me. Im using a variable be able to re use it. var positions = [-1,-1,-1, 1,1,-1,1, 1,1,-1,-1, 1,]; – César Valdez Sep 24 '19 at 10:45
  • If you're resuing the positions they aren't correct for texcoords and your shader isn't taking that into account. As for `componentDidMount` that doesn't tell you the image is read to use, it only tells you React created an `` tag. – gman Sep 24 '19 at 10:56
  • @gman, I just added the remaining code, I got rid of the positions variable and I am now puting the array directly. My question is, what would be a correct coordinate por textcoord's? Im lost in regard of what I should do instead. As for the image, I am now checking for image.downloaded property before calling the function. Hope Im closer to a solution now =) – César Valdez Sep 24 '19 at 12:32
  • I have no idea how your code is supposed to work. The top half compiles shader, setup position, then draw. The second half sets up texture coords and loads a texture. The two pieces of code appear unelated to each other. I'd suggest you get something to run outside of React then put it back in once it works outside. – gman Sep 25 '19 at 09:38
  • @gman Ok I'll do that and see how it goes, thak's for your advice! – César Valdez Sep 25 '19 at 12:24

0 Answers0