blob: 5c9d6ab6c86bbd1e2f8f33869b078ded0c6815b1 [file] [log] [blame] [view]
# GPU-AV Shader Instrumentation
Shader instrumentation is the process of taking an existing SPIR-V file from the application and injecting additional SPIR-V instructions.
When we can't statically determine the value that will be used in a shader, GPU-AV adds logic to detect the value and if it catches an invalid instruction, it will not actually execute it.
## Expected behavior during error
When we find an error in the SPIR-V at runtime there are 3 possible behaviour the layer can take
1. Still execute the instruction as normal (chance it will crash/hang everything)
2. Don't execute the instruction (works well if there is not return value to worry about)
3. Try and call a "safe default" version of the instruction
For things such as ray tracing, we decided to go with `2` as it would have the least side effects. The main goal is to make sure we get the error message to the user.
## How Descriptor Indexing is instrumented
As an exaxmple, if the incoming shader was
```glsl
#version 450
#extension GL_EXT_nonuniform_qualifier : enable
layout(set = 0, binding = 1) uniform sampler2D tex[];
layout(location = 0) out vec4 uFragColor;
layout(location = 0) in flat uint index;
void main(){
uFragColor = texture(tex[index], vec2(0, 0));
}
```
it is first ran through a custom pass (`InstBindlessCheckPass`) to inject logic to call a known function, this will look like after
```glsl
vec4 value;
if (inst_bindless_descriptor(/*...*/)) {
value = texture(tex[index], vec2(0.0));
} else {
value = vec4(0.0);
}
uFragColor = value;
```
The next step is to add the `inst_bindless_descriptor` function into the SPIR-V.
Currently, all these functions are found in `gpu/shaders/instrumentation`
```glsl
bool inst_bindless_descriptor(const uint inst_num, const uvec4 stage_info, const uint desc_set,
const uint binding, const uint desc_index, const uint byte_offset) {
// logic
return no_error_found;
}
```
which is compiled with `glslang`'s `--no-link` option. This is done offline and the module is found in the generated directory.
> Note: This uses `Linkage` which is not technically valid Vulkan Shader `SPIR-V`, while debugging the output of the SPIR-V passes, some tools might complain
Now with the two modules, at runtime `GPU-AV` will call into `gpuav::spirv::Module::LinkFunction` which will match up the function arguments and create the final shader which looks like
```glsl
#version 460
#extension GL_EXT_buffer_reference : require
#extension GL_EXT_nonuniform_qualifier : require
layout(set = 0, binding = 1) uniform sampler2D tex[];
layout(location = 0) out vec4 uFragColor;
layout(location = 0) flat in uint index;
bool inst_bindless_descriptor(uint inst_num, uvec4 stage_info, uint desc_set,
uint binding, uint desc_index, uint byte_offset) {
// logic
return no_error_found;
}
void main()
{
vec4 value;
if (inst_bindless_descriptor(2, 42, uvec4(4, gl_FragCoord.xy, 0), 0, 1, index, 0)) {
value = texture(tex[index], vec2(0.0));
} else {
value = vec4(0.0);
}
uFragColor = value;
}
```
## How runtime spirv-val for single instructions is instrumented (Currently in proposal status)
There are a set of `VUID-RuntimeSpirv` VUs that could be validated in `spirv-val` statically **if** it was using `OpConstant`.
Since it is likely these are variables decided at runtime, we will need to check in GPU-AV.
Luckily because these are usually bound to a single instruction, it is a lighter check
### Example - OpRayQueryInitializeKHR
An example of a VU we need to validate at GPU-AV time
> For OpRayQueryInitializeKHR instructions, the RayTmin operand must be less than or equal to the RayTmax operand
the instruction operands look like
```swift
OpRayQueryInitializeKHR %ray_query %as %flags %cull_mask %ray_origin %ray_tmin %ray_dir %ray_tmax
```
The first step will be adding logic to wrap every call of this instruction to look like
```glsl
if (inst_ray_query_initialize(/* copy of arguments */)) {
rayQueryInitializeEXT(/* original arguments */)
}
```
From here, we will use the same `gpuav::spirv::Module::LinkFunction` flow to add the logic and link in where needed
The SPIR-V before and after adding the conditional check looks like
```swift
// before
// traceRayEXT(a, b, c)
%L1 = OpLabel
%value = OpLoad %x
OpRayQueryInitializeKHR %value %param
OpReturn
// after
// if (IsValid(a, b, c)) {
// traceRayEXT(a, b, c)
// }
%L1 = OpLabel
%value = OpLoad %x // for simplicity, can stay hoisted out
%compare = OpSomeCompareInstruction
OpSelectionMerge %L3 None
OpBranchConditional %compare %L2 %L3
%L2 = OpLabel
OpRayQueryInitializeKHR %value %param
OpBranch %L3
%L3 = OpLabel
OpReturn
```