Shader validation is run at vkCreateShaderModule and vkCreate*Pipelines time. It makes sure both the SPIR-V is valid
as well as the VkPipeline object interface with the shader. Note, this is all done on the CPU and different than
GPU-Assisted Validation.
There are many VUID labeled as VUID-StandaloneSpirv-* and all the
Built-In Variables
VUIDs that can be validated on a single shader module and require no runtime information.
All of these validations are passed off to spirv-val in SPIRV-Tools.
There are a few special places where spirv-opt is run to reduce recreating work already done in SPIRV-Tools.
These can be found by searching for RegisterPass in the code
Currently these are
- Specialization constants
- This
spirv-optpass is used to inject the constants from the pipeline layout. - Some checks require the runtime spec constant values
- This
- Flatten OpGroupDecorations
- Detects if group decorations were used; however, group decorations were deprecated early on in the development of the SPIR-v specification.
- GPU-AV and Debug Printf
- instruments the shaders - see GPU-Assisted Validation and GPU-AV Shader Instrumentation.
The code is currently split up into the following main sections
layers/shader_instruction.cpp- This contains information about each SPIR-V instruction.
layers/shader_module.cpp- This contains information about the
VkShaderModuleobject
- This contains information about the
layers/shader_validation.cpp- This takes the following above and does the actual validation. All errors are produced here.
layers/vulkan/generated/spirv_validation_helper.cpp- This is generated file provides a way to generate checks for things found in the
vk.xmlrelated to SPIR-V
- This is generated file provides a way to generate checks for things found in the
layers/vulkan/generated/spirv_grammar_helper.cpp- This is a general util file that is generated from the SPIR-V grammar
All Shader Validation can be broken into 4 types of checks
- SPIR-V with runtime properties
- Things like features and limits
- Shader interface
- Ex. going between a Vertex and Fragment shader
- Interaction with Pipeline creation structs
- Vertex input, fragment output, etc
- Draw time
- making sure bound descriptor matches up what is being touched
When dealing with shader validation there are a few concepts to understand and not confuse
EntryPoints- Tied to a shader stage (fragment, vertex, etc)
- Knows which variables and instructions are touched in stage
- There might be things in a
ShaderModulenot related to shader stage validation
- There might be things in a
SPIR-V ModuleSPIRV_MODULE_STATE- This object takes in SPIR-V, parses it, creates
EntryPointobjects, validates what we can- We do validation first as sometimes a bad SPIR-V can crash a driver
- can contain multiple EntryPoints
- contains SPIR-V instructions (in an array of
uint32_twords) - knows the relationship between instructions
Shader ModuleandShader ObjectVkShaderModuleobject (SHADER_MODULE_STATE) orVkShaderEXTobject (SHADER_OBJECT_STATE)- can hold a
SPIR-V modulereferencePipeline Library(GPL) (VK_EXT_graphics_pipeline_library)- part of a pipeline that can be reused
ShaderModuleIdentifier(VK_EXT_shader_module_identifier)- lets app use a hash instead of having the driver re-create the
ShaderModule - not possible to validate as the VVL don't know what the
ShaderModuleis
- lets app use a hash instead of having the driver re-create the
Pipeline- contains 1 or more
Shader Moduleobject - decides both which
Shader ModuleandEntryPointare used - has other state not known if validating just the shader object
- contains 1 or more
When dealing with validation, it is important to know what should be validated, and when.
If validation only cares about... :
- the SPIR-V itself, is mapped to the
SPIRV_MODULE_STATE - if two stages interface, needs to be done when all stages are there
- For
Pipeline Libraryit might need to wait until linking
- For
- descriptors variables, use
EntryPoint - the stage of a shader module is always known, regardless of even using
ShaderModuleIdentifier - Pipeline can have fields that are related to shaders, but don't actually require the SPIR-V
There are 2 types of Variables
Resource Interfacevariables (mapped to descriptors)Stage Interfacevariables (input and output between shader)- Can be either a
BuiltInorUser Definedvariables
- Can be either a
For each EntryPoint we walk the functions and find all Variables accessed (load, store, atomic).
Infomaration to note:
- It is possible to have multiple
EntryPointspointing to the same interface variable. - 2 different accesses (ex.
OpLoad) can point to sameVariable - 2
Image operationcan point to 2 differentVariables
Any variable in a shader pointing to an Image is a Resource Interface variable.
There are validation checks that need care only if the variable is accessed.
This requires a OpImage* instruction to access the variable.
Most Accesses look like
OpImage* -> OpLoad -> OpAccessChain (optional) -> OpVariable
There are a few exceptions:
An Image Fetch has an OpImage prior to the OpLoad
OpImageFetch -> OpImage -> OpLoad -> OpVariable
Image Atomics use OpImageTexelPointer instead of OpLoad
OpAtomicLoad -> OpImageTexelPointer -> OpAccessChain (optional) -> OpVariable
The biggest thing to consider is using either a
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLERVK_DESCRIPTOR_TYPE_SAMPLERandVK_DESCRIPTOR_TYPE_SAMPLED_IMAGEcombo
// VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
OpImageSampleExplicitLod -> OpLoad -> OpAccessChain (optional) -> OpVariable -> OpTypePointer -> OpTypeSampledImage
// VK_DESCRIPTOR_TYPE_SAMPLER and VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE
OpImageSampleImplicitLod -> OpSampledImage -> OpTypeSampledImage
-> OpLoad -> OpAccessChain (optional) -> OpVariable (image)
-> OpLoad -> OpAccessChain (optional) -> OpVariable (sampler)
Both contain a OpTypeSampledImage, which is how we know a VkSampler is being used with the variable
But it is also possible to have the Image and Samplers mix and match
ImageAccess -> Image_0
-> Sampler_0
ImageAccess -> Image_0
-> Sampler_1
ImageAccess -> Image_0 (non-sampled access)
This is handled by having the Resource Interface variable track if it has a OpTypeSampledImage, OpTypeImage or OpTypeSampler
- If it has
OpTypeSampledImage, there is no way for it to be part of aSAMPLER/SAMPLED_IMAGEcombo - If it has a
OpTypeImageorOpTypeSampler, we need to know if they are accessed together- This means the the
ValidateDescriptorlogic needs to know everyOpTypeSamplervariable accessed together with aOpTypeImagevariable - There is no case where only a
OpTypeSamplervariable can be used by itself, so no need to track it the other way
- This means the the
If the Image Access is in a function, it might point to multiple OpVariable
There are 2 types of atomic accesses: "Image" and "Non-Image"
Image atomics are described above how they use OpImageTexelPointer instead of OpLoad
Non-Image atomics will look like
OpAtomicLoad -> OpAccessChain (optional) -> OpVariable