Using SBT offsets in GAS in program with only one SBT record?

Is it, um, “illegal”, to create a GAS with SBT offsets different than 0, but then use it in a program with an SBT that ends up containing only one record?

To clarify: of course, if a shader tied to that program attempted to use non-existing SBT records, that’s bad. I know that. HOWEVER, if my shaders do not ask for the SBT record, and instead use only the offset in their calculations, would it be considered a bad practice that would end up becoming deprecated and removed in future versions of Optix?

The formula which calculates the effective SBT index is this:

sbt_index =
    sbt_instance_offset
    + (sbt_geometry_acceleration_structure_index * sbt_stride_from_trace_call)
    + sbt_offset_from_trace_call

from here: https://raytracing-docs.nvidia.com/optix8/guide/index.html#shader_binding_table#accelstruct-sbt

If you have set the SBT offset on a GAS build input, respectively on each primitive in a GAS using the sbtIndexOffsetBuffer (which must contain values in the range [0, numSbtRecords - 1] because these are local to the build input), then there is only one way to get that down to SBT index zero which is by setting the sbt_stride_from_trace_call to zero which in turn would prevent the implementation of different ray types.

That in turn will only work when there are no instances, respectively when all sbtOffset values in all instances are zero as well because that is always added by the sbt_instance_offset in the formula above.

Also note that the sbt_offset_from_trace_call, means the optixTrace SBToffset argument is only 4 bits (see the Limits chapter) which means there can only be 16 different ray types, which shouldn’t be a problem because you can implement different behaviors in one ray type as well.

All the SBT offsets are unsigned so there isn’t any way to subtract the different inputs in that formula.

So in terms of the above formula, if you want sbt_index == 0, but have sbt_geometry_acceleration_structure_index != 0, then you must have sbt_instance_offset == 0, sbt_stride_from_trace_call == 0, and sbt_offset_from_trace_call == 0.

HOWEVER, if my shaders do not ask for the SBT record

I assume you mean you’re not calling optixGetSbtDataPointer to get the additional data stored inside SBT hit record in that case?
If you store per geometry data on the SBT hit records, redirecting the SBT index wouldn’t be able to access the proper vertex attributes then, so all you could do is a hit/miss condition plus all attributes coming from the intersection.

I wouldn’t do that because it’s too restrictive on the SBT layout. Just always setup your SBT exactly as required for your specific render graph instead.

There are other ways to access the GAS data when using an IAS->GAS structure (see the example below).

I’m often using GAS with exactly one SBT record, so sbt_geometry_acceleration_structure_index == 0, and then select the SBT hit record with the OptixInstance sbtOffset only, which selects the material shader.
If that needs to be changed, I simply update the IAS with new sbtOffset values.
All other data is accessed via the user defined OptixInstance instanceId value!
An example doing that is here (rtigo12): https://forums.developer.nvidia.com/t/optix-advanced-samples-on-github/48410/15
When you change the BXDF inside the GUI, the respective instances using that material reference will be updated.

Here are some related threads about how to setup SBTs in different ways. Follow the links at the bottom of this one:
https://forums.developer.nvidia.com/t/question-about-instance-acceleartion-struction/283898/4

Hey. Thanks for the explanation! From what I’m gathering, even if the scenario I brought was not “illegal”, it would be pretty much a bad idea anyway :D .

So, for now, I will stick to doing one SBT record only, no SBT offsets, and instead use primitive index offsets to have a mechanism to find out which mesh I’m on, and then decide what to do with the triangle being hit on closest_hit.

(If you’re wondering why I simply don’t just use SBTs for that, as expected, it’s because SBTs for some reason are not working in my environment, and I’m a bit short on time to deliver my fix…)

There shouldn’t be any problem setting up a correctly working SBT matching the acceleration structures in your scene and the algorithmic requirements.

a mechanism to find out which mesh I’m on, and then decide what to do with the triangle being hit on closest_hit.

Detecting what mesh a primitive belongs to can be done in various ways in OptiX.

If each mesh is a different build input inside a single GAS, then each of these build inputs has one or more SBT records. Means you can add SBT hit record data which uniquely identifies each mesh.

If there is only one GAS inside the scene, then the primitive index uniquely identifies each primitive so that can be used to look-up a mesh identifier stored as additional per primitive attribute. I usually do that by using uint4 triangle indices where .xyz-components are the vertex indices and the .w-component is some other data.
(Reading a 4-component vector is also faster than reading a 3-component vector because latter is read as three scalars while 2- and 4-component vectors can be read with a single vectorized instruction by the hardware.)

When a two-level IAS->GAS structure is used there are two straightforward ways:
If each mesh is a separate GAS with only one SBT record, then either the user defined instanceId (read with optixGetInstanceId on device side) or its index inside the IAS, that is, the order in which the OptixInstances are given to the IAS build input (read with optixGetInstanceIndex) uniquely identify the instance.
That would even be able to differentiate the same GAS instanced multiple times. (That’s why I’m assigning the material at instance level.)

This IAS->GAS scene structure is also faster than a single GAS on RTX boards because traversal through an IAS->GAS structure is fully hardware accelerated.

And then there would be combinations of the two methods above.