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.
When we find an error in the SPIR-V at runtime there are 3 possible behaviour the layer can take
- Still execute the instruction as normal (chance it will crash/hang everything)
- Don't execute the instruction (works well if there is not return value to worry about)
- 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.
As an exaxmple, if the incoming shader was
#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
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
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 ShaderSPIR-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
#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;
}
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
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
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
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
// 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