Anti-aliasing on OpenGL ES 2.0

I’m currently implementing an OpenGL ES 2.0 rendering back-end for Qt’s QPainter vector graphics API. The problem is Anti-Aliasing.

Ideally we’d like to use pixel-coverage rather than multisampling, but this has been removed from the OpenGL ES 2.0 API. So we’re tuck with 4x multisampling. For text rendering, this simply is not goot enough. 4-level multisampling vs 256-level pixel coverage. We have thought up several solutions, but none are ideal.

The first is rendering the shape 32 times into a 4x multisampled mask with slightly different offsets and 1/32 for alpha. The second is tesselating the path into trapazoids on CPU & doing coverage calculation in a shader, passing in the slope values of the trap as uniforms). This is probably going to be slow due to the CPU-bound path tesselation.

Does anyone know of a better solution?

Does this only apply to text or do you need a solution for any kind of vector graphics? If both, can you separate them? It seems to me that rendering the shape 32 times with 4x MSAA is overkill. How exactly do you render the mask? Have you considered supersampling?

We need a solution for vector graphics in general. For small font sizes we generally use a software font engine to generate glyphs which are cached in a texture and blended when rendering. This lets us use font-hinting - which gives much better quality than regular anti-aliasing. So small fonts aren’t really a problem. At larger font sizes, the glyphs are converted into vector paths and rendered using the same code path as every other complex vector path.

Rendering to the mask would be done by setting an the alpha value of the fragment to 1/n and rendering n times with addative blending enabled. We render into an 8-bit framebuffer (or into a single channel on an RGBA8888 framebuffer) which has previously been cleared to zero. Each time we render, we translate around n points of a circle of radius 0.5 - the center of which is the actual position. Fragments on edges won’t be rendered all the time so the resultant alpha value will be <1.0. Fragments on the inside are rendered every time, so will have an alpha of 1.0. We then draw the shape to the window’s back buffer by drawing the bounding rect with the mask set as a texture input of a fragment shader. I think that’s how it would work anyway.

Supersampling? Do you mean sampling a texture multiple times in a shader?

I meant rendering at a higher resolution, then downsampling.

Doesn’t that have the same performance hit as rendering multiple time? To emulate pixel coverage calculation, we’d have to render at 256x the resolution. I guess it’s only an 8-bit mask we’re rendering into, but even so, it’s going to eat memory.

I’m currently looking into some way we can do the coverage calculation of an arbitary triangle in a fragment shader. Pass the vertex coordinates in as uniforms and render a triangle larger than needed to make sure we get fragments for the edges. Not sure what to do with concave paths - currently we get round having to tesselate concaves by rendering all the vertices as a tri-strip into the stencil buffer with the stencil op set to xor. Fragments in the shape are rendered an odd number of times and fragments outside the shape are rendered an even number of time and the stencil buffer value goes to zero. We then just render the bounding rect with the stencil test on. I’m trying to work out if there’s a similar way we can do this but using blending instead. Although the GL_LOGIC_OP blend equasion has also been removed in ES 2.0…

Rendering multiple times means the geometry has to be processed multiple times and the results need to be blended. Rendering at a higher resolution is more efficient. On the other hand you only get an ordered sample grid, while distributing the samples in a sparse pattern gives higher quality. I don’t think you really need 256x the resolution.

It depends on the target hardware whether 8-bit masks are renderable, similarly it could be that only EGL window surfaces can be multisampled.

I doubt doing the coverage calculation in the fragment shader is going to get you good performance.

You’re probably right about the need for 256x - could probably get away with 64x on high-resolution displays (8x8 per pixel). I just gulp at the thought of allocating such a large buffer. Say it’s 800x480 display with a full-screen window - that’s a 6400x3840 8-bit renderbuffer and 8-bit stencil buffer. I believe the max texture size on SGX is 2048x2048, so we’d have to start getting into tiling before we start - and have to allocate ~50MB worth of buffers. Nope, I really don’t think this is workable.

We’re mainly targeting SGX (OMAP3) as that’s the only development hardware we have. Looked at doing the coverage calculation in a shader and agree - it’s going to be too slow. You’d have to pass in all the triangles as uniforms and calculate the intersection of each triangle for the pixel square for each fragment. It’s almost like doing the tesselation for every fragment - no thanks.

I think the only workable solution we have left is to tesselate on the CPU. :frowning:

One last thing - The Wikipedia entry on PowerVR SGX mentions it has geometry shaders, but I can’t find any reference to them in the GL ES 2.0 SDK. Are they supported and are they exposed through any extensions?

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.