Question about SBTs

Moving from Optix 6, mainly used from the managed world of .Net I struggle a bit with Optix 7 and how things fit together. It would be great if I could bounce my understanding so far off of someone to see if I am on the right track. I did read the programming guide a few times, worked through articles I could find on the web, and looked at a number of examples (whether working for me at the time or not ;))

I am only dealing with regular triangle geometry and no instancing. But multiple geometry types, each with their own set of multiple hit/miss programs. I need to run different queries against the same geometry and/or swap out geometry during execution. If you think about buildings, a wall getting hit during one query “means” something different than if I run a different query. Windows can be ignored sometimes, but the building hits after not… that sort of problem.

My understanding is that there is one SBT record per Geometry/Mesh/Primitive per RayType. (The Optix 6 raytype translates to a set of Hit programs, a program group.)
Basically there is one SBT for the whole scene.

So if I have one group of 5 meshes and another of 2, I’d have at least 7 records, 14 with 2 ray types. And that really is the only/direct connection between meshes and hit programs. The index of the geometry (or rather the indeces in the vertex/index arrays) and the position in the SBT.

I guess when meshes in one group have 5 different ray types then all of the meshes have to have 5 SBT entries = 35 records total?

Now, if I have different “sets” of programs that need to run depending on the query I run, is this where pipelines come in and I just swap those out to load a different set of programs/program groups, but maintain the SBT?

If the max ray types is now lower, say 3, the way I understand the documentation then I’d still need to have 5 records per geometry, just pointing at different programs, some never used.

Is that the rough outline?

Basically I think I am trying to fit this into an entity relationship model in my head, which probably doesn’t work well to begin with. :)

I really appreciate any insight on this “Optix 7 for enterprise developers”

I am only dealing with regular triangle geometry and no instancing.

Ok, then depending on the number of primitives you have the option in OptiX to either put all triangles into one huge Geometry Acceleration Structure (GAS) or, if that exceeds the maximum primitive limit per GAS (see OptixDeviceProperty), under individual OptixInstance objects inside an Instance Acceleration Structure (IAS) with each instance holding a GAS, which in turn can be one or multiple meshes.

But multiple geometry types, each with their own set of multiple hit/miss programs.

Let’s call “geometry types” == “shapes”, then it’s less confusing with geometric primitive types (triangles, curves, custom).

Please clarify, you mean one hitgroup per shape or multiple hitgroups?
(The former means geometries are sorted by material)

I need to run different queries against the same geometry

That is normally handled with different pipelines and maybe different Shader Binding Tables, but depends on what you store along with the SBT records.

and/or swap out geometry during execution.

That effectively means different acceleration structures. Depending on what you store with the SBT data, your would normally need to update/rebuild these as well, for example to access the triangle indices and additional vertex attributes.

If you think about buildings, a wall getting hit during one query “means” something different than if I run a different query. Windows can be ignored sometimes, but the building hits after not… that sort of problem.

OptiX supports visibility masks which can be used to ignore specific geometry. These are stored at OptixInstance objects which means you could make good use of a top-level IAS for that.

My understanding is that there is one SBT record per Geometry/Mesh/Primitive per RayType. (The Optix 6 raytype translates to a set of Hit programs, a program group.)

Yes, when dealing with a single GAS you have at least one SBT entry per shape per ray type.

But you can index the SBT in a very flexible manner, that is a matter of understanding this formula. This is absolutely crucial! 7.3 SBT + AS

sbt-index = sbt-instance-offset + (sbt-geometry-acceleration-structure-index * sbt-stride-from-trace-call) + sbt-offset-from-trace-call

Have a look at this as well: SBT problem when using multiple GAS objects.

Basically there is one SBT for the whole scene.

Well, normally yes. It’s more like one SBT per pipeline per whatever you consider your set of scene traversables reachable from a top-level traversable.

So if I have one group of 5 meshes and another of 2, I’d have at least 7 records, 14 with 2 ray types.

Yes, for a single GAS.

And that really is the only/direct connection between meshes and hit programs.
The index of the geometry (or rather the indices in the vertex/index arrays) and the position in the SBT.

Yes, for a single GAS. The sbt-index inside the above formular selects the SBT entry.
There is more SBT flexibility when using instances because then you have two more values to work with in that formula.
(There is the instance index and the user defined instance ID you can abuse for indexing tricks.)

I guess when meshes in one group have 5 different ray types then all of the meshes have to have 5 SBT entries = 35 records total?

Correct.
The question you need to ask yourself is, do you really need so many different ray types, or could you just have the same hitgroup shaders handling multiple cases?
What “type” of ray you’re shooting can be encoded in a per-ray payload value or flags bit.

Now, if I have different “sets” of programs that need to run depending on the query I run, is this where pipelines come in and I just swap those out to load a different set of programs/program groups, but maintain the SBT?

No, you cannot change just the pipeline. The SBT contains program records (the 32 bytes header you get with optixSbtRecordPackHeader) of the called programs and these need to match your pipeline.

If the max ray types is now lower, say 3, the way I understand the documentation then I’d still need to have 5 records per geometry, just pointing at different programs, some never used.

Well, the SBT layout might stay identical but you would need to exchange the SBT program record headers anyway when changing between pipelines with different modules and then it makes sense to have pairs of matching pipelines and SBTs.
(Look at how I exchange the hit group headers between opaque and cutout opacity shaders when toggling that material property in my intro examples.)

We’re getting in the “it depends” territory here. :-)

Is that the rough outline?
Basically I think I am trying to fit this into an entity relationship model in my head, which probably doesn’t work well to begin with. :)

Yes, for a single GAS with individual meshes and one or more SBT entry per mesh, the SBT indexing flexibility is limited.

Keep reading the Chapter 7 Shader Binding Tables and esp. the Chapter 7.3 Acceleration Structures in there as often as you need to understand all possibilities of that SBT indexing formula.

David posted a very nice link about that as well in another thread. It’s interactive!
Here’s an external blog post that compares shader binding tables in OptiX 7 to DXR and VKR: https://www.willusher.io/graphics/2019/11/20/the-sbt-three-ways

Thanks a lot, this really helped clear things up as I worked through this over the weekend.

I think there is still some confusion then regarding acceleration structures…

What is the equivalent of Optix 6 Groups? I keep going back and forth. By my understanding a GAS is a single mesh and IAS another instance/copy of that.

As for your question. A window and a wall are different, but they also differ in the context the query is launched.

What is the benefit of ray types then? In theory I could probably to everything in one large script and work just with ray payloads etc. Just to understand the trade off.

What is the equivalent of Optix 6 Groups?

That would be done with Instance Acceleration Structures in OptiX 7.

In OptiX 7 there are only IAS and GAS, in OptiX versions before 7 only the nodes with “Group” in the name held AS, which means Group and GeometryGroup. So GeometryGroup is now handled with GAS and Group is handled with IAS in OptiX 7.

The variable scope handling is rather different though. Group could not hold rtVariables. That was handled at GeometryInstance nodes in the past which live beneath GeometryGroups.
But with the OptixInstance user defined index and ID fields you can store things per OptixInstance easily, so IAS with OptixInstances it’s somehow a combination.

What is the benefit of ray types then?

Dedicated minimal shader programs, where minimal can sometimes mean no program at all, e.g. shadow/visibility rays don’t need a miss program.

My examples are architected for material system flexibility using direct callable programs in a single closest hit program. It would be actually faster to build individual closest hit program per material type, but that got unwieldy (and compiled a lot slower) in the past with a huge number of different materials in a scene.

Again, the “best” solution depends on the use case and requirements. You can architect your renderer as you like.

Thanks again. That really helped clear things up :)