DirectQ 2.0.0 currently uses a 2D texture array for representing lightmaps, but that may be about to change. Before I talk about what it may change into, let's examine the benefits of this current representation.
One of the main bottlenecks in rendering large open scenes in the Quake engine is the inability to batch efficiently. You essentially have two conditions on which you must break a batch - if the diffuse texture changes and if the lightmap texture changes (there may or may not also be a fullbright texture but in practice that will change at the same time as the diffuse texture).
Because of the way Quake packs lightmaps, the texels for any given group of surfaces (otherwise sharing the same diffuse texture) may be scattered among 3, 4, 5 or even 64 different lightmap textures. A basic batching scheme that doesn't take account of this will require over 500 draw calls to handle the Marcher Fortress scene I commonly use as a benchmark, owing to constant batch breaking.
Using a texture array neatly resolves this. By packing all lightmaps into a single texture object, with different lightmaps represented as different slices in this object, you immediately reduce the conditions on which you need to break a batch to 1 - the diffuse texture. That gets the number of draw calls down to 46 - over a 10x improvement (and part of the reason why I can currently hit 1300fps with it).
(In case you're interested, 1.9.0 and earlier used a complex multi-level sort and reverse of texture chains to achieve a similar result, as well as allocating lightmaps in sorted texture order to help ensure that surfaces with the same texture stand a better chance of having the same lightmap - that all goes away with the texture array setup and the resulting code is a lot simpler, cleaner and more robust).
Today while chewing over some unrelated issues it struck me that there was another way. Instead of using a texture array, why not pack everything into a single large regular 2D texture? Individual "lightmaps" can be still represented as 128x128 tiles in this texture, with their own TexSubImage updates for their own tiles, but the actual GPU view of it is as a single large texture. It just needs some offset fixing up for texcoords and updates, but it works and initial tests show that performance is no different than using the array.
The size of the texture is, of course, important. It needs to be a multiple of 128 and should probably (but not necessarily) be some power of two in each dimension. That means padding with some extra unused space, but it's never going to be too much (and relaxing the power of two requirement - guaranteed by DX10/11 class hardware - will make it even less). It will probably be possible to find something else to stash in the unused space too.
Looking at the calculations, with DX9 class hardware you're guaranteed a maximum texture size of 2048x2048 or better - that's enough to hold 256 Quake-sized lightmap-tiles, by which point in time you're going to be running into other format limits anyway. With DX10/11 hardware it only goes higher. A typical id1 map, with - say - 16 lightmaps, just needs a 512x512 texture. That's nothing - external texture packs exist that replace native 64x64 textures with 512x512.
This all forms part of my statement of intent which is to implement compute shader lightmap updates. Having a GPU-side representation that can be easily accessed as an array lookup in shader code will help to make things much easier there, as well as possibly (don't know, can't find info) make this update mechanism available to a broader range of hardware.
It's also interesting that it splits the CPU-side representation of a resource from it's GPU-side representation, and lets each have a different view of it. That's slightly unintuitive but also slightly cool - reducing interdependencies between the two processors (or at least containing them to critical code sections) seems a good thing.
Monday, June 25, 2012
Lightmap Representation
Posted by
mhquake
at
10:27 PM
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment