wtracy
wtracy

Reputation: 133

OpenGL ES 2.0 on Android: Cannot load Textures

I'm a fixed-function pipeline guy getting used to the newfangled shader stuff.

I'm trying to extend the Android OpenGL ES example to texture a square shape. If the last line of my shader is gl_Fragcolor = vColor; I get a white square. If it is gl_FragColor = vColor * texture2D(u_Texture, v_TexCoordinate); I get just black.

Here's where it gets weirder: When I started checking for GL errors, I found that the line GLES20.glUseProgram(mProgram); at the start of my draw() function is giving me GL error 1280, GL_INVALID_ENUM. This makes no sense to me--there are no enums passed to that function.

Thoughts?

DemoRenderer.java:

package com.wtracy.executivedemo;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.Matrix;
import android.util.Log;

public class DemoRenderer implements Renderer {
    float ratio;
    // mMVPMatrix is an abbreviation for "Model View Projection Matrix"
    private final float[] mMVPMatrix = new float[16];
    private final float[] mProjectionMatrix = new float[16];
    private final float[] mViewMatrix = new float[16];
    private SplashScreen splash; 
    Activity parent;

    public DemoRenderer(Activity parent) {
        this.parent = parent;
    }

    public void onDrawFrame(GL10 arg0) {
        // Draw background color
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

        // Set the camera position (View matrix)
        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

        // Calculate the projection and view transformation
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

        // Draw square
        splash.draw(mMVPMatrix);
    }

    public void onSurfaceChanged(GL10 arg0, int width, int height) {
        // Adjust the viewport based on geometry changes,
        // such as screen rotation
        GLES20.glViewport(0, 0, width, height);

        ratio = (float) width / height;

        float newWidth;
        float newHeight;

        if (ratio >= 1f) {
            newWidth = ratio;
            newHeight = 1f;
        } else {
            newWidth = 1f;
            newHeight = 1f/ratio;
        }
        // this projection matrix is applied to object coordinates
        // in the onDrawFrame() method
        Matrix.frustumM(mProjectionMatrix, 0, -newWidth, newWidth, -newHeight, newHeight, 3, 7);
    }

    public void onSurfaceCreated(GL10 arg0, EGLConfig arg1) {
        // Set the background frame color
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

        splash = new SplashScreen(parent.getResources());
    }

    public static int loadShader(int type, String shaderCode){
        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);
        checkGlError("glCreateShader");

        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode);
        checkGlError("glShaderSource");
        GLES20.glCompileShader(shader);
        checkGlError("glCompileShader");

        return shader;
    }

    public static void checkGlError(String glOperation) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Log.e("com.wtracy.executivedemo", glOperation + ": glError " + error);
            throw new RuntimeException(glOperation + ": glError " + error);
        }
    }
}

SplashScreen.java:

package com.wtracy.executivedemo;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import com.wtracy.executivedemo.R;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;

/**
 * A two-dimensional square for use as a drawn object in OpenGL ES 2.0.
 */
public class SplashScreen {

    private final String vertexShaderCode =
            // This matrix member variable provides a hook to manipulate
            // the coordinates of the objects that use this vertex shader
            "uniform mat4 uMVPMatrix;" +
            "attribute vec4 vPosition;" +
            "attribute vec2 a_TexCoordinate; " +// Per-vertex texture coordinate information we will pass in.
            "varying vec2 v_TexCoordinate;" +
            "void main() {" +
            // The matrix must be included as a modifier of gl_Position.
            // Note that the uMVPMatrix factor *must be first* in order
            // for the matrix multiplication product to be correct.
            "  gl_Position = uMVPMatrix * vPosition;" +
            "  v_TexCoordinate = a_TexCoordinate;" +
            "}";

    private final String fragmentShaderCode =
            "precision mediump float; " +
            "uniform vec4 vColor; " +
            "uniform sampler2D u_Texture; " +
            "varying vec2 v_TexCoordinate; " +
            "void main() {" +
            /*"  float diffuse;" +
            "  diffuse = diffuse * (1.0 / (1.0 + (0.10 * distance)));" +
            "  diffuse = diffuse + 0.3;" +*/
            "  gl_FragColor = vColor * texture2D(u_Texture, v_TexCoordinate);" +
            //"  gl_FragColor = vColor;" +
            "}";

    private final FloatBuffer vertexBuffer;
    private final ShortBuffer drawListBuffer;
    private final int mProgram;
    private int mPositionHandle;
    private int mColorHandle;
    private int mMVPMatrixHandle;
    /** Store our model data in a float buffer. */
    private final FloatBuffer mCubeTextureCoordinates;
    /** This will be used to pass in the texture. */
    private int mTextureUniformHandle;
    /** This will be used to pass in model texture coordinate information. */
    private int mTextureCoordinateHandle;
    /** Size of the texture coordinate data in elements. */
    private final int mTextureCoordinateDataSize = 2;
    /** This is a handle to our texture data. */
    private int mTextureDataHandle;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float squareCoords[] = {
            -1.f,  1.f, 0.0f,   // top left
            -1.f, -1.f, 0.0f,   // bottom left
             1.f, -1.f, 0.0f,   // bottom right
             1.f,  1.f, 0.0f }; // top right

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

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

