Module re_renderer::renderer::lines

source ·
Expand description

Line renderer for efficient rendering of many line(strips)

§How it works:

Each drawn line strip consists of a series of quads and all quads are rendered in a single draw call. The only data we upload are the user provided positions (the “skeleton” of the line so to speak) and line strip wide configurations. The quads are oriented and spanned in a vertex shader.

It is tempting to use instancing and store per-instance (==quad) data in a instance-stepped vertex buffer. However, GPUs are notoriously bad at processing instances with a small batch size as various people point out .

Instead, we use a single (un-instanced) triangle list draw call and use the vertex id to orient ourselves in the vertex shader (e.g. the index of the current quad is vertex_idx / 6 etc.). Our triangle list topology pretends that there is only a single strip, but in reality we want to render several in one draw call. So every time a new line strip starts (except on the first strip) we need to discard a quad by collapsing vertices into their predecessors.

All data we fetch in the vertex shader is uploaded as textures in order to maintain WebGL compatibility. (at the full webgpu feature level we could use raw buffers instead which are easier to handle and a better match for our access pattern)

Data is provided in two separate textures, the “position data texture” and the “line strip texture”. The “line strip texture” contains packed information over properties that are global to a single strip (see gpu_data::LineStripInfo) Data in the “position data texture” is laid out a follows (see gpu_data::PositionRadius):

                  ___________________________________________________________________
position data    | pos, strip_idx | pos, strip_idx | pos, strip_idx | pos, strip_idx | …
                  ___________________________________________________________________
(vertex shader)  |             quad 0              |              quad 2             |
                                   ______________________________________________________________
                                  |               quad 1            |              quad 3        | …

§Why not a triangle strip instead if list?

As long as we’re not able to restart the strip (requires indices!), we can’t discard a quad in a triangle strip setup. However, this could be solved with an index buffer which has the ability to restart triangle strips (something we haven’t tried yet).

Another much more tricky issue is handling of line joints: Let’s have a look at a corner between two line positions (line positions marked with X)

o--------------------------o
                           /
X=================X       /
                 //      /
o---------o     //      /
         /     //      /
        o      X      o

The problem is that the top right corner would move further and further outward as we decrease the angle of the joint. Instead, we generate overlapping, detached quads and handle line joints as cut-outs in the fragment shader.

§Line start/end caps (arrows/etc.)

Yet another place where our triangle strip comes in handy is that we can take triangles from superfluous quads to form pointy arrows. Again, we keep all the geometry calculating logic in the vertex shader.

For all batches, independent whether we use caps or not our topology is as follow: _________________________________________________ \ | |\ | |
\ | … n strip quads … | \ | … m strip quads … |
||||__
(start cap triangle only) (start+end triangle) (end triangle only)

§Things we might try in the future

  • more line properties
  • more per-position attributes
  • experiment with indexed primitives to lower amount of vertices processed
    • note that this would let us remove the degenerated quads between lines, making the approach cleaner and removing the “restart bit”

Modules§

Structs§

Enums§