views:

142

answers:

1

I have an application that receives information from a database, and is used to visualize 2D bitmaps onto a GLSurfaceView. The information received will determine the x-position of the bitmap, and which bitmap image to use (there are 4 different bitmaps in my res folder to choose from).

Below are the three classes that are being used. The Activity sets the Shapes objects that need to be drawn by passing an ArrayList to the GLLayer class. This ArrayList is passed to the instance of ShapeStorage class via another setList method. This class is responsible for drawing when they are received.

The problem that I'm having is the following. Suppose I receive one object (let's say that it's a square at it's located at x=1). Some time goes by, and I receive another shape (this time, it's a triangle, and it's located at x=-1). However, when this new shape appears on the screen, the old bitmap's appearance changes to a triangle, and the new one becomes a square. In other words, the objects themselves are at the correct position that they are supposed to be at, but the bitmap being associated with them has changed. Does anyone know what the possible cause of this can be? I'm still a newbie to OpenGL-ES, and while this code looks very convoluted, it just involves setting a bunch of various properties for the View. Please help me, StackOverflow! You're my only hope.

public class GLLayer extends GLSurfaceView implements Renderer {
    int onDrawFrameCounter=1;
    int[] cameraTexture;
    byte[] glCameraFrame=new byte[256*256]; //size of a texture must be a power of 2
    private Context context;
    FloatBuffer cubeBuff;
    FloatBuffer texBuff;
    ShapeStorage shapes;
    ArrayList<Shapes> shapereceptionbuffer;

    public GLLayer(Context c) {
        super(c);
        this.context=c;
        //Initiate our stars class with the number of stars
        shapes = new ShapeStorage();
        shapereceptionbuffer=new ArrayList<Shapes>();

        this.setEGLConfigChooser(5, 6, 5, 8, 16, 0);
        this.setRenderer(this); //set the following class as a GLSurfaceView renderer
        this.getHolder().setFormat(PixelFormat.TRANSPARENT); //makes the GLSurfaceView translucent

    }

    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        try {       
            gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
            gl.glShadeModel(GL10.GL_SMOOTH);                    //Enable Smooth Shading
            gl.glEnable(GL10.GL_TEXTURE_2D);                    //Enable Texture Mapping
            gl.glEnable(GL10.GL_BLEND);                         //Enable blending
            gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);            //Black Background
            gl.glClearDepthf(1.0f);                             //Depth Buffer Setup
            gl.glDisable(GL10.GL_DEPTH_TEST);                   //Disable depth test
            gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);     //Set The Blending Function For Translucency
            shapes.setTextures(gl,context);



        } catch (Exception e) {
            // TODO Auto-generated catch block
            Log.d("Created",e.getMessage());
        }
    }//end of surfacecreated    

    public void setList(ArrayList<Shapes> receivedList){
        synchronized(this.shapereceptionbuffer){
            shapereceptionbuffer=receivedList;

        }
    }

    public void onSurfaceChanged(GL10 gl, int width, int height) {  
        try {

            if(height == 0) {                       //Prevent A Divide By Zero By
                height = 1;                         //Making Height Equal One
            }

            gl.glViewport(0, 0, width, height);//specifies transformation from normalized device coordinates to window coordinates
            float ratio = (float) width / height;
            gl.glMatrixMode(GL11.GL_PROJECTION); //Select The Projection Matrix
            gl.glLoadIdentity();//Reset The Projection Matrix
            GLU.gluPerspective(gl, 45.0f, ratio, 0.1f, 100.0f);
            gl.glMatrixMode(GL11.GL_MODELVIEW);//Select The Modelview Matrix
            gl.glLoadIdentity();//Reset The Modelview Matrix
        } catch (Exception e) {
            // TODO Auto-generated catch block
            Log.d("Changed",e.getMessage());
        }
        //GLU.gluLookAt(gl, 0, 0, 4.2f, 0, 0, 0, 0, 1, 0);//eye-point location, center of the scene and an UP vector        
    }//end of surfacechanged

    public void onDrawFrame(GL10 gl) {
        try {
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); //Clear Screen And Depth Buffer
            Log.d("Buffer Size", String.valueOf(shapereceptionbuffer.size()));
            synchronized(this.shapereceptionbuffer){

                shapes.setShapes(shapereceptionbuffer);
                shapes.draw(gl, this.context);
            }

        } catch (Exception e) {
            Log.d("Draw",e.getMessage());
        }   
    }//end of ondrawframe
}

This class is responsible for drawing each of the shapes that are received from the external database.

