diff --git a/README.adoc b/README.adoc index 5408095..2cc3073 100644 --- a/README.adoc +++ b/README.adoc @@ -184,6 +184,10 @@ The Vulkan Guide can be built as a single page using `asciidoctor guide.adoc` // include::{chapters}push_constants.adoc[] +=== xref:{chapters}ways_to_provide_spirv.adoc[Push Constants] + +// include::{chapters}ways_to_provide_spirv.adoc[] + == xref:{chapters}robustness.adoc[Robustness] // include::{chapters}robustness.adoc[] diff --git a/chapters/ways_to_provide_spirv.adoc b/chapters/ways_to_provide_spirv.adoc new file mode 100644 index 0000000..7d4a32b --- /dev/null +++ b/chapters/ways_to_provide_spirv.adoc @@ -0,0 +1,149 @@ +// Copyright 2024 The Khronos Group, Inc. +// SPDX-License-Identifier: CC-BY-4.0 + +ifndef::chapters[:chapters:] +ifndef::images[:images: images/] + +[[ways-to-provide-spirv]] += Ways to Provide SPIR-V + +This chapter is designed for anyone who wants to write a tool to inspect, consume, edit, or do anything related to the SPIR-V modules passed into Vulkan. + +Over the years, more and more ways have been created to pass SPIR-V down to the driver and this chapter goes over them all + +== vkCreateShaderModule + +In Vulkan 1.0 release, this was the only method there was to pass SPIR-V into Vulkan. It is a very simple workflow: + +[source,cpp] +---- +VkShaderModuleCreateInfo shader_module_ci; +shader_module_ci.pCode = spirv_source; +shader_module_ci.codeSize = sizeof(spirv_source); + +VkShaderModule shader_module; +vkCreateShaderModule(device, &shader_module_ci, NULL, &shader_module); + +// used to create VkPipeline +VkPipelineShaderStageCreateInfo pipeline_stage_ci; +pipeline_stage_ci.module = shader_module; +---- + +The most common issue for tools or drivers consuming `vkCreateShaderModule` is there is still information missing: + +* For graphics pipeline, what are the descriptor set layouts bound +* What are the other shader stages that will be linked up to this `VkShaderModule` +* If there are multiple entry-points, which one is set (via `VkPipelineShaderStageCreateInfo::pName`) +* What are the final Specialization Constant values + +== Inlining inside VkPipelineShaderStageCreateInfo + +If you have support for either `VK_KHR_maintenance5` or `VK_EXT_graphics_pipeline_library` you can just skip the `VkShaderModule` object completely. + +When creating the `VkPipeline` you can just pass in SPIR-V then: + +[source,cpp] +---- +VkShaderModuleCreateInfo shader_module_ci; +shader_module_ci.pCode = spirv_source; +shader_module_ci.codeSize = sizeof(spirv_source); + +VkPipelineShaderStageCreateInfo pipeline_stage_ci; +pipeline_stage_ci.pNext = &shader_module_ci +pipeline_stage_ci.module = VK_NULL_HANDLE; +---- + +== Shader Module Identifier + +The `VK_EXT_shader_module_identifier` extension allows the app to not even need the SPIR-V source anymore, instead the `VkShaderModule` can be cached on the disk and then in a future run of the application, a "ID" pointing to that cache can be used. + +The biggest challenge for tools is you don't have a chance to see the original `vkGetShaderModuleIdentifierEXT` call, there will be no chance to know what is inside the SPIR-V + +The following is an example of the workflow an application will use: + + +[source,cpp] +---- +// First time running an application +VkShaderModule shader_module; +vkCreateShaderModule(device, &shader_module_ci, NULL, &shader_module); + +VkShaderModuleIdentifierEXT sm_identifier; +vkGetShaderModuleIdentifierEXT(device, shader_module, &sm_identifier); + +SaveToDisk(sm_identifier); + +// ----------- +// Potentially a 2nd seperate application run +// ----------- + +VkShaderModuleIdentifierEXT sm_identifier; +LoadFromDisk(sm_identifier); + +VkPipelineShaderStageModuleIdentifierCreateInfoEXT shader_module_id_ci; +shader_module_id_ci.identifierSize = sm_identifier.identifierSize; +shader_module_id_ci.pIdentifier = sm_identifier.identifier; + +// Inline when creating the pipeline +VkPipelineShaderStageCreateInfo pipeline_stage_ci; +pipeline_stage_ci.pNext = &shader_module_id_ci +pipeline_stage_ci.module = VK_NULL_HANDLE; +---- + +== Graphics Pipeline Library + +The `VK_EXT_graphics_pipeline_library` extension breaks up the pipeline into 4 smaller parts with the intent of allowing faster pipeline loading for applications reusing the same shaders or state in multiple pipelines. + +From a tools point a view, you may see up to 5 different `vkCreateGraphicsPipelines` calls, but only 2 of them will have SPIR-V in it. The following is an example workflow: + +[source,cpp] +---- +// Will be no SPIR-V +VkPipeline vertex_input_lib; +vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, create_info, NULL, vertex_input_lib); +VkPipeline fragment_output_lib; +vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, create_info, NULL, fragment_output_lib); + +// Will be SPIR-V +VkPipeline pre_raster_lib; +vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, create_info, NULL, pre_raster_lib); + +// May be SPIR-V (can have pipelines without fragment shaders) +VkPipeline fragment_shader_lib; +vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, create_info, NULL, fragment_shader_lib); + +// Will be no SPIR-V when linking in pipeline library containing the SPIR-V already +VkPipeline executable_pipeline; +VkPipelineLibraryCreateInfoKHR library_ci; +library_ci.pLibraries = [vertex_input_lib, pre_raster_lib, fragment_shader_lib, fragment_output_lib]; +create_info.pNext = &library_ci; +vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, create_info, NULL, executable_pipeline); +---- + +== Shader Objects + +The `VK_EXT_shader_object` extension created a completely new flow to pass state information in the command buffer that doesn't involve pipelines. The following is the workflow to pass in the SPIR-V. + + +[source,cpp] +---- +VkShaderCreateInfoEXT shader_ci; +// Note that the SPIR-V can actually be passed in a binary blob +shader_ci.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; +// Note that this is a void pointer unlike the uint32_t pointer found in VkShaderModuleCreateInfo +shader_ci.pCode = spirv_source; +shader_ci.codeSize = sizeof(spirv_source); + +VkShaderEXT shader_object; +vkCreateShadersEXT(device, 1, &shader_ci, NULL, &shader_object); +---- + +== vkCreateRayTracingPipelinesKHR::deferredOperation + +When dealing with Ray Tracing pipelines, it is important to note that a `VkDeferredOperationKHR` handle might be used to defer the creation of the pipeline to unblock the CPU thread. + +The link:https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#deferred-host-operations-requesting[spec states] + +> Parameters to the command requesting a deferred operation may be accessed by the implementation at any time until the deferred operation enters the complete state. + +In this particular case this means that if your tool is touching the SPIR-V being passed in, **all** parameters passed down to `vkCreateRayTracingPipelinesKHR`, including pointers, shader modules, inlined SPIR-V... must live until operation completion. \ No newline at end of file diff --git a/guide.adoc b/guide.adoc index d23a4fc..37ee51e 100644 --- a/guide.adoc +++ b/guide.adoc @@ -183,6 +183,10 @@ include::{chapters}descriptor_dynamic_offset.adoc[] include::{chapters}push_constants.adoc[] +// === Ways to Provide SPIR-V + +include::{chapters}ways_to_provide_spirv.adoc[] + // == Robustness include::{chapters}robustness.adoc[]