Question

My app uses a fullscreen glSurface overlay, with 8888 format. For most devices this works fine - I can draw semi-transparent imagery using OpenGL ES, and this will indeed be overlaid with transparency on top of my other native views.

However on Nexus 10 and Note 2 (and most likely some other devices), the semi-transparent pixels do not look correct on the physical display, even though a screenshot taken through DDMS looks absolutely correct! This has me stumped - how can the visual display look so different from a screenshot?

It appears that the strangeness occurs for any pixel whose alpha value in the framebuffer is anything other than 1 (0xff) or 0 (0x00).

I've attached the DDMS screenshot from Nexus 10, showing a test-card image that is exactly as it should be. I've also attached a photograph from the display, showing a very different image... as described, it appears that the pixels where the EGL overlay alpha value was not 0x00 or 0xff are incorrectly displayed. Note 2 is the same.

Does anyone know how this issue can be resolved? It's a major blocker for us, as we don't even know of a way of determining programmatically if the device display exhibits the issue.

Nexus 10 display photograph, looks incorrect Nexus 10 DDMS screenshot, looks correct

Was it helpful?

Solution

Translucent windows on Android are expected to contain colors premultiplied by alpha. The blending equation used by the window compositor is:

dest.rgb = src.rgb + dest.rgb*(1 - src.a)

Valid premultiplied colors always have color.rgb <= color.a. If this isn't the case, then the result of the blending equation can be larger than 1.0. In OpenGL ES, if you try to write a color larger than 1.0, it will be clamped to 1.0 (unless you're rendering to a floating-point color buffer). So invalid premultiplied colors often go unnoticed in GL rendering, or when the window compositor uses GL for composition.

But Android doesn't require that devices with specialized composition hardware (which is nearly all Android devices these days) clamp to 1.0 when blending overflows. Most devices do clamp, but the Exynos 5250 in the Nexus 10 doesn't; it does the blending math in 8-bit fixed-point, and wraps on overflow (0xFF + 0x2 == 0x01). It wouldn't surprise me if the Exynos 4412 in the Note 2 behaves the same way.

To fix this you need to have valid premultiplied colors in your framebuffer at the end of the frame. Many apps, including the Android UI framework, do this by making sure any non-opaque inputs (textures, vertex colors, etc.) are premultiplied -- most of the math just works out automatically after that. If you can't ensure premultiplied inputs, you can just add

gl_FragColor.rgb *= gl_FragColor.a;

to the end of your fragment shaders. If you do any blending, you'll need to adjust the blend equations/factors you use.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top