Home > OS >  Only seeing glClear color with no geometry on Android OpenGL ES
Only seeing glClear color with no geometry on Android OpenGL ES

Time:08-01

I am attempting to write a very simple Android application that uses OpenGL to display a green background and render a single cyan triangle over it, to verify that my code properly displays some geometry. I am running into an issue where the call to glClearColor/glClear to set the background is visible and makes the view green as intended, but I see no visible change from calling glDrawArrays after pointing the 0th attribute array to a FloatBuffer containing vertex coords. All my vertex shader does is pass the position as a vec4 to the fragment shader, which always just sets the output color to cyan, so I would expect to see the view green with one cyan triangle, but instead I see only the green background, and I am unsure why.

Main activity file (imports omitted):

setupDefaultProgram()
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        GLES30.glViewport(0, 0, width, height)
    }

    override fun onDrawFrame(gl: GL10?) {
        glRenderer.renderFunc()
    }

}

Renderer code:

class GlRenderer {

    companion object {
        // Pass through position and UV values
        val vertexSource = """
            #version 300 es
            
            in vec2 position;
            
            void main() {
                gl_Position = vec4(position, -0.5, 1.0);
            }
        """.trimIndent()

        // Eventually get the texture value, for now, just make it cyan so I can see it
        val fragmentSource = """
            #version 300 es
            precision mediump float;
            
            out vec4 fragColor;
            
            void main() {
                fragColor = vec4(0.0, 1.0, 1.0, 1.0);
            }
        """.trimIndent()
    }

    private var vertexBuffer: FloatBuffer

    private var defaultProgram: Int = -1
    private var vertexLocation: Int = -1

    private fun checkGlError(msg: String) {
        val errCodeEgl = EGL14.eglGetError()
        val errCodeGl = GLES30.glGetError()
        if (errCodeEgl != EGL14.EGL_SUCCESS || errCodeGl != GLES30.GL_NO_ERROR) {
            throw RuntimeException(
                "$msg - $errCodeEgl(${GLU.gluErrorString(errCodeEgl)}) : $errCodeGl(${
                    GLU.gluErrorString(
                        errCodeGl
                    )
                })"
            )
        }
    }

    init {
        // Flat square
        // Am I allocating and writing to these correctly?
        val vertices = floatArrayOf(-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f)
        vertexBuffer = ByteBuffer.allocateDirect(vertices.size * 4).asFloatBuffer().also {
            it.put(vertices)
            it.position(0)
        }
    }

    fun setupDefaultProgram() {
        defaultProgram = makeProgram(
        mapOf(
            GLES30.GL_VERTEX_SHADER to vertexSource,
            GLES30.GL_FRAGMENT_SHADER to fragmentSource
            )
        )
        vertexLocation = GLES30.glGetAttribLocation(defaultProgram, "position")
        checkGlError("Getting uniform")
    }

    private fun compileShader(source: String, shaderType: Int): Int {
        val shaderId = GLES30.glCreateShader(shaderType)
        checkGlError("Create shader")
        if (shaderId == 0) {
            Log.e("Native", "Could not create shader for some reason")
            checkGlError("Could not create shader")
        }
        GLES30.glShaderSource(shaderId, source)
        checkGlError("Setting shader source")
        GLES30.glCompileShader(shaderId)
        val statusBuffer = IntArray(1)
        GLES30.glGetShaderiv(shaderId, GLES30.GL_COMPILE_STATUS, statusBuffer, 0)
        val shaderLog = GLES30.glGetShaderInfoLog(shaderId)
        Log.d("Native", "Compiling shader #$shaderId : $shaderLog")

        if (statusBuffer[0] == 0) {
            GLES30.glDeleteShader(shaderId)
            checkGlError("Failed to compile shader $shaderId")
        }
        return shaderId
    }

    private fun makeProgram(sources: Map<Int, String>): Int {
        val program = GLES30.glCreateProgram()
        checkGlError("Create program")
        sources.forEach {
            val shader = compileShader(it.value, it.key)
            GLES30.glAttachShader(program, shader)
        }
        val linkBuffer = IntArray(1)
        GLES30.glLinkProgram(program)
        GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkBuffer, 0)
        if (linkBuffer[0] == 0) {
            GLES30.glDeleteProgram(program)
            checkGlError("Failed to link program $program")
        }
        return program
    }

    // Called to actually draw to the surface. When fully implemented it should draw whatever is
    // on the associated texture, but for now, to debug, I just want to verify I can draw vertices,
    // but it seems I cannot?
    fun renderFunc() {
        GLES30.glClearColor(0f, 1f, 0.5f, 1f)
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
        checkGlError("Clearing")

        GLES30.glUseProgram(defaultProgram)
        checkGlError("Use program")

        GLES30.glEnableVertexAttribArray(vertexLocation)
        vertexBuffer.position(0)
        FloatArray(2 * 4).apply {
            vertexBuffer.get(this)
            vertexBuffer.position(0)
            Log.d("Native", "Vertex buffer ${contentToString()}")
        }
        GLES30.glVertexAttribPointer(vertexLocation, 2, GLES30.GL_FLOAT, false, 0, vertexBuffer)
        checkGlError("Attribute 0")

        // Just render a triangle
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3)

        GLES30.glFinish()
        checkGlError("Finished GL")
    }

}

My debug output logs what I would expect:

D/Native: Compiling shader #2 : 
D/Native: Compiling shader #3 : 
D/Native: Vertex buffer [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0]

I am including <uses-feature android:glEsVersion="0x00020000" android:required="true" /> in my manifest. Where am I going wrong that the geometry of the triangle is either not rendered to the screen or is not visible?

CodePudding user response:

When you create Buffer for GL, you need to specify the native byte-order.

val vertices = floatArrayOf(-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f)
vertexBuffer = ByteBuffer.allocateDirect(vertices.size * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().also {
    it.put(vertices)
    it.position(0)
}

CodePudding user response:

To expand on @ardget's answer: A byte-order is the internal order of the ByteBuffer's data, which basically determines how the data is stored in memory.

There are two 'byte-orders' possible: little-endian and big-endian. I won't go in to too much detail here, but big-endian stores data from the most significant to least significant value and little-endian stores data from least significant to most significant value. Your machine has a native order, which can be either little-endian or big-endian.

When you call ByteBuffer.allocateDirect, Java automatically sets the byte-order to be big-endian. But when you then use glBufferData, (if I'm not mistaken) OpenGL expects the byte-order to be the machine's native order. If the native-order is not big-endian, but instead is little-endian (as in your machine), then problems occur.

You can fix this by directly specifying the byte-order as the native byte order by using .order(ByteOrder.nativeOrder()).

  • Related