Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nexus 10 - render to external rendertarget works only in landscape

We're developing live wallpaper with OpenGL ES 2.0 on Nexus 10.

Live wallpaper uses 2 small (128x128) external framebuffers to make ping-pong rendering between them to blur image.

While this works perfectly fine on any device (even on aged Motorola Milestone) there is a strange issue with Nexus 10. This works only if device is in landscape orientation. If device is rotated in any other position (90, 180 or 270 degrees) framebuffers has only clear color. I've set glClearColor to red so it is clearly visible that these framebuffers are cleared but nothing is rendered into them.

I've tested it on Tegra 2, Tegra 3, Adreno 200, Adreno 320, 2 PowerVR GPUs and it works just fine.

This looks like some weird driver bug, but might also be some specifics of Mali driver. Please advice.

Code excerpts.

Init framebuffers:

private void initBloomStuff() {
    mBloomTextureID = loadTexture("textures/empty128.png");
    mBloomVertTextureID = loadTexture("textures/empty128.png");

    mBloomFBHeight = 128;
    mBloomFBWidth = 128;

    float blurSize = 1.0f;

    // Texel offset for blur filter kernel
    m_fTexelOffset = 1.0f / mBloomFBWidth / blurSize;

    ByteBuffer tmpFB, tmpRB;
    IntBuffer handle, renderbuffers;
    int result;

    tmpFB = ByteBuffer.allocateDirect(4);
    tmpFB.order(ByteOrder.nativeOrder());
    handle = tmpFB.asIntBuffer();
    GLES20.glGenFramebuffers(1, handle);
    framebufferHandle = handle.get(0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBloomTextureID);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferHandle);
    GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, mBloomTextureID, 0);

    checkGlError("FB 1");
    tmpRB = ByteBuffer.allocateDirect(4);
    renderbuffers = tmpRB.asIntBuffer();
    GLES20.glGenRenderbuffers(1, renderbuffers);
    checkGlError("FB 1 - glGenRenderbuffers");
    depthbufferHandle = renderbuffers.get(0);
    GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, depthbufferHandle);
    checkGlError("FB 1 - glBindRenderbuffer");
    GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, mBloomFBWidth, mBloomFBHeight);
    checkGlError("FB 1 - glRenderbufferStorage");
    GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, depthbufferHandle);
    checkGlError("FB 1 - glFramebufferRenderbuffer");

    result = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
    if (result != GLES20.GL_FRAMEBUFFER_COMPLETE) {
        Log.d(TAG, "Error creating framebufer 1: " + result);
    } else {
        Log.d(TAG, "Created framebufer 1: " + result);
    }

    GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

    tmpFB = ByteBuffer.allocateDirect(4);
    tmpFB.order(ByteOrder.nativeOrder());
    handle = tmpFB.asIntBuffer();
    GLES20.glGenFramebuffers(1, handle);
    framebufferVertHandle = handle.get(0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBloomVertTextureID);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferVertHandle);
    GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, mBloomVertTextureID, 0);

    checkGlError("FB 2");
    tmpRB = ByteBuffer.allocateDirect(4);
    renderbuffers = tmpRB.asIntBuffer();
    GLES20.glGenRenderbuffers(1, renderbuffers);
    checkGlError("FB 2 - glGenRenderbuffers");
    depthbufferVertHandle = renderbuffers.get(0);
    GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, depthbufferVertHandle);
    checkGlError("FB 2 - glBindRenderbuffer");
    GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, mBloomFBWidth, mBloomFBHeight);
    checkGlError("FB 2 - glRenderbufferStorage");
    GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, depthbufferVertHandle);
    checkGlError("FB 2 - glFramebufferRenderbuffer");

    result = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
    if (result != GLES20.GL_FRAMEBUFFER_COMPLETE) {
        Log.d(TAG, "Error creating framebufer 2: " + result);
    } else {
        Log.d(TAG, "Created framebufer 2: " + result);
    }

    GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

    mTriangleVerticesVignette = ByteBuffer.allocateDirect(mQuadTriangles.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
    mTriangleVerticesVignette.put(mQuadTriangles).position(0);
}