/**
 * This class contains, loads, initiates textures and draws our Shapes
 */
public class ShapeStorage {
    private ArrayList<Shapes> shapestoragebuffer;
    private Random rand = new Random(); // Initiate Random for random values of
                                        // stars

    /** Our texture pointer */
    private int[] textures = new int[4];

    /**
     * Constructor for our holder
     */
    public ShapeStorage() {
        shapestoragebuffer = new ArrayList<Shapes>();
    }

    public void setShapes(ArrayList<Shapes> receivedlist) {
        shapestoragebuffer = receivedList;
    }

    public void setupTextures(GL10 gl, Context context) {
        // Get the texture from the Android resource directory
        InputStream is = null;
        gl.glGenTextures(4, textures, 0);
        for (int i = 2; i < 6; i++) {
            switch (i) {
            case 2:
                is = context.getResources().openRawResource(R.drawable.square);
                break;
            case 3:
                is = context.getResources().openRawResource(R.drawable.circle);
                break;

            case 4:
                is = context.getResources().openRawResource(R.drawable.hexagon);
                break;

            case 5:
                is = context.getResources()
                        .openRawResource(R.drawable.triangle);
                break;
            }

            Bitmap bitmap = null;
            try {
                // BitmapFactory is an Android graphics utility for images
                bitmap = BitmapFactory.decodeStream(is);

            } finally {
                // Always clear and close
                try {
                    is.close();
                    is = null;
                } catch (IOException e) {
                }
            }

            // Generate the texture pointer

            // Create Linear Filtered Texture and bind it to texture
            GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0);
            gl.glBindTexture(GL11.GL_TEXTURE_2D, textures[i - 2]);
            gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
                    GL11.GL_LINEAR);
            gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
                    GL11.GL_LINEAR);

            // Clean up
            bitmap.recycle();
        }
    }

    /**
     * The drawing function.
     * 
     * @param gl
     *            - The GL Context
     * @param twinkle
     *            - Twinkle on or off
     */
    public void draw(GL10 gl, Context context) {
        // Bind the icon texture for all Shapes



        for (int loop = 0; loop < shapestoragebuffer.size(); loop++) {
            // Recover the current star into an object
            Shapes shape = shapestoragebuffer.get(loop);

            gl.glLoadIdentity(); // Reset The Current Modelview Matrix
            // gl.glRotatef(180.0f, -1.0f, 0.0f, 0.0f);
            float x = shape.get_Offset_from_center();
            gl.glTranslatef(x, 0.0f, -40.0f);

            // Draw
            switch (victim.getType()) {
            // green
            case 2:
                shape.draw(gl, textures[0]);
                break;
            // red
            case 3:
                shape.draw(gl, textures[1]);
                break;
            // yellow
            case 4:
                shape.draw(gl, textures[2]);
                break;

            case 5:
                shape.draw(gl, textures[3]);
                break;
            }

        }
    }
}

Here is the class that defines each of the objects that are being drawn to the GLSurfaceView; each of the shapes that are being drawn.

public class Shapes {


    private int _Offset_from_center;
    private int type;

    Context c;
    /** The buffer holding the vertices */
    private FloatBuffer vertexBuffer;
    /** The buffer holding the texture coordinates */
    private FloatBuffer textureBuffer;

    /** The initial vertex definition */
    private float vertices[] = {
                                -1.0f, -1.0f, 0.0f,     //Bottom Left
                                1.0f, -1.0f, 0.0f,      //Bottom Right
                                -1.0f, 1.0f, 0.0f,      //Top Left
                                1.0f, 1.0f, 0.0f        //Top Right
                                                    };

    /** The initial texture coordinates (u, v) */   
    private float texture[] = {
                                0.0f, 0.0f, 
                                1.0f, 0.0f, 
                                0.0f, 1.0f, 
                                1.0f, 1.0f,
                                            };
    public Shapes() {
        //
        ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
        byteBuf.order(ByteOrder.nativeOrder());
        vertexBuffer = byteBuf.asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);

        //
        byteBuf = ByteBuffer.allocateDirect(texture.length * 4);
        byteBuf.order(ByteOrder.nativeOrder());
        textureBuffer = byteBuf.asFloatBuffer();
        textureBuffer.put(texture);
        textureBuffer.position(0);
    }


    public int get_Offset_from_center() {
        return _Offset_from_center;
    }

    public void set_Offset_from_center(int _Offset_from_center) {
        this._Offset_from_center = _Offset_from_center;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    /**
     * The object own drawing function.
     * Called from the renderer to redraw this instance
     * with possible changes in values.
     * 
     * @param gl - The GL Context
     */
    public void draw(GL10 gl,int texture) {
        gl.glBindTexture(GL11.GL_TEXTURE_2D, texture);
        //Enable the vertex, texture and normal state
        gl.glEnableClientState(GL11.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);

        //Point to our buffers
        gl.glVertexPointer(3, GL11.GL_FLOAT, 0, vertexBuffer);
        gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, textureBuffer);

        //Draw the vertices as triangle strip
        gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, vertices.length / 3);

        //Disable the client state before leaving
        gl.glDisableClientState(GL11.GL_VERTEX_ARRAY);
        gl.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);


    }


} 
+1  A: 

You're doing something very strange with the textures. Why do you upload the texture image data for every shape on every frame, and why do you do it after you render the shape?

Here's how the usual flow of texture use in OpenGL works:

At app initialization time:

  1. Generate a texture ID using glGenTextures.
  2. Use glBindTexture to make that ID the current texture.
  3. Set texture parameters.
  4. Upload image data with texImage2D or similar.

Then, every time you need to render stuff with the texture:

  1. Bind the texture with glBindTexture with the same ID you used above.
  2. Render things, which will use the texture.

What I would recommend here is this:

When you activity starts up (called indirectly from onCreate or maybe onResume depending on how Android OpenGL works):

  1. Make textures a 5-element array and pass 5 to glGenTextures
  2. Loop through, and for each of your five resources, bind one of the four above, and upload your image with texImage2D just like you have.

Then, when you actually need to draw a shape:

  1. Pass in an int for the texture, not an int[]; choose the right one based on the shape you want.
  2. Call glBindTexture in your draw function, first, with that value.
  3. Do not make any calls to texImage2D in your rendering pass.
  4. Call glDrawArrays to draw the shape you chose with glBindTexture.

Note also that all your shapes can share the same vertex and texture buffers, since their contents are the same; that's just an efficiency thing though.

Walter Mundt
@Walter Mundt Thanks for the help. One disclaimer I should have placed in my question was that I'm a complete novice when it comes to OpenGL. Just one question, what do you mean by "Why do you upload the texture image data for every shape on every frame, and why do you do it after you render the shape?" In which class is this happening?
rohanbk
In shapes, in the `bitmapdraw` function which is called from `draw`.
Walter Mundt
So, the way I have interpreted your advice, are you saying that I should make my textures array within ShapeStorage class into a 5-element array, and I should bind each bitmap in the switch statement of bitmapdraw to a cell of the array?
rohanbk
Yes -- and call `bitmapdraw` early, probably in `onSurfaceCreated`. You might want to rename it to `setupTextures` or something.
Walter Mundt
So, am I calling `glBindTexture` 5-times in my `setupTextures` function? Is that how I associate each of my bitmaps with the `textures` array? I'm having some trouble understanding where `texImage2D` is storing my bitmaps.
rohanbk
Yes, and texImage2D as well, after each bind. texImage2D copies the bitmaps into space controlled by OpenGL. You don't have any access to it after that except by calling gl* functions with that texture bound.
Walter Mundt
To put it another way, you can think of the numbers returned by glGenTextures as "pointers" to textures, which are initially empty. When you call `glBindTexture` you are saying "anything I do with a texture from here on out will use this one" -- but that on its own doesn't do anything. `texImage2D` for example says "overwrite the currently-bound texture with this image data".
Walter Mundt
I made the modifications that you suggested, and things actually seem to be working noticeably better than before. I modified the source code in my question to reflect the changes as per your answers to my questions. However, I'm unsure of the correctness of my `setTextures` function (located in the `ShapeBuffer` class and called from the `onSurfaceCreated` function in the `GLLayer` class). Is the order in which I invoke `glBindTexture` and `texImage2D` correct? Also, do my `draw` methods need to be modified in any way?
rohanbk
No, you have them backwards; you have to bind a texture before you can change what it will display. Remember, `glBindTexture` must always come first, before any operation related to a texture. Also, you haven't updated `draw` to take a plain int instead of an array, or added the `glBindTexture` call to the top of that function.
Walter Mundt
Oops, guess I forgot to make the changes in my question. Everything you suggested worked flawlessly, and icons are not swapping position anymore! Thank you so much. I now have a better understanding of the flow of things within an OpenGL ES program. I wish I could upvote you more for all the help you've given me over the past couple days.
rohanbk