    final float color[] = { 1f, 1f, 1f, 1.0f };
    final int[] textureHandle = new int[1];

    /**
     * Sets up the drawing object data for use in an OpenGL ES context.
     */
    public SplashScreen(Resources resources) {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
        // (# of coordinate values * 4 bytes per float)
                squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

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

        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);

        // prepare shaders and OpenGL program
        int vertexShader = DemoRenderer.loadShader(
                GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = DemoRenderer.loadShader(
                GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        mProgram = GLES20.glCreateProgram();             // create empty OpenGL Program
        DemoRenderer.checkGlError("glCreateProgramr");
        GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program
        DemoRenderer.checkGlError("glAttachShader");
        GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
        DemoRenderer.checkGlError("glAttachShader");
        GLES20.glLinkProgram(mProgram);                  // create OpenGL program executables
        DemoRenderer.checkGlError("glLinkProgram");

        GLES20.glGenTextures(1, textureHandle, 0);
        DemoRenderer.checkGlError("glGenTextures");

        Bitmap bitmap;
        BitmapFactory.Options bo = new BitmapFactory.Options();
        bo.inScaled = false;
        bitmap = BitmapFactory.decodeResource(
                resources, 
                R.drawable.myfirstdemo,
                bo);
        // Bind to the texture in OpenGL
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
        DemoRenderer.checkGlError("glBindTexture");

        // Set filtering
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
        DemoRenderer.checkGlError("glTexParameter");

        // Load the bitmap into the bound texture.
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

        // Recycle the bitmap, since its data has been loaded into OpenGL.
        bitmap.recycle();

        if (textureHandle[0] == 0)
        {
            throw new RuntimeException("Error loading texture.");
        }
    }

    /**
     * Encapsulates the OpenGL ES instructions for drawing this shape.
     *
     * @param mvpMatrix - The Model View Project matrix in which to draw
     * this shape.
     */
    public void draw(float[] mvpMatrix) {
        // Add program to OpenGL environment
        GLES20.glUseProgram(mProgram);
        DemoRenderer.checkGlError("glUseProgram");

        // get handle to fragment shader's vColor member
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
        DemoRenderer.checkGlError("glGetUniformLocation");
        // get handle to vertex shader's vPosition member
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        DemoRenderer.checkGlError("glGetAttribLocation");
        mTextureUniformHandle = GLES20.glGetUniformLocation(mProgram, "u_Texture");
        DemoRenderer.checkGlError("glGetUniformLocation");
        mTextureCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "a_TexCoordinate");
        DemoRenderer.checkGlError("glGetAttribLocation");

        // Set color for drawing the triangle
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);
        //DemoRenderer.checkGlError("glUniform4fv");

        GLES20.glActiveTexture(textureHandle[0]);
        //DemoRenderer.checkGlError("glActiveTexture");
        // Bind the texture to this unit.
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);
        //DemoRenderer.checkGlError("glBindTexture");
        // Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
        GLES20.glUniform1i(mTextureUniformHandle, 0);
        //DemoRenderer.checkGlError("glUniform1i");

        // get handle to shape's transformation matrix
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        //DemoRenderer.checkGlError("glGetUniformLocation");

        // Apply the projection and view transformation
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
        //DemoRenderer.checkGlError("glUniformMatrix4fv");

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(
                mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // Pass in the texture coordinate information
        mCubeTextureCoordinates.position(0);
        GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false,
                0, mCubeTextureCoordinates);
        GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);

        // Draw the square
        GLES20.glDrawElements(
                GLES20.GL_TRIANGLES, drawOrder.length,
                GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(mPositionHandle);
        GLES20.glDisableVertexAttribArray(mTextureCoordinateHandle);
    }

}

Upvotes: 1

Views: 2427

Answers (1)

Reto Koradi
Reto Koradi

Reputation: 54592

Your GL_INVALID_ENUM error is almost certainly from a different call. Keep in mind that the error value sticks until you check it. So it could be from any call since the last call of glGetError().

From quickly scanning the code, I see two calls that get the wrong argument here:

GLES20.glActiveTexture(textureHandle[0]);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);
  • glActiveTexture() takes an enum that defines the texture unit you want to bind to, while you pass a texture name.
  • glBindTexture() on the other hand needs a texture name as the second argument, while you pass a variable that is not assigned a value anywhere in the posted code.

These calls should be:

GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);

Upvotes: 1

Related Questions