42

I am trying to filter the stream coming from the camera hardware by running it through an openGL filter, then displaying it in a GLSurfaceView. When openGL goes to render the frame, the LogCat repeatedly spits out an error:

[unnamed-3314-0] updateTexImage: clearing GL error: 0x502

0x502 is a generic openGL error, and doesn't really help me track down the problem. This is a sequence of how the code works (or atleast should be working as seen in my head), and I've copied my code below that. I am hoping that somebody else can see what my problem is.

  1. Create new MyGLSurfaceView. This internally creates the new MyGL20Renderer object as well. This MyGLSurfaceView is set as the content view.
  2. Once the MyGLSurfaceView is done inflating/initializing, this completion event triggers the renderer to create a DirectVideo draw object, which compiles/links the shaders defined and adds them to an openGL program. It then creates a new openGL texture object, and then calls back to the MainActivity with the texture object ID.
  3. When the MainActivity method is invoked from the renderer, it creates a new SurfaceTexture object using the openGL texture object passed. It then sets itself as the surface's onFrameListener. It then creates/opens the camera object, sets the created SurfaceTexture as the video stream's target, and starts the camera feed.
  4. When a frame is available from the feed, the onFrameAvailable sends a render request to the renderer. This is picked up on the openGL thread, which calls the SurfaceTexture's updateTexImage(), which loads the frame memory into the openGL texture. It then calls the DirectVideo's draw object, and the openGL program sequence is run. If I comment out this .draw() line, the mentioned error above disappears, so it seems likely that the problem lies somewhere inside here, but I am not ruling it out being caused by an improperly linked/created texture.

MainActivity.java

public class MainActivity extends Activity implements SurfaceTexture.OnFrameAvailableListener
{
    private Camera mCamera;
    private MyGLSurfaceView glSurfaceView;
    private SurfaceTexture surface;
    MyGL20Renderer renderer;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        glSurfaceView = new MyGLSurfaceView(this);
        renderer = glSurfaceView.getRenderer();
        setContentView(glSurfaceView);
    }

    public void startCamera(int texture)
    {
        surface = new SurfaceTexture(texture);
        surface.setOnFrameAvailableListener(this);
        renderer.setSurface(surface);

        mCamera = Camera.open();

        try
        {
            mCamera.setPreviewTexture(surface);
            mCamera.startPreview();
        }
        catch (IOException ioe)
        {
            Log.w("MainActivity","CAM LAUNCH FAILED");
        }
    }

    public void onFrameAvailable(SurfaceTexture surfaceTexture)
    {
        glSurfaceView.requestRender();
    }

    @Override
    public void onPause()
    {
        mCamera.stopPreview();
        mCamera.release();
        System.exit(0);
    }

MyGLSurfaceView.java

class MyGLSurfaceView extends GLSurfaceView
{
    MyGL20Renderer renderer;
    public MyGLSurfaceView(Context context)
    {
        super(context);

        setEGLContextClientVersion(2);

        renderer = new MyGL20Renderer((MainActivity)context);
        setRenderer(renderer);
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

    }
    public MyGL20Renderer getRenderer()
    {
        return renderer;
    }
}

MyGL20Renderer.java

public class MyGL20Renderer implements GLSurfaceView.Renderer
{

    DirectVideo mDirectVideo;
    int texture;
    private SurfaceTexture surface;
    MainActivity delegate;

    public MyGL20Renderer(MainActivity _delegate)
    {
        delegate = _delegate;
    }

    public void onSurfaceCreated(GL10 unused, EGLConfig config)
    {
        mDirectVideo = new DirectVideo(texture);
        texture = createTexture();
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
        delegate.startCamera(texture);
    }

    public void onDrawFrame(GL10 unused)
    {
            float[] mtx = new float[16];
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
            surface.updateTexImage();
            surface.getTransformMatrix(mtx); 

            mDirectVideo.draw();
    }

    public void onSurfaceChanged(GL10 unused, int width, int height)
    {
        GLES20.glViewport(0, 0, width, height);
    }

    static public int loadShader(int type, String shaderCode)
    {
        int shader = GLES20.glCreateShader(type);

        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }

    static private int createTexture()
    {
        int[] texture = new int[1];

        GLES20.glGenTextures(1,texture, 0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
             GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);        
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
             GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
     GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
             GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
     GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
             GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);

        return texture[0];
    }

    public void setSurface(SurfaceTexture _surface)
    {
        surface = _surface;
    }
}

DirectVideo.java

public class DirectVideo {


    private final String vertexShaderCode =
            "#extension GL_OES_EGL_image_external : require\n"+
            "attribute vec4 position;" +
            "attribute vec4 inputTextureCoordinate;" +
            "varying vec2 textureCoordinate;" +
            "void main()" +
            "{"+
                "gl_Position = position;"+
                "textureCoordinate = inputTextureCoordinate.xy;" +
            "}";

        private final String fragmentShaderCode =
            "#extension GL_OES_EGL_image_external : require\n"+
            "precision mediump float;" +
            "uniform vec4 vColor;" +
            "void main() {" +
            "  gl_FragColor = vColor;" +
            "}";

        private FloatBuffer vertexBuffer, textureVerticesBuffer;
        private ShortBuffer drawListBuffer;
         private final int mProgram;
            private int mPositionHandle;
            private int mColorHandle;
            private int mTextureCoordHandle;


    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 2;
    static float squareVertices[] = { // in counterclockwise order:
         -1.0f,  1.0f,
         -1.0f,  -1.0f,
         1.0f,  -1.0f,
         1.0f,  1.0f
    };

    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices

    static float textureVertices[] = { // in counterclockwise order:
        1.0f,  1.0f,
        1.0f,  0.0f,
        0.0f,  1.0f,
        0.0f,  0.0f
   };

    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    private int texture;

    public DirectVideo(int _texture)
    {
        texture = _texture;

        ByteBuffer bb = ByteBuffer.allocateDirect(squareVertices.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareVertices);
        vertexBuffer.position(0);

        ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);

        ByteBuffer bb2 = ByteBuffer.allocateDirect(textureVertices.length * 4);
        bb2.order(ByteOrder.nativeOrder());
        textureVerticesBuffer = bb2.asFloatBuffer();
        textureVerticesBuffer.put(textureVertices);
        textureVerticesBuffer.position(0);

        int vertexShader = MyGL20Renderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
        int fragmentShader = MyGL20Renderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

        mProgram = GLES20.glCreateProgram();             // create empty OpenGL ES Program
        GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program
        GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
        GLES20.glLinkProgram(mProgram);          
    }

    public void draw()
    {
        GLES20.glUseProgram(mProgram);

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture);

        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "position");
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,vertexStride, vertexBuffer);

        mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");
        GLES20.glEnableVertexAttribArray(mTextureCoordHandle);
        GLES20.glVertexAttribPointer(mTextureCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,vertexStride, textureVerticesBuffer);

        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
        GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(mPositionHandle);
        GLES20.glDisableVertexAttribArray(mTextureCoordHandle);
    }
}
Dan Collins
  • 1,018
  • 1
  • 11
  • 20
  • 1
    502 is `GL_INVALID_OPERATION`. Can you try explicitly checking for error before calling updateTexImage? `Assert.assertTrue(GLES20.glGetError() == 0);` It's possible that something you've done is causing the error, and it doesn't get queried until updateTexImage calls it. It's not clear from that error message if that error is caused by updateTexImage, or if it's a pending error from before. – Tim Sep 20 '12 at 19:13
  • Indeed, you are correct. The draw function runs fine during the initialization, but on the first draw() call made when the first onFrameAvailable makes a render request, the ASSERT fails. – Dan Collins Sep 20 '12 at 19:30
  • 3
    You'll have to sprinkle asserts throughout your draw function until you find the OpenGL call that is causing the error. – Tim Sep 20 '12 at 19:40
  • Actually playing around with this further; assuming that glGetError resets the "error" to 0, The problem may be in the draw function. I put debug outputs conditional to there being no error when the onDrawFrame is called before the updateTexImage() call, after the updateTexImage() call but before the DirectVideo.draw() call, and after the DirectVideo.draw() call. The first two pass with no error, but the third doesn't. So, it seems like the behavior I posted previously was the residual error from the previous frame render triggering the Assert. – Dan Collins Sep 20 '12 at 19:46
  • Okay I will try that and see what shows up. Thanks! – Dan Collins Sep 20 '12 at 19:46
  • So I have found that the #extension require that is needed because of the GL_TEXTURE_EXTERNAL_OES should be in the fragment shader, but not in the vertex shader. The errors are gone once I fix this. I am still missing something because the video isn't showing up on the screen, but I am one step closer, and this solves the error posted in the original question. Thanks Again! – Dan Collins Sep 20 '12 at 20:12
  • http://stackoverflow.com/questions/7847912/is-it-possible-to-give-effect-like-vertex-shader-and-fragment-shader-to-the-andr | http://stackoverflow.com/questions/18449162/simultaneous-camera-preview-and-processing | http://stackoverflow.com/questions/30723055/how-to-obtain-the-rgb-colors-of-the-camera-texture-in-opengl-es-2-0-for-android – Ciro Santilli OurBigBook.com Nov 13 '16 at 01:26

1 Answers1

36
mDirectVideo = new DirectVideo(texture);
texture = createTexture();

should be

texture = createTexture();
mDirectVideo = new DirectVideo(texture);

Shader

private final String vertexShaderCode =
        "attribute vec4 position;" +
        "attribute vec2 inputTextureCoordinate;" +
        "varying vec2 textureCoordinate;" +
        "void main()" +
        "{"+
            "gl_Position = position;"+
            "textureCoordinate = inputTextureCoordinate;" +
        "}";

    private final String fragmentShaderCode =
        "#extension GL_OES_EGL_image_external : require\n"+
        "precision mediump float;" +
        "varying vec2 textureCoordinate;                            \n" +
        "uniform samplerExternalOES s_texture;               \n" +
        "void main() {" +
        "  gl_FragColor = texture2D( s_texture, textureCoordinate );\n" +
        "}";

mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

should be

mColorHandle = GLES20.glGetAttribLocation(mProgram, "s_texture");

remove initialization stuff from DirectVideo draw.glVertexAttribPointer etc. Put it in some init function.

public void draw()
{
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture);
    GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
            GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
}
psharma
  • 421
  • 4
  • 4
  • 1
    I believe this indeed fixed it. Thank You! (and accepted; better late than never). – Dan Collins Dec 31 '12 at 22:27
  • hmm.. @DanCollins I followed all these steps, but only got a grey screen... any suggestions? – Brad Moore Feb 26 '14 at 06:59
  • @Brad, sorry but I've been moved on to other projects and haven't touched openGL in a long time. I wish I could be of more help. – Dan Collins Mar 05 '14 at 21:10
  • Do not do the last draw() step he's suggested. I'll update if I move the correct stuff to the init as he has suggested, but part of what was moved needed to be in the draw() routine – Joel Teply Feb 10 '15 at 22:29
  • 6
    Coordinates are off as well, strange triangles. For native phone usage, use this: `static float squareVertices[] = { -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; static float textureVertices[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, };` – Joel Teply Feb 10 '15 at 23:05
  • 2
    I've tried out the project with your suggestions and created a GitHub repo with the results: [link](https://github.com/chauthai/glcam.git). I moved the GLES20.glVertexAttribPointer(mTextureCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,vertexStride, textureVerticesBuffer); method call to DirectVideo for this to work. The image displayed is still mirror-inverted. Any suggestions to fix this are very welcome. – Chau Thai Mar 08 '15 at 08:50
  • 2
    To remove mirror inversion, replace { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, }, with { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, } – RobotRock Sep 10 '15 at 18:34
  • I tried Chau Thai's github link on a Note4 with 4.4.4, it works just fine after applying RobotRock's change. It didn't work on a S6 with 5.1.1 though... – Saeid Farivar Oct 13 '15 at 21:19