Login
You're viewing the discuss.systems public feed.
  • Oct 3, 2026, 9:41 PM

    @GDCPresoReviews The TL;DR version is that the number of vertices generated makes a _huge_ difference, and the main reason mesh shaders work, tessellation HW semi-works and GS doesn't work at all has to do with the amount of vertex sharing you get out of them.

    💬 1🔄 4⭐ 19

Replies

  • Oct 3, 2026, 9:45 PM

    @GDCPresoReviews In a 2-manifold tri mesh (outside things like normal discontinuities or UV seams) with regular topology, a typical vertex is referenced by around 6 tris.

    Ideally, you have an infinite vertex cache, shade each vertex exactly once, and can set up tris out of that vertex however you want.

    In practice vertex cache sizes are limited and we might want to do things like cut things into fully independent blocks of verts (to simplify lifetime management).

    💬 1🔄 0⭐ 2
  • Oct 3, 2026, 9:46 PM

    @GDCPresoReviews Mesh shaders are pretty much exactly that. You get re-shading (and, more importantly, duplication of the output data) for verts on meshlet boundaries and sharing for inside verts. For regular geometry on mesh shader-style HW (or blocked vertex caches), you essentially greedily build meshlets on the fly from the index list you see, which tends towards more waste.

    💬 1🔄 0⭐ 1
  • Oct 3, 2026, 9:48 PM

    @GDCPresoReviews Tessellation is absolutely horrid when you turn it on but leave all the tess factors at 0̶ 1.

    That will un-share all the verts on the GPU side, so you're re-domain-shading all the verts ~6x (for each non-tessellated tri they participate in) and take ~6 slots worth of vertex data for every source vertex. This really sucks and is the biggest single reason why tess for regular LOD meshes never really took off, it's a giant perf cliff right as you go from no tess to _some_ tess.

    💬 3🔄 0⭐ 2
  • Oct 3, 2026, 9:50 PM

    @GDCPresoReviews If you're always tessellating (e.g. how it's often used for terrain), this is not a problem. You have reshading/duplication at the boundary verts but all the interior verts within a tessellated tri are shared. If your tess factors are large-ish, this is fine; most vertices are still shared.

    💬 1🔄 0⭐ 1
  • Oct 3, 2026, 9:52 PM

    @GDCPresoReviews GS has the same issue where every vertex produced by the GS is essentially unique and un-shared, and on top of that, your output topology is a triangle strip, which also doesn't let you induce much data sharing _within_ a GS evaluation.

    Some special cases work. E.g. using GS to turn points into sprites is decent, a quad is a 2-tri strip and the 4 corner verts were never gonna be shared with other points anyway.

    💬 4🔄 0⭐ 2
  • Oct 3, 2026, 9:54 PM

    @GDCPresoReviews Note that this is one of the few common, and endorsed, uses of GS, for this exact reason. It's not adding any waste, and it's doing the data amplification in a shader rather than making a 4x larger vertex buffer or similar that would need to be uploaded and streamed into the shader.

    Okay, final question: I mentioned the memory concerns before, but what memory are we actually talking about? This is where we really get into the weeds.

    💬 2🔄 0⭐ 1
  • Oct 3, 2026, 9:57 PM

    @GDCPresoReviews The key issue that GPUs (and drivers) need to think about a lot, and graphics programmers barely think about at all, is how things like positions or vertex attributes go from vertex shader outputs to pixel/fragment shader inputs.

    💬 1🔄 0⭐ 0
  • Oct 3, 2026, 9:59 PM

    @GDCPresoReviews You're doing vertex shading on some shader core, that produces something like 32-128 vertices in one go that can participate in 100+ triangles, each of these triangles gets rasterized into some number of quads (some of them quite a lot of them, some none), and you need to get all that data from the vertex shader to either the rasterizers (positions) or the pixel/fragment shaders (attributes).

    💬 1🔄 0⭐ 0
  • Oct 3, 2026, 10:01 PM

    @GDCPresoReviews Enough quads are (potentially) generated here that you _need_ a work redistribution stage where the quad shading workload gets distributed to other shader cores.

    Your vertex/domain/geometry shader has vertex attribute data, the thing the pixel shader ultimately needs is plane equations for how the attribute varies across the triangle.

    Typically you just use

    v0 + (v1 - v0) * I + (v2 - v0) * J

    where I, J are perspective-corrected barycentric coords for interpolation.

    💬 1🔄 0⭐ 0
  • Oct 3, 2026, 10:04 PM

    @GDCPresoReviews So _somehow, _someone_ needs to go from a block of vertex attributes computed on one shader core to

    1. figure out which tris are involved in a warp/wavefront worth of quads to be dispatched
    2. fetch the 3 verts for every referenced tri and computing these plane equations
    3. stash those plane equations somewhere the pixel/fragment shader can get at them quickly to do that interpolation.

    💬 1🔄 0⭐ 1
  • Oct 3, 2026, 10:07 PM

    @GDCPresoReviews I can get concrete for AMD HW, since all the stuff I'm about to say is in their public docs.

    There's two large(-ish) dedicated banks of SRAM, the "position cache" and "parameter cache" respectively, that are the centralized staging area for shaded vertex positions (->rasterizer) and parameters/attributes (->shaders), respectively.

    These are conceptually connected to every single shader core in the system. (Some caveats, will cover later.)

    💬 1🔄 0⭐ 1
  • Oct 3, 2026, 10:09 PM

    @GDCPresoReviews Then the shader cores have dedicated setup hardware that is used to prepare shader wavefronts that sets up things like barycentrics and also makes sure things like those plane equations are in place.

    This pulls parameter data from the parameter cache, forms the plane equations, and stores them into LDS. (I.e. same bank as used for shared memory in a compute shader). The "interpolation" instrs use the data in LDS to get the interpolated attributes from there, given barycentrics.

    💬 1🔄 0⭐ 0
  • Oct 3, 2026, 10:13 PM

    @GDCPresoReviews The position/parameter caches are a bottleneck.

    Vertex shaders can't export their shaded positions/parameters until there's a free slot to write that data to. They just stall on those export insns until their output space is available.

    They're also shared between _all_ shader cores (again, some caveats later), so relatively high traffic.

    And un-sharing almost all vertices (as you end up doing with low tess factors or GS) massively increases space used per tri in those caches.

    💬 1🔄 0⭐ 0
  • Oct 3, 2026, 10:16 PM

    @GDCPresoReviews "Why don't you just make them bigger then?"
    Broadly, they're as big now as they have to be. It's up to several megabytes now in the big GPUs, that's a lot of dedicated memory for this, and they absolutely don't want to grow it by another 4-6x or whatever to deal with crappy unshared verts everywhere.

    Plus they'd need ~4-6x the BW in and out as well! It's all bad.

    💬 2🔄 0⭐ 0
  • Oct 3, 2026, 10:17 PM

    @GDCPresoReviews Broadly, increasing usage by a double-digit percentage because you lose cache hits from resetting the vertex cache every 128 or 256 tris or whatever, when you shade in big batches: you can absorb that.

    Absorb "suddenly everything is un-shared": not gonna happen at reasonable cost.

    Finally, the thing I've been dodging: it doesn't have to be a single global thing. You can, and people do, split them a bit.

    💬 1🔄 0⭐ 1
  • Oct 3, 2026, 10:20 PM

    @GDCPresoReviews The thing you see in bigger GPUs is that they are sometimes effectively built as like 2 smaller GPUs in a trenchcoat with a shared command processor and memory interface, and then you sometimes have a setup where there's not a full crossbar and not every vertex shader can generate data used by every fragment shader. Lets you build more, smaller caches (better for BW, contention), limits work redistribution.

    💬 1🔄 0⭐ 1
  • Oct 3, 2026, 10:23 PM

    @GDCPresoReviews i.e. say you have 64 shader cores total, you might build that in 2 "hemispheres" where batches are cut up very early on runs of something like 128-256 prims or so, get assigned to a partition (with 32 shader cores), and then it just can't leave that partition whatsoever until it all re-joins at the blend/ROP/output stage.

    Simpler to build, more scalable, but costs you work efficiency, so a delicate balancing act.

    (end infodump!)

    💬 1🔄 0⭐ 0
  • Oct 3, 2026, 10:36 PM

    @GDCPresoReviews bonus note: if you're looking at AMD HW, you might have noticed that a while back, they added support for making vertex attributes half-floats.

    A lot of attrs really don't need to be float32 (e.g. most normals/tangents, some UVs), and making them float16 takes up half the space in the parameter cache, which as noted is often a bottleneck.

    (It also has savings elsewhere, but the parameter cache is usually the most significant one.)

    💬 1🔄 0⭐ 2
  • Oct 3, 2026, 10:48 PM

    @GDCPresoReviews In the other direction, bonus win from mesh shaders:

    "Classic" vertex shaders don't know what tri they belong to. Things like back face culling happen _after_ storage for vertices is committed, and at that point you're stuck even if all of the tris referencing a vertex end up culled.

    Mesh shaders can cull _before_ allocating output space, and actually remove unreferenced verts entirely.

    💬 4🔄 0⭐ 2
  • 💬 0🔄 0⭐ 1
  • Oct 4, 2026, 9:36 AM

    @rygorous @GDCPresoReviews though the output space allocation strategies also vary by hardware. NVIDIA's current architectures do use a pre allocation (think shared memory) before the mesh shader is run. This is worst case outputs. (And also true for vertex shaders). As result it can limit occupancy.

    From that, the surviving subset is then passed through pipeline.

    NV doesn't support non 32-bit outputs, but one can use shader barycentrics and interpolate custom packed attributes.

    💬 1🔄 0⭐ 0
  • Oct 4, 2026, 10:30 AM

    @rygorous

    So it sounds like: if you’re using the GS to:

    1. Generate a many-vertex triangle strip for every input primitive
    2. And every triangle strip has (roughly) the same number of vertices in it

    Then you might be able to get up to half the hit rate of the vertex cache and vertex/parameter buffer utilization/bandwidth as you would get for a traditional fully-connected indexed mesh (because a tri-strip has avg. connectivity = 3, compared to 6)

    💬 2🔄 0⭐ 0
  • 💬 0🔄 0⭐ 0
  • Oct 4, 2026, 8:29 AM

    @rygorous @GDCPresoReviews Programmers aren't used to worrying about internal register bandwidth, because in many cases the HW architects go to huge lengths to either make "enough" bandwidth, or to hide those bandwidths with various cunning mini-caches and bypasses, but it's hugely important in cases like this, and it's why too many interpolants going from VS to PS can be such a surprising perf hit.

    💬 0🔄 0⭐ 1
  • Oct 4, 2026, 8:24 AM

    @rygorous @GDCPresoReviews Just a tweak here - the important part of this "blessed path" is a FIXED amplification factor. Every GS invocation always produces the same number of verts/tris.

    Otherwise, the thing that reassembles the triangles after the GS can't reserve the slots deterministically and your perf goes in the toilet.

    💬 0🔄 0⭐ 0
  • Oct 3, 2026, 10:36 PM

    @rygorous @GDCPresoReviews I believe the other big performance problem with geometry shaders is you end up emitting the geometry serially within very few threads, meaning you potentially have a huge chunk of output (that you also need to reserve if the max declared GS outputs are large, even if the GS outputs little at runtime) with very little parallellism.

    💬 1🔄 0⭐ 0
  • Oct 3, 2026, 10:39 PM

    @bas @GDCPresoReviews Yes, that was mentioned before, and that can explain why GS perf is bad if you have large max output size and normally don't use it, but it does not explain why GS perf is still mostly bad if you have a fixed number of output prims each invocation and stick to it (exceptions like the point sprites I mentioned aside), which is what the original question was about.

    💬 0🔄 0⭐ 1
  • Oct 5, 2026, 10:59 AM

    @rygorous @GDCPresoReviews Graphics programming noob here.
    What's the trade-off of converting a point to a quad sprite using GS vs drawing a quad and displacing it using VS to get the same result?

    💬 0🔄 0⭐ 0
  • Oct 5, 2026, 2:47 PM

    @rygorous "Some special cases work. E.g. using GS to turn points into sprites is decent, a quad is a 2-tri strip and the 4 corner verts were never gonna be shared with other points anyway."

    Oh… this was what I actually wanted GS for. But then they were taken away >_>

    💬 1🔄 0⭐ 0
  • 💬 0🔄 0⭐ 0
  • 💬 0🔄 0⭐ 1
  • 💬 1🔄 0⭐ 0
  • 💬 0🔄 0⭐ 0