Render to FB:

    GLES20.glUseProgram(mProgram);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mStemTextureID);
    drawBird();
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mSphereTextureID);
    drawSphere();

    GLES20.glViewport(0, 0, mBloomFBWidth, mBloomFBHeight);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferHandle);
    // GLES20.glBindRenderbuffer(GLES20.GL_FRAMEBUFFER, depthbufferHandle);

    GLES20.glClearColor(1.0f, 0.5f, 0.5f, 1.0f);
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mStemTextureID);
    drawBird();
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mSphereTextureID);
    drawSphere();

    GLES20.glViewport(0, 0, screenWidth, screenHeight);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
    // GLES20.glBindRenderbuffer(GLES20.GL_FRAMEBUFFER, 0); 

Ping-pong rendering between 2 FBs to blur image:

    GLES20.glUseProgram(mBloomProgram);
    GLES20.glUniform1i(mBloom_sTexture, 0);
    GLES20.glUniform1f(mBloom_bloomFactor, 0.8f);

    GLES20.glActiveTexture(GL10.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBloomTextureID);
    GLES20.glViewport(0, 0, mBloomFBWidth, mBloomFBHeight);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferVertHandle);
    GLES20.glUniform1f(mBloom_TexelOffsetX, m_fTexelOffset);
    GLES20.glUniform1f(mBloom_TexelOffsetY, 0.0f);
    GLES20.glActiveTexture(GL10.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBloomTextureID);
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    drawBloom();
    GLES20.glViewport(0, 0, screenWidth, screenHeight);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

    GLES20.glActiveTexture(GL10.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBloomVertTextureID);
    GLES20.glViewport(0, 0, mBloomFBWidth, mBloomFBHeight);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferHandle);
    GLES20.glUniform1f(mBloom_TexelOffsetX, 0.0f);
    GLES20.glUniform1f(mBloom_TexelOffsetY, m_fTexelOffset);
    GLES20.glActiveTexture(GL10.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBloomVertTextureID);
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    drawBloom();
    GLES20.glViewport(0, 0, screenWidth, screenHeight);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

    GLES20.glActiveTexture(GL10.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBloomTextureID);
    GLES20.glViewport(0, 0, mBloomFBWidth, mBloomFBHeight);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferVertHandle);
    GLES20.glUniform1f(mBloom_TexelOffsetX, m_fTexelOffset / 2);
    GLES20.glUniform1f(mBloom_TexelOffsetY, m_fTexelOffset / 2);
    GLES20.glActiveTexture(GL10.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBloomTextureID);
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    drawBloom();
    GLES20.glViewport(0, 0, screenWidth, screenHeight);
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); 

Test application to reproduce bug

You can download test APK here: https://dl.dropboxusercontent.com/u/7197208/LiveWallpaperAnimTest.apk It is a live wallpaper app, install it and select 'Test' live wallpaper (has an icon with rose).

As you can see, in default landscape orientation you will see some 'bloom' effect around bird which is implemented by ping-pong rendering between 2 framebuffers. In any other device orientation it doesn't work and fills FB with clear color (red).

Additional links

I've also posted this issue to Mali Developer Center and Google Code:

http://forums.arm.com/index.php?/topic/16894-nexus-10-render-to-external-rendertarget-works-only-in-landscape/

http://code.google.com/p/android/issues/detail?id=57391

like image 771
keaukraine Avatar asked Nov 12 '22 02:11

keaukraine


1 Answers

Mali driver support team confirms that this is a driver bug and will fix it in next driver version. As a temporary workaround, they propose to call glViewport after glBindFramebuffer. This workaround works - after changing call order I managed to achieve correct rendering.

I've proposed Google to contact ARM to include a fixed OpenGL ES driver for Nexus 10 as soon as fixed driver is available. If anybody is willing for this to happen faster, you can star an issue in Android bug tracker: http://code.google.com/p/android/issues/detail?id=57391

Link to Mali Developer Center forums where I've filed this issue: http://forums.arm.com/index.php?/topic/16894-nexus-10-render-to-external-rendertarget-works-only-in-landscape/

As this driver bug is verified and confirmed by ARM, please go and star Android issue #57391 instead of voting for this SO question. Hopefully this will force Google to pay attention to issue and to release a firmware update, making Nexus 10 better. This is the reason I've assigned bounty for this question.

EDIT. This bug is finally fixed in Android 4.4.

like image 151
keaukraine Avatar answered Nov 15 '22 11:11

keaukraine