Advertisement

The impossible mission of 1 pixel thick lines GL_LINES

Started by August 30, 2024 09:45 PM
35 comments, last by Aybe One 1 week, 2 days ago

It is somewhat an achievement to go from ~300ms frame time to ~0.5ms .
But no, getting ~42% GPU use for such task is of course nothing to be proud of.

I tried your suggestion of [numthreads(1024, 1, 1)], it's down to 30% GPU now!
If I can shave off an extra 15% so it consumes like GL, i.e. nothing, I'm sold!

Hadn't Unity been doing its crap under the hood, I would have been finished ages ago:

It gets it half right with MSAA but totally wrong when it's off…

Without MSAA:

With MSAA:


Drawn in some painting software:

Aybe One said:
It is somewhat an achievement to go from ~300ms frame time to ~0.5ms .

Ah ok, but then it was some misunderstanding on my side.
I guess you did not get such speedup only from changing workgroup size from 16x16 to 32x32.

Aybe One said:
Without MSAA:

Looks better to me than

Aybe One said:
Drawn in some painting software:

Notice the painting result never puts two pixels side by side, but the stairstepping is much more noticable.

Why do you feel it's ‘totally wrong’? To draw a line, the line must have some width. If it has width, it will intersect two cells. If we apply proper rounding, some of those partially intersected cells have to be drawn on both sides.

Advertisement

Aybe One said:
Hadn't Unity been doing its crap under the hood, I would have been finished ages ago

It is the interface you're using.

It isn't a pixel rendering system, but you're treating it like one. Most of the time seems to have been spent wrapping your brain around that. 3D rendering systems are not raster graphics. Most of this confusion will drop away once you let raster graphics and pixel-perfect representations go.

The systems are built to render textured shaded polygons in space, not render lines, not generate raster or pixel art. The line drawing routines treat the pixels as area in a volume projection, and do work to estimate the area within the projected space to determine if a fragment / pixel needs to be activated or not. The line rendering system is a costly way to have to simulate it, but it's the system the hardware and drivers are using. Drop all thoughts of frame buffers and arrays of video memory getting displayed, they don't apply in the rendering tools.

The way to improve it is to make a polygonal strip, something wide enough to fit your view frustum taking one pixel when projected out, and render that instead of a line strip. In many ways that type of triangle strip would be less work for the graphics system overall.

frob said:
The way to improve it is to make a polygonal strip, something wide enough to fit your view frustum taking one pixel when projected out, and render that instead of a line strip. In many ways that type of triangle strip would be less work for the graphics system overall.

Afaik that's what the driver is doing already when drawing lines. The two vertices of a segment are duplicated and displaced so each segment forms a quad of given width using two triangles for each segment. Since GPUs can only draw triangles, but no points, lines or quads, the triangles must have some area. Otherwise pixel coverage would be always zero and no pixels would be drawn.

The conversion to quads can be observed by setting a large line width, iirc. It should look like this:

The black quads are generated from the given red line segments.

I don't think a custom implementation could do somethign against the tradeoff of small width: some missing pixels, and large width: some double pixels.

We could only improve the gap on the vertices. But that's not the problem, and eventually the gaps are better with AA on. (Similar to points, which become proper circles with AA on, but are only squares with AA off.)

Thus, if we want no duplicated pixels and no holes, the compute approach is simpler and more efficient. Trying to achieve this with triangles would require careful and complicated construction of geometry considering the pixel grid precisely.

However, the current approach to generate only vertical lines actually is such approach. It should work as desired by turning above axample into this:


You may need to experiment with subpixel accuracy, e.g. moving all vertices by half a pixel, but after some experimentation it should work.

Otherwise i would accept some ‘artifacts’, or go back to the compute approach but making it efficient.

To me, jaggies with MSAA are off, since when it's on, rendering reveals the real line. (another bug?)

Such style isn't that bad but is off the requirements of having exactly 1 pixel thick lines.

Yes, you're totally right, I mistook it for a 2D raster while it's not, it's drastically different.

Other than that, I'd like to move on, almost 3 weeks I'm on it, it's not fun anymore at this point…

Today's experience further tells me I should get the hell out of this trap ASAP:

  • can't reliably profile externally with Unity's fake DX12 on DX11
  • when using it, both equally consume the same GPU…
  • CS seems to have a minimal GPU budget that you can't go below of
  • CS approach is systematically +10% than GL approach

Basically, I think I'm doing “turd polishing” at this point…

I harnessed the drawing of what I need in a non-2D surface, though cheap and ridiculous, GL is faster.

Sounds like I should just stay reasonable, be happy with what I have and move on.

Thank you all, I finally fixed everything, it looks neat and is fast! 🙂

Before:

MSAA is able to fix the zoomed out view but it fails on simpler waves making them look as if they had high frequency content in them. It also thins out some of the sections that are not meant to be.

After:

No need for MSAA, simple waves are as accurate as possible and zoomed out view as crispy as possible:

And there's no more constant red highlights in the profiler, it runs fast enough now.

Thanks! 🥳

Advertisement