# 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.

## 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 `spirv-opt` pass to inject logic to call a known function, this will look like

```glsl
vec4 value;
if (inst_bindless_check_desc(/*...*/)) {
    value = texture(tex[index], vec2(0.0));
} else {
    value = vec4(0.0);
}
uFragColor = value;
```

The next step is to add the `inst_bindless_check_desc` function into the SPIR-V.

Currently, all these functions are found in `inst_functions.comp`

```glsl
bool inst_bindless_check_desc(const uint shader_id, 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 `spirv-link` 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_check_desc(uint shader_id, 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_check_desc(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 */)
} else {
    rayQueryInitializeEXT(/* safe arguments */); // fall-back default call
}
```

From here, we will use the same `spirv-link` flow to add the logic and link in where needed