Hello everyone, welcome to Flashtem, the graphics devrim, we're kicking it off today with
a talk about Jamadashidus in PNBK with WebPoly.
So about me for those you don't know, because I haven't been to Flashtem in like a decade.
I mean, it's Feifexbrand, I work for Clapper, I've been on a free desktop for quite a few
years now.
I was at Intel for a while where I did the Intel Vulcan driver there for Linux, I did a bunch
of other stuff, compiler stuff, various Intel bits, a bunch of Vulcan common code that
I've raised now using, and then I've been at Clapper's since 2022, where I worked basically
anyone lap Linux graphics stack where I'm needed.
I wrote the NVK library stack from video hardware, and I spent the last two or three
months helping out the PNBK team, trying to get stuff in a little bit of a shape there,
and we're going to talk a little bit about that today.
So what did Jamadashidus, that's the first question, because we need to talk about what
these are and the wide bit hold and why this is interesting stuff.
So Jamadashidus sit right before the actualization, so you have your vertex shading, you
might have some test solution, if you have a high enough API version for that, and then
you have potentially a Jamadashidus that runs out and put it aside.
And unlike some of the other shaders stages, Jamadashidus shaders act pro-primitive, so
they consume primitives, they produce primitives, they might consume, and produce different
primitive types, like they can consume fire calls, produce points, consume points, produce
triangles, and they can also dynamically change the number of primitives, so you can consume
one point and produce seven triangles.
You can have a geometry that sometimes produces seven triangles, sometimes produces zero
triangles.
So here's one example of a geometry shader that takes a point and produces two triangles
to make a square.
Why might you want to do this?
Well, maybe you don't trust point sprites, and you want to do some sort of environment
effect, and it's easier to show a pixel, points down your pipeline, and you can do everything
with a geometry.
And the way this works is you have your normal outputs, like in this case, geoposition,
and you write to it, and then you do emit vortex, and that causes that fully text to be
kicked out, and then you set up your outputs again, and you emit vortex, and you set
up your outputs again, and you emit vortex.
Here is another example that turns a solid triangle into lines, so we walk over all the
vertices, and we do the first one, second one, and third one, and then back to the
first one again, as a line loop.
And voila, we can do lines.
Here's one that duplicates triangle, so you put it one triangle, it gives you out two.
These are just some examples of things that you could potentially do with geometry shaders.
In the way I will, again, you can talk again, but also the application can do whatever
it wants.
I want to hear the tell them what they can and cannot do, but this is some examples.
OK, so let's talk about why geometry shaders are hard.
First of all, like I said, the number of proofs you can emit is dynamic, so emit vortex
and primitive might be in control for.
So you might have a loop that limits a bunch of primitives, and the loop has a loop
counter, and the loop counter might not be known at the pile time.
So we don't necessarily know when we're looking at the shader code or the object that
we've compiled, how many primitives we're going to get out where we primitive we put in.
We try to figure the certain pile time if we can, and if we can unwall the loops, and
we can do all things, then oftentimes we can figure this out, but sometimes we can't,
and we have to be able to deal with that.
We also have to deal with partial primitives, so if you are emitting vertices, if you're
emitting triangles, and you emit five vertices, that only gets you on primitive, so you
can end up with sort of redundant stuff at the end of your call, and we have to deal
with that.
We also have to be able to output primitives in a predictable order, and this is one of the
hardest problems with geometry shaders, and why everybody's built, and why it just
generally sucks.
There's a few different reasons for this.
One is that geometry shaders can feed into transform feedback, which if you are a bit
of how deep you're feeling, and if you're cringing right now at the thought of that API,
but there it is.
We need to be able to support that.
Transform feedback is something that goes back quite a long ways in Vulcan, and basically
it's quite a long ways in OpenGL, and we added it to Vulcan, unfortunately, and what
it does is it allows you to ask the hardware to please take all of the data that's coming
out of the geometry pipeline and wait it into a buffer for me.
What we need to be able to do that, and the order that we, all right, that data out needs
to be predictable, because applications are then going to weed that data, and do something
with it, and we need to know with this.
There's also some things that are order-dependent in the actualization pipeline, so
color-blending, for instance, is not necessarily something that you can do in overtry
order and get the same results.
Some color-blending equations, like just adding or doing a minimum max, are predictable,
but some of them are not.
Deptesting is also not necessarily order-dependent.
It's mostly order-dependent, depending on your values, but if you're using a fixed-point
depth format, like our 16 or 24-bit, then whether you use greater or greater equal, gives
you a first primitive wins or the last primitive wins rule, and if you don't know what
your primitive order is, you can get unpredictable results with that testing as well.
So it's really important that we output stuff in the correct order.
And then finally, we also don't know, then I'm going to put primitives necessarily.
So this isn't the worst of the problems, but it is something that we have to be able
to deal with in the implementation.
And this is mostly due to dry and direct, although there's a couple of other cases where
that pops up as well.
Okay, so let's talk about geometry shaders on Mali, which is what this is what we're trying
to do here.
So, so the Vahal Mali added support to enable geometry on a shading, they added layer
to rendering in hardware, which is really nice not having that would have been a pain.
But they added the built-like GLA and GL primitive idea from a vertex shader.
That is the sum total of Mali's geometry shaders' support.
Everything else is just left as an exercise to the leader, and unfortunately that's us.
So, how are we going to do this?
And the answer is computer shaders, lots of computer shaders, and also lots of dry
and direct, which we'll get into that in a bit.
So this is where Liv Poly comes in.
What is Liv Poly?
So Liv Poly is a library that was really, really written as part of the Asahi driver stack
for the Apple M1s by Osuluers and Sweet, and she did it because she was trying to build
a basically desktop capable Vulcan driver for what is effectively mobile silicon.
And so, obviously she needed to be able to support all the desktop APIs because people want
to play with play Steam games, and so we need to be able to support Java shaders' facilitation
shaders' works.
And then, Mali did a bunch of work to pull Liv Poly out so that it can be used by other
drivers, and we've since done some cleaning up to get it into the shape where it's more
broadly usable.
It is written in a combination of open CL and no.
The manual and those stuff is mostly for the glowing passes, we need to be able to take
a Java shader and tone it into the sequence of compute shaders that needs to run.
There's some sort of manual manipulation that needs to happen.
But then the actual details of how do we do vertex fetching, how do we deal with all the stuff
we have in-depth buffers, the test later, which we're not going to be talking about that
detail today, all of that stuff is just written in the open CL, and the open CL kernel
then get compiled down into no builder or straight into open CL kernels.
Currently, the M1 support test solution, but we haven't actually pulled that out into
Liv Poly yet, there's still a lot of that lives in the M1 driver, but it is in the
low map to do that. And then with this, we can implement geometry shaders, transform feedback,
and all of the other stuff that is generally a pain on tiling GPUs because we're able to do in
commuteshaves.
Okay, so how does Liv Poly solve these problems? So, I had a list of problems, I'm going to
kind of go through them and talk about how they get solved. First, let's look at every simple case,
because this case is actually getting this case where it is actually really important for
getting any kind of performance out of this in the real world.
So, let's say we have a very simple case. We have a regular, not indirect,
do I? So, no funny business there. There's no primitive we start because that screws up everything,
because that's a wonderful API. The geometry shader produces a compile time known number of
primitives. So, we can look at the geometry shader and we can see there's one primitive or two
primitives. And there's no transform feedback. Okay, so if all of this happens,
then we can compute the number of output primitives up front, which is really nice because this
allows us to allocate memory up front, because one of the other problems that we have here is that
the output of my vertex shader, which is now a compute shader, has to go somewhere to be
fed into the geometry shader. And it's nice to be able to allocate that with a fixed size.
And this also means we can use direct dispatches and drives because we don't need to, we know
all of the numbers of things up front. But the really nice thing here is that we can actually
run the entire geometry as a vertex shader. So, the vertex shader you get out looks like this.
It just switches on the vertex ID, the tomons which put of the primitive that it's in,
based off the vertex ID, and runs that particular chunk of the geometry shader. And this allows
us to run the entire geometry shader, as if it's part of the vertex shader. And so this makes
a bunch of cases with geometry shaders basically free. In particular, when you're dealing with
transform feedback, this means that we can do transform feedback with a geometry shader, but the
geometry shader does nothing, and it doesn't actually add any overhead. Because even with
out having to deal with geometry shaders, transform feedback is still a giant pain, because
mobile hardware does not like doing memory writes from vertex shaders, which is what you have to
do for it. So, what we do here is we can just insert a dummy geometry shader, it just does
simplest geometry shader you can possibly do. And we do transform feedback as part of the,
now compute vertex shader that feeds into this dummy geometry shader, and the dummy geometry
shader compiles down to basically just copy data, because even the switch can compile away
if the switch is just copying data. So, this takes the single most common geometry shader case,
and basically eliminates it for us. Unfortunately, that's the simple case.
The complicated case is a lot more complicated. So, let's talk about the different problems
I said that make geometry shaders hard. First is that we don't necessarily know
the vertex in primitive counts. One way this can happen is we're trying to act. So, with
dry-indirect, you have all of the vertex buffers and index buffers that get set up, and then the actual
drawers is in a way of GPU visible memory that contains your base vertex, your vertex count,
base instance, instance count, and the hardware reads it out of memory, and then draws that amount.
But we as a dry-of-oh not, that have visibility into how much drawing is actually being done.
There's also cases where a direct draw can basically end up as an indirect. So,
one example of this is primitive we start, because what primitive we start does is you have an
index draw, so you have an index buffer, and the index buffer has a bunch of indices in it that
tell you which vertex to use, very common in applications that have to do index draw.
And then there's a magic index value that says, actually don't draw anything, we set the
primitive for get whatever half primitive you've started. This is particularly useful if you're using
triangle strips, triangle fans, loops, line loops, et cetera, because you can just do like
five vertices for your triangle strip, and then primitive we start, and then do more vertices,
and then primitive we start, and do a bunch of triangle strips in a single draw, it's very useful for
that. But from an output perspective, it means that we can't actually tell how many vertices
are going, or how many primitives are going to be done. We can tell how many indices are going
in the buffer, but without knowing what those indices are, we don't know how many times it we
start, and so we don't know how many primitives that actually turns into. Another case we can get into
vectors, we're not diving into this in super great detail today, but test relationships also
effectively produce an indirect draw going into the geometry. It's the same solution for all of these,
but yeah, so how do we solve this? We have this thing called poly heat, and the way this works
is the inside the driver you detect the indirect case where you're going to need the heat,
and you allocate a big heat the first time you ever need one, and then the driver is about
a kits out of it. Currently there is no solution for growing this heat dynamically. One could be
developed. There are tricks on Molly, for instance, to do flush out the tile buffer and do these
incremental rendering, where we realize that we're getting too much memory, and so we flush
the tile buffer, and then we start again, we have not hooked this into this heat. It could
theoretically be done, but it's a bunch of work that hasn't been done. For the case in the M1,
a list I found a heat value that lets you play every D3D title she's tried, and call it good.
And honestly, as long as the set size isn't too big, it's probably a fine solution,
because most applications aren't using geometry shows that heavily, and so you really only
penalizing the ones that are using this feature, so it's kind of fine. If you're on a phone or whatever,
it's going to be the one game you have on your screen that needs this buffer, it's not going to be
your system composure or any of that stuff. So it's kind of fine to just allocate a bunch of memory
the first time you see a weird geometry to share the case. And then the other thing we do,
to deal with unknown counts, is that we do the actual setup as a computer shape. So we have a
tiny little 1 by 1 by 1 computer, that runs, it looks at the indirect wire data, it does the calculations
for how much memory we need to allocate and allocate some of the heat. And then we feed that into
the indirect wire. We feed that into the wire. So how do we drop the heat?
This is one of the problems that we have, is that, okay, so we can allocate stuff on the heat,
that's easy enough, that's just an atomic. We can do atomic. How do we actually feed this into the
3D hardware? Because we're doing a bunch of compute shaders, but at the end of the day,
this is going into very, very fixed function hardware. And when a list was originally doing this,
unlike unmoly, where we have this command stream, where we can use, that can do memory loads
and do state setup and stuff, the M1 doesn't have that. The only thing the M1 has is dry indirect,
and it's very, very baked into hardware. It was just like a dry indirect call that you pass it,
the 0.0 to the 45D words that describe your indirect wire, and you get an indirect wire.
So we need to be able to feed into that. For anything that is just a compute shader,
so if we're feeding from a vertex shader into a geometry shader, we're feeding from a
tasselation shader into geometry shader, anytime with the destination is a compute shader,
we can just pass pointers around. We have 64, but pointers that we can use in the hardware,
we can pass this around, it all works. Votex data is typically fetched using global leads,
so we don't really need to worry about that side, that's also kind of left up to the driver,
that doesn't really come from the heat. But at the end, we need to build feed to the
afterizer, and the afterizer is going to be fairly fixed. So the way we do this is we're trying to
direct. So the Votex data, like I said, is the Votex shader is responsible for loading it,
you as a driver, we have to figure out how that works. That's fine, that's the easy point.
The Votex, the output of the Votex shader then goes in memory someplace, that goes into a
pointer, that's fine, we can handle that. The point point is the index buffer. So there's a
bunch of these cases that end up generating index buffer is that feed into the next thing. In fact,
most of Liv Poly works in terms of index buffers. The hardware needs to be able to access the
index buffer, and the index buffer lives somewhere inside this heat. And the way that we deal with that
is that whenever we're doing an indirect raw, we have the actual like API level index buffer binding
points to the base of the heat, and we use base index to offset to wherever we actually live
some of the heat. And so by doing this, we don't need to be able to pass around arbitrary GPU
pointers that come from computer shaders into that fixed function hardware. We can pass around
the base of our allocation, which you know on the CPU, and then we can use the base index from the
indirect raw in order to actually get to that offset in the buffer. So O index buffer can live
anywhere we want them to, and we just do that to get everything into a direct and off we go.
And we also assume that our code is correct, and that we already did the balance checking
for index buffers, and so when we bind this index buffer, we literally just bind the entire heat.
And we just assume again that we're not generating bad indices of bad sizes,
which you know, hopefully we can touch it on code, but you know who knows.
And then there's also some cases we'll use dispatch indirect. So if we're feeding from a
computer shader into another computer, and we don't know how many parameters we have,
then we might not know how many computer applications we need for the second thing,
and so that's a dispatch indirect. And it's basically the same story. We have the one by one
computer that sets up allocations memory sets at parameters. It can also set up the indirect dispatch.
OK, so that problem is sorted. We can deal with, hopefully, amounts of geometry,
we don't know at CPU time. How do we deal with primitive ordering? So this is also a giant pain.
And again, if the number of outputs is fixed, everything is easy. We can literally just multiply.
Oh, good. We have the number of primitives proved, we can just place them in the output
buffer based off of that. Everything's good. If not, we have to do geometry shading in flow passes.
The first pass runs an extremely stripped down version of the geometry shader,
where we've deleted everything except counting the number of primitives. All it does is it runs
enough geometry shader to figure out how many primitives that particularly geometry
is going to do. Hopefully, if everything folds nicely, this just turns into writing out a uniform
or something, because maybe you have some loop inside your geometry shader that it's primitives,
really all we need to do is the loop that. So hopefully, it's a very tiny shader that isn't
actually doing anything interesting. But we run this computer, it figures out the number of
primitives, and it writes that out to memory. Then we run an algorithm called a prefix sum.
And a prefix sum just takes a sequence of integers, and it produces, for each one of those integers,
the sum of all of the integers before. It's a fairly standard algorithmic computer science.
It's well understood how to parallelize this. And we have a prefix sum algorithm that's written as an
opensial kernel that uses subgroupops and shared memory as needed to try and is efficiently as
possible to a prefix sum. The reason why we need to do this is because we need to know what each
output primitive goes in our output buffer in order. And so we pushed right out the number of
primitives, and then we prefix sum, and that tells us at each primitive index where that primitive
needs to go in the output buffer. Once that's done, then we can run the final geometry average shader.
And this geometry shader is going to then do a bunch of geometry sharing stuff, do a bunch of calculations,
and write out at the end each primitive in the output buffer. And then the vertex shader in this case is
just a pass through. So this is not the case we can run the geometry shader, as if it were the
vertex shader we have to have a vertex shader that then copies from the data output by the
geometry shader into the vectorizer in our field. Often on these GPUs, like a molly we could
feel radically on some generations feed the vectorizer directly ourselves, but on some of them we
really need to want to rotate shader anyway. A vertex shader that just copies data is not particularly
expensive, it's fine, but we do need something there to take from an output buffer and put it into
the last lesson. And there are also some simple cases where we might need to do all of this,
even if we don't have a geometry shader. One is transform feedback, like I mentioned before,
vertex shaders that have side effects, like writing goal memory, doing atomic things like that.
Those are very unfriendly to tie with GPUs, because titles like to re-run vertex shaders
total, vertex shaders can end up getting run in an arbitrary number of times. And so you really
want to avoid doing side effecty things in them, because that gets way c in weird. And
by doing this we can just do the transform feedback as part of the geometry shader, which runs
before the vertex shader. And we get transform feedback fairly easily. Doing transform feedback
from a compute shader is relatively easy compared to some of the other options. There's also
some pipeline statistics that are easier to compute this way. So there's pipeline statistics for
things like primitive generated. And even if you don't have a vertex shader, if you have,
it is if you don't have a geometry shader. It might just be the number of
vertices divided by something, but it might get more complicated than that, especially if you
have a primitive result. And so we just turn on the geometry shader pass in that case.
Okay. Now let's talk about what this looks like inside the driver. So how does integrate?
So you have all this code. All of this is living in a show folder. It's all implemented. You can
basically just turn it on, but just turn it on is not as easy as it sounds. So what does this look
like inside the driver? A lot of the work to hook this up wasn't actually work on lipholi
or work on shader compilers or any of that. It was mostly just breaking assumptions inside the NVK.
So when the NVK was brought up, it was brought up on hardware that is basically designed to
geolius through that one. So Votex shaders, fragment shaders, computers. And it made a lot of
assumptions about just only having those three shader stages. One thing that is assumed is
assumed that all API stages map one to one to the hardware stage. So there were various places
in the state setup code where instead of passing around the actual API stage that we're looking
at or passing around pointers to the data structures containing bindings, we just passed the
shader around and looked at the stage from the shader and used that to go fetch stuff.
That isn't going to work. Another thing that we assume to compute shaders are computers.
But now we have computers that are not compute shaders. So we had to break some assumptions there.
We assume that Votex shaders are always a Votex shader. Again, not a valid assumption.
And we assume that jabbit shape always comes from the Vulcan API, which again isn't a valid assumption
anyone. So what do we have to do? So the first one, like I said, a lot of this came down to just
various places where we were kind of lazy inside the driver and we said, hey, I don't need to pass
this extra data around and passing around the shader anyway. There's a stage enum inside the shader.
I can just fetch that and look at it and figure out what my bindings are or whatever.
That doesn't work anymore because sometimes you shader stage is not what your shader stage looks like it should be.
Mace of shader geometry does not exist after glowing.
If you look at the Vulcan shader object, you can see that it's a VK geometry shader and if you poke
it that bit. But if you look at the thing that the back end compiler produced, it produced computers.
Not jabbit shaders. Also the jabbit shaders produces vertex shaders, which are not,
which are kind of vertex shaders, but they also kind of not vertex shaders.
That was confusing inside the driver. So a lot of this came down to just going through each point
that looks at bindings, looks at anything that depends on the shader stage, and intentionally
kind of passing all of that information to the whole way. We're setting up
descriptive sets for jabbit shaders now. So let's pass the point of the jabbit shader state
and pass the shader to the separate things so that we set up the late thing.
And we're not switching non shader stages and we're looking stuff up and getting it long.
We also had some fairly deep assumptions built into the compiler that computers were
the biggest issue here was the way we do push constants and route descriptors.
Where we had a separate push constant layout for 3D stuff and compute stuff,
which used to be fine and a good idea because you need different system values for compute as
you need for 3D. In 3D you don't need a compute shader size. You don't need the compute
shader base indices and similarly in compute you don't need like you base vertex. So we had
these separate things, one for 3D and one for compute. And if the shader stage was made
a shader compute then you got the compute one but we suddenly need to peel the dispatch those
as if 3D shaders. So there were some assumptions that had the compiler that we had to break.
We also had some common system values that we showed between 3D and compute.
That won't hit the same offset and so it would just look at the shader stage and be like,
ah, use this offset now instead of this one but then we would bind that as a 3D shader
and give it system values for 3D shader and it would blow up.
So it was kind of an audit process of going through the shader compile and making sure that we
didn't screw that up on ourselves.
So we had this concept of a vertex shader inside the driver because you have a vertex shader,
you can find them, everyone. And we had this split into two pieces. So one piece is the API
of a vertex shader with a software of a vertex shader and this is the thing that we used
a vertex shader. It's the thing that uses any descriptors that are bound for a vertex stuff.
It's the thing that has to deal with multi-dler which is a weird pan-fast detail where we have to
do some descriptive patching for the inputs in order to get attribute devices correct but there's
some commands between fiddling we have to do for that. But that again only applies to the actual
like API vertex shader. And so we have this stuff, this is what we call the API vertex shader
but this might actually not be a vertex shader. It might be the vertex shader or it might be a computer shader.
And so we had this sort of separate this often to this concept of a vertex shader is the
full shader that runs. Whether it's a computer, the vertex shader doesn't matter,
it's the first one in the pipeline. The second concept we had to have
is a hardware vertex shader. So this is the last shader in the diagram of your pipeline.
And this is the one that actually runs as part of tiling-bending that whole process.
I'm mulling the whole IDVS and this might be the vertex Vulkan shader,
the vertex shader it might not be. It might come from jampe, it might come from deceleration
but it is whatever it is, it's the last shader that runs. And so we had to build this divide
inside the variable that didn't really exist before because we just assumed that vertex shader is
vertex shader and the code was all entangled and we had a very cleanly cut this.
The next issue, which was not obvious to me when I started this, but it became obvious
as we went along, is that you Japanese state does not necessarily come from Vulkan. So they're
not very many dynamic states that impact geometry. When you look at the dynamic state for the
3D rendering pipeline, apart from vertex input bindings, most of it is for acceleration.
Most of it is depth states, blending states, primitive modes, all of this stuff is rationalization
stuff. The only things that actually affect the geometry pipeline, apart from, again, the vertex
bindings, are the primitive topology, do we start neighbor, the index buffer, and the size
of an index in the index buffer. Unfortunately, in the CSF backend, which is for the new
emology piece, we were doing all of the usual graphics library tricks of track the doughty bits,
see when something changes, pull it from the Vulkan API, shove it into the hardware, all of that
usual song in it. But the problem is that the states might actually come from Lidpoly,
which means that it's not coming from the Vulkan runtime, sometimes we need to overwrite all of that
and fetch it from Lidpoly, and sometimes it comes from the Vulkan runtime, depending on whether
or not you have a geometry to transfer feedback or whatever else. So what we end up doing is
plumbing it through the dry itself. So we have this struct that is in entire draw, and when you do
like VKC and VD draw, it constructs one of these, and it passes it onto the one draw, and
those fields for indirects, those fields for all of these different things that you can control it,
and we can sort of pass it all to a central point. But what we end up having to do is make it so that
these things will part of this sort of draw state that gets passed atomically so that it could be
modified by Lidpoly. Because when you have Lidpoly, you're rendering pipeline actually looks
more like this. So it's not like this linear set of stages. I mean, it kind of is, but it ends
of looking more like you have some vortex data, which you have to provide a way to fetch the
vortex data in the use for this compute shader. That's a problem that's left to the driver.
On some hardware, that means doing some sort of a shader preamble and just doing global memory.
On Mali, we have descriptors for vortex data. And there's a scripted type, we just
populate those, it's part of the descriptor set, and it feeds into the flow shader stage. It's actually
not a whole problem for us to solve. But you have that, it feeds into this big blob of compute shaders.
Some of those compute shaders are going to take shaders, and they fetch the flow texture
descriptors, some of them are geometry out of shaders, and they fetch from geometry descriptors.
And then what that blah blah stuff produces is draws. And the draw on this side and the draw on
that side might not be the same draw. So you might have something that starts off as a regular
plane and simple draw that ends up being an index draw. You might have something that starts
to plane and simple draw that ends up being a draw in direct. You might have a big multi draw
that ends up being a single draw in direct. And so you need to be able to mutate this draw
state as a separate concept from the volcano API, because draws are the output of this pile of
code. And then those draws feed into the fixed function pipeline and rationalization and
fragment shading, just like everything else. But you end up with this thing that's kind of
bolted between your vertex data and your astrization pipeline. You have this extra blah blah stuff
that you have to insert whenever you have one of these cases where you need lip poly.
Okay, that's a lot of work. Bunch of refactoring is a bunch of churning the driver. What do you get out of it?
So, what lip poly can do? Lip poly can do geometry. It can do transform feedback.
Once you have everything running in computer shader and you've sorted out all the problems to get
your pivotors in the way to order doing transform feedback is actually pretty easy. You have to
pass a little bit of extra data for your buffer bindings. But otherwise it's just global data rights
from the geometry shader. It can also do pipelines and statistics for you. So again,
things like primitive counts, stuff like that that you might not have, and might not even make sense
in a Tyler because Tyler is in primitive counts of weird because it all gets bins. We can
implement that in a consistent way that makes it look like any other, any other like desktop driver.
You get the same statistics up. And one of these days we'll get test relations moved into lip poly so
you'll get that too. So, let's talk about where we are. Lip poly is just separate from
EGX. It's already been pulled out. I had to do a bunch of work on it to get it working on Molly because
there were some Apple GPU assumptions in there like it assumed. 1,024 work group sizes. It assumed
32 wide subgroups and some other stuff in some of the, particularly the prefix sum algorithm
and some of the algorithms around. Primitive we start had did subgroup ups and they had a bunch of
assumptions about work group sizes and subgroup sizes that don't actually apply the pancake. So we
had to fix that and generalize all of that. That work has been done. It's upstream. Through factors
are done. We managed to do without waking us out here. We'll get. The pair of equally factors that
I talked about about 50% upstream, about 50% of it is still living in a branch because I got this
all working and then had the both under something else that was pressing and I haven't gotten back
to it, but we'll be over the course of the next month or two. Yeah, let's look at what we're
doing. Mostly we're going to branch needs a little bit cleaning up. The transform people can
queries. We know they work because they work for Ajx. I know how to hook them up. It's just
the matter of like actually adding all of the volcanometry points and plumbing it through.
There shouldn't be any hard engineering work there, but there is some work that needs to be done.
But it is mostly working. We have geometry training happening on PNVK. It is passing
thousands of tests. There's a couple of bugs that have been fixed since the last time I
re-based my branch, so that should be down to like 20 test failures. I think once I re-based.
And yeah, you too could have JavaScript to share this. If you want them.
Mostly I expect this to be interesting to Molly people and people who are working on an
Apple hardware. But if you have some other title, would you have to share your giant pain?
This might be easier. I don't know. And that's it. Any questions?
Go ahead. I don't think we have Mike.
You want to make the version. When you go to the site, you want to ask an invitation and you want
to run a very small, don't share it in all steps. How do you make the resolution of the
geometry? Okay. So the question is how do we actually produce this geometry
that just writes out the number of primitives? It's actually pretty easy. So we already have
some passes in Mesa for various hardware. It's pretty common for hardware to want to know the
number of primitives. So the way it looks like in GLSL as you have this in it primitive thing.
But there's a bunch of hardware that the way that it works is you emit like you pass it in index,
like you say, this is the 0th primitive, this is the second primitive, this is the third primitive,
and then at the end you say, and I had four primitives. And so we already have a lowing pass,
which turns these in it primitive calls into basically adds a counter for the number of primitives,
and it increments that counter, and then at the very end of the shader, it has an intrinsic
to write the counter out. Well, what we do is we just take that intrinsic to write the counter out
and we turn it into something that writes to memory, and then we delete the rest of the shader.
So if you just delete all of the other stuff that has side effects, and you only leave the
one thing that writes memory, then the compiler said code pass will just delete the rest of the shader
and, well, you have this simple version. And then looping rolling in some of those passes
are hopefully going to be smart enough to be able to turn that into just the loop counter or something.
But if it can't learning a loop to just add an integer isn't actually that expensive,
so it's kind of fine. But yeah, it's a problem that looks like a code. Like, oh my gosh,
we're going to take this geometry here and we do sit, but it really just comes down to
running this pass, doing something useful with the final intrinsic at the end, and then
deleting some things and letting the compiler dead code pass go to town.
So what happens if that one mentioned buffer does run out? What does that look like from a
color sign? What happens if the buffer looks out? Lend out. Um, things probably blow up.
It's, yeah, we, I think we could probably fairly easily detect that case and just stop
knitting geometry at that point. But, um, that's kind of the best that we can do without
doing a whole giant, like, super dynamic, stop the entire pipeline, flush everything out,
we started. Like I said, that kind of already exists for Molly because Tyler GPUs tend to run
into these problems, and so there's an annoying amount of engineering effort that has gone into
being able to flush the Tyler and restart on some of these hardware, we have something that
has to do that for Molly. Um, but then tying that into the pile of computers that we have
and making it so that those are also restartable. Not as easy as one would like it to be.
It is probably possible if we run into this in the wild and we need a buffer that's way bigger
than anything we're using today than maybe that's worth it. But right now, allocating, you know,
64 megabytes or something, the moment we see a geometry to, is good enough for the X12 titles,
so it's good enough for anything you're going to run on Molly.
Good.
I think he said that it's possible on Molly to feed the rasterized directly, like you can just
write something so on ship, so on ship memory and then that. So, okay, asking about it, feeding
the rasterized directly. So, Molly has gone through a series of changes regarding how the rasterized
it gets fed. Um, on the super old ones, the Votex shader was literally just a compute shader
that ran over every Votex and produced the outputs in a special buffer format, and that's what
the Tyler consumed. Um, later Molly's has shifted more and more towards the hardware having
more direct management over those buffers. By the time you get to the Molly's, we're running
geometry, this is actually interesting. Um, writing into that buffer for directly isn't really
something that's practical. Um, it is all just memory. We could probably do something, but at that
point, it's probably not really worth it because the caches and stuff will already set up to be
able to handle, taking the output of the Votex shader and shoving it into rasterization, and so
just having that dummy shader that does the copy is probably okay, and not going to mold
performance. Um, and again, we don't, it is getting to the point where it is so tightly managed by
the hardware that trying to write to ourselves is maybe not real practical. Did you have a question?
Yeah, um, to let Molly be, would it be possible to either have a little, little, little, little
little to implement, uh, stable counters from hardware like Mississippi? What counters?
Stipple counters. Um, what do you possibly line stippling? Um, possibly, it depends on,
it depends on what exactly we're trying to do and how we say that, but yes, I think that if you
wanted to do stable, stippling across line lists, or it wouldn't be like, yeah, line strips,
or line loops, um, that would be the place to do it because stipple counting is basically the
prefix sum problem. You would have a compute shader that runs ahead of it and just adds up all of the
line lengths, and then do the same prefix sum to figure out your offsets and then you could figure
out your stipple patterns and that. So I think it would probably be the way it plays to do that.
If we, if we really decide that we care about reproducible stiffering on Molly,
the reason why I'm asking you to do a lot of more well-debuted stuff missed this picture and
about failing test. Yeah, um, again, I'm not sure there's a lot of Molly, a lot of, like,
workloads that really care about that, but I'm going to run on those sheep ears because that tends
to be stuff like cat applications, but, yes, we could probably be line-stop-way. Why not?
Once you have anything to do to the compute shader, we'll do oyster.
So in the case where you have this variable amount of vertices per chi-estred,
you mentioned that you do four passes. My question is,
could you do it just with one pass, but allocate the maximum amount of vertices instead of, of,
course. So the question was, in this case where the four passes,
can we just do it with a single pass, if we allocate the maximum amount. And the answer is no,
because we still have to be able to produce the primitives in the correct order.
So, part of the problem is figuring out the numbers that we can allocate memory, but the other
problem and the whole problem is making sure the primitives are output in the correct order.
We can't just have an atomic that we increment from the compute shaders to pick where the
primitive goes, we have to do all of the primitives from the first geometry shader,
all the primitives from the second geometry shader, all the primitives from the third geometry shader,
because we need predictable pasteurization order, well, depth testing and color blending breaks.
If you use a what, you'll speak up.
So the question is, why would the order change, if we did, I don't know what that was,
the atomic kind of thing, or, like, so the problem is we're trying to process all of them
in parallel, right? We have computers that are lying, we have thousands of primitives that are
happening, and they all have to come out in the way to order. We don't know where they end up going
in the output buffer, for transfer, for, if we're not doing transfer, we could theoretically
make the output buffer space and just skip some primitives at times. That would probably work,
as long as we make the geometry shader write out some sort of a empty primitive that has,
like, if we can do a zero, but that still doesn't work for the transfer in feedback case.
So when we have transfer in feedback, we have to pack it tight, but yes, we could theoretically
emit degenerative primitives for instead of eliminating them and doing the packing step.
Is that going to be more efficient? I don't know, because that means you're going to be
emitting a lot more than we write, because you have to fill your entire maximum space with
these zero size primitives. Maybe it's when, maybe it's not, not sure. The pre-fixum itself is
really fast, so I suspect it would actually probably be a loss in the common case.
And the common case, if we're honest, is either a predictable number or, like, just deleting
some of the primitives. So, maybe we'll be fine, but yeah, it's not a 100% clear where the one
would be there. I think that's probably the last question.
So those lies you've had, like, you mentioned that you are a self-being of the futures that
are going to open CL. Why open CL? Why is that we open CL in the graphics?
That's a very good question. So the question was, I mentioned some of the good internal
shaders have written an open CL, why open CL? And the answer is that it's just a,
hello, a lot with easier to write open CL code than GLSL or some of the other options that we have.
This is especially to, when you have to start poking it memory, because in open CL, we have actual
pointers. And we don't have to try and shoehorn everything into SSBO bindings that get turned into
the scripted set that then get bound. We can just do a point to this ground and it's all good.
It also means that we can use, we can have a header file that gets included in C code on the
CPU and gets included in open CLC code on the GPU and both of those can talk to it. So, like,
all of the sort of sideband state setup stuff for this that controls, that sort of controls it,
it's just structs and you just fill them out with data and you stick them in GPU visible memory
and then the open CL code just goes and leads from those tracks in all looks and we don't have to
like duplicate anything and bangles had been trying to make sure that the GLSL layout is the same as the
C layout, we can just use pointers and it works in exactly things that much easier.
We could do it without that, but it just makes our lives live easier.
What we're talking about for device address, we could do that too and, like, that's basically
what point is turned into, but a lot of it also comes down to the fact that it's really nice to be
able to just write C code. Especially since four things like the test later, they were unit tests,
the unit tests run on the CPU version and we can actually test and develop and debug stuff
and then put it on the GPU and so it's not that it's strictly necessary, like we could do all
of this in GLSL, we could do all of this in, you know, whatever other language, we could do it all
directly in a no-builder, it's just that from a quality of life perspective and a trying to make
it so that we can develop this without pulling the hair out perspective, just writing up a little
lot easier and that's what it comes down to. So I think that's all the time we have, so thank you very much.
Yeah.
Yeah, we are running test box there as well and each is barge is in this morning and it's all the