Myles Hollowed
Myles Hollowed

Reputation: 546

Gles2.0 Overlay on Android

I'm trying to build an overlay for an Android application that uses GLESv2. I've hooked eglSwapBuffers in order to insert my rending code just before the frame finishes.

I'm able to do simple things like drawing a square with the scissor test:

glEnable(GL_SCISSOR_TEST);
glScissor(0, 0, 200, 200);
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_SCISSOR_TEST);

I've also had success drawing simple shapes with the following code, but as soon as I start using vertex attrib pointers the application stops rending correctly and shows a mostly-black screen with a small section that still displays correctly. I'm sure there's some open-gl state that I'm clobbering here but I'm not sure what it is. What would I need to save/restore before/after my draw calls in order to allow the app to continue to render correctly with my overlay?

// Save application state
GLint prev_program;
glGetIntegerv(GL_CURRENT_PROGRAM, &prev_program);

// Do overlay drawing
glUseProgram(program);
glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, RectangleVertices);
glEnableVertexAttribArray(vPosition);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDisableVertexAttribArray(vPosition);

// Trying to restore application state here - there are probably more things that I'm missing.
glUseProgram(prevProgram);

Upvotes: 7

Views: 315

Answers (2)

G. Putnam
G. Putnam

Reputation: 1765

Based on your response to my comment, I believe you're trying to accomplish something like this effect:

In the example shown, the 3D information is drawn first, and then the buttons on the lower left are drawn in orthographic mode using a tile based rendering shader. The tile based portion is not essential, yet if you're interested Sprite Tile Maps on GPU has a good example of how to implement.

Apologies as I use some Javaisms (most of what you see is for cell phones)

Have attempted to swap over to c++ conventions where able.

The general structure of the render loop looks like:

public void onDrawFrame() {
    // Do any updates you need to do before you would start rendering
    // In my case I update the erosion on the planetary surface
    // and do fluid simulations of rainfall

    // Draw background color
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    // Enable face culling so we don't draw a lot of unnecessary triangles
    // Won't draw any triangles whose normal would face away from the camera
    // I do this just in case I turned it off somewhere else
    // I use a lot of offscreen rendering to textures
    glEnable( GL_CULL_FACE );

    // Enable depth buffering
    // Checks pixels for depth in the fragment shader and throws them away
    // if they fail the depth test (IE: something is already drawn in
    // front of them)
    glEnable( GL_DEPTH_TEST );

    // glViewport x,y specifies the lower-left corner (not the center)
    // scaling is not uniform around the center
    // (extends up-right with positive numbers)
    glViewport( 0, 0, viewport_w, viewport_h );

    // Draw all the geometry
    geometry.draw( camera.getVPMatrix(), mModelMatrix );

    // Start changes for portions of the user interface that depend
    // on the geometry viewport
    glViewport( 0, 0, screen_w, screen_h );

    // Disable depth buffering
    // Because you're going to draw in orthographic mode,
    // you don't want to check for depth because it may fail unexpectedly,
    // since you're using a different type of matrix transform
    glDisable( GL_DEPTH_TEST );

    // Draw the portions of the user interface that are
    // only related to the screen dimensions
    // This portion is drawn using orthographic coordinates
    // (flat, no perspective)
    // The orthographic matrix is just an identity matrix
    ui.draw( ui.mMVPMatrix_Identity );

    // Do any updates you need to do after the User Interface gets drawn
}

Inside each of the geometry groups, I then have draw() calls that perform the draw operations for that type of geometry, so I can collect and keep together all the necessary shader inputs, glEnable calls, ect... A typical example would look like:

//--------------------------------
// Do anything that needs to be done to this specific
// object before we start rendering
//
// (Your update actions)
//
//-------------------------------- 

// Add program to OpenGL environment
// I keep a reference to each compiled shader with
// the object I'm going to draw so I can just
// call it whenever I draw
glUseProgram( shader.getProgram() );

// Do tests for any conditional attributes or uniforms
// that might differ from object to object
// Ex: sometimes, two similar objects may use a
// a slightly different shader with slightly
// different inputs
//
// Note, I also tend to get a lot of objects as
// references where I instantiate the object a
// single time and then grab a pointer to the
// object based on the class type
// (your mileage may vary on this coding pattern)
// 
// PreRenderArgs are collections of arguments for
// shader attributes and uniforms that are going to
// be set prior to rendering.  They're kept on a
// per-group or per-object basis while the shader
// itself is handled seperately
//
// Below are two examples
if( shader.uniforms.containsKey( "cameraPos" ) ){
    PreRenderArgs.get( "cameraPos" )[ 1 ] = CameraGLES.getRef().eye.toArray();
}

if( shader.uniforms.containsKey( "lightPos" ) ){
    PreRenderArgs.get( "lightPos" )[ 1 ] = Renderer.getRef().ambientLightPos.toArray();
}

// Loop through all the shader attributes and uniforms
// Each attribute or uniform has a certain set of steps
// and inputs that need to be called each time that
// attribute or uniform is activated prior to rendering
// I call these preRenderSteps
// (I include an example of an attribute pre- and post- steps below)

for( String attr : shader.attributes.keySet() ){
    RefMethodwArgs preRenderStep = shader.attributes.get( attr )[ "pre" ];
    if( preRenderStep != null ){
        preRenderStep.invoke( PreRenderArgs.get( attr ) );
    }
}
for( String uni : shader.uniforms.keySet() ){
    RefMethodwArgs preRenderStep = shader.uniforms.get( uni )[ "pre" ];
    if( preRenderStep != null ){
        preRenderStep.invoke( PreRenderArgs.get( uni ) );
    }
}

//--------------------------
// Actually draw your object
// In this case I'm using glDrawElements with GL_TRIANGLES
// and a drawListBuffer so it goes through and calls
// attributes based on indices for vertices in the drawList
//--------------------------

geomObj.drawListBuffer.position( 0 );
glDrawElements( GL_TRIANGLES, geomObj.drawOrder.length, GL_UNSIGNED_INT, geomObj.drawListBuffer );

// Loop through all the shader attributes and uniforms
// Each attribute or uniform has a certain set of steps
// that need to be called after rendering is complete
// to make sure you go back to a neutral state
// I call these postRenderSteps

for( String attr : shader.attributes.keySet() ){
    RefMethodwArgs postRenderStep = shader.attributes.get( attr )[ "post" ];
    if( postRenderStep != null ){
        postRenderStep.invoke();
    }
}
for( String uni : shader.uniforms.keySet() ){
    RefMethodwArgs postRenderStep = shader.uniforms.get( uni )[ "post" ];
    if( postRenderStep != null ){
        postRenderStep.invoke();
    }
}

Below is an example of what would be called during one of the attribute Pre- and Post- steps, in this case an AttributeBuffer

void AttributeBufferPreActions( int attrHandle, ByteBuffer attrBuffer, int attrSize ){
    // Pre-rendering actions
    glEnableVertexAttribArray( attrHandle );
    attrBuffer.position(0);

    // Prepare the coordinate data
    // Note: Size is usually coordinates per vertex x,y (2) or x,y,z (3)
    // Note: Stride is almost always Size * 4 bytes per vertex (if its not then change this function)
    int attrStride = 4 * attrSize;
    glVertexAttribPointer(
            attrHandle, attrSize,
            GL_FLOAT, false,
            attrStride, attrBuffer );
}

void AttributeBufferPostActions( int attrHandle ){
    // Post-rendering actions
    // Disable vertex array
    glDisableVertexAttribArray( attrHandle );
}

The draw call for the user interface, is almost the same, except it loops through a tree structure of MenuElements (which may have child elements), and then draw all of them using Orthographic projection and no depth testing.

void draw( float[] mvpMatrix ){ // Note: Identity matrix, no perspective transform
    if( uiMenuElements.size() > 0 ){
        for( int i = 0; i < uiMenuElements.size(); i++ ){
            MenuElement curElement = uiMenuElements.get( i );
            curElement.draw( mvpMatrix );
        }
    }
}

As a final note on this, I've provided some of the structural choices, because one of the best ways I've found to handle OpenGL drawing, is to compartmentalize all the necessary PreRender and PostRender steps for each attribute or uniform on a shader, bookkeep them all for each object or group of objects (many never change most of their inputs), pass the shader around between objects being drawn, and then just assign and execute all the PreRender and PostRender changes as necessary for each shader. This helps to ensure you'll return to a neutral state after each draw session.

Upvotes: 1

solidpixel
solidpixel

Reputation: 12109

What would I need to save/restore before/after my draw calls in order to allow the app to continue to render correctly with my overlay?

Everything that you modified ...

Note that even full state restoration can be inadequate for some developer use cases. Normally this is a bug in the application (application making assumptions about object ID assignments), but there are use cases in development tooling such as verbatim API trace replay tools where such assumptions are made.

Upvotes: 2

Related Questions