Skip to content

Commit

Permalink
Add Ways To Provide SPIR-V Chapter (#270)
Browse files Browse the repository at this point in the history
* Add Ways To Provide SPIR-V Chapter

* Apply feedback
  • Loading branch information
spencer-lunarg authored Oct 17, 2024
1 parent 027f972 commit 2a98e56
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
149 changes: 149 additions & 0 deletions chapters/ways_to_provide_spirv.adoc
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down

0 comments on commit 2a98e56

Please sign in to comment.