Skip to content

Commit 2a98e56

Browse files
Add Ways To Provide SPIR-V Chapter (#270)
* Add Ways To Provide SPIR-V Chapter * Apply feedback
1 parent 027f972 commit 2a98e56

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed

README.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ The Vulkan Guide can be built as a single page using `asciidoctor guide.adoc`
184184

185185
// include::{chapters}push_constants.adoc[]
186186

187+
=== xref:{chapters}ways_to_provide_spirv.adoc[Push Constants]
188+
189+
// include::{chapters}ways_to_provide_spirv.adoc[]
190+
187191
== xref:{chapters}robustness.adoc[Robustness]
188192

189193
// include::{chapters}robustness.adoc[]
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright 2024 The Khronos Group, Inc.
2+
// SPDX-License-Identifier: CC-BY-4.0
3+
4+
ifndef::chapters[:chapters:]
5+
ifndef::images[:images: images/]
6+
7+
[[ways-to-provide-spirv]]
8+
= Ways to Provide SPIR-V
9+
10+
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.
11+
12+
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
13+
14+
== vkCreateShaderModule
15+
16+
In Vulkan 1.0 release, this was the only method there was to pass SPIR-V into Vulkan. It is a very simple workflow:
17+
18+
[source,cpp]
19+
----
20+
VkShaderModuleCreateInfo shader_module_ci;
21+
shader_module_ci.pCode = spirv_source;
22+
shader_module_ci.codeSize = sizeof(spirv_source);
23+
24+
VkShaderModule shader_module;
25+
vkCreateShaderModule(device, &shader_module_ci, NULL, &shader_module);
26+
27+
// used to create VkPipeline
28+
VkPipelineShaderStageCreateInfo pipeline_stage_ci;
29+
pipeline_stage_ci.module = shader_module;
30+
----
31+
32+
The most common issue for tools or drivers consuming `vkCreateShaderModule` is there is still information missing:
33+
34+
* For graphics pipeline, what are the descriptor set layouts bound
35+
* What are the other shader stages that will be linked up to this `VkShaderModule`
36+
* If there are multiple entry-points, which one is set (via `VkPipelineShaderStageCreateInfo::pName`)
37+
* What are the final Specialization Constant values
38+
39+
== Inlining inside VkPipelineShaderStageCreateInfo
40+
41+
If you have support for either `VK_KHR_maintenance5` or `VK_EXT_graphics_pipeline_library` you can just skip the `VkShaderModule` object completely.
42+
43+
When creating the `VkPipeline` you can just pass in SPIR-V then:
44+
45+
[source,cpp]
46+
----
47+
VkShaderModuleCreateInfo shader_module_ci;
48+
shader_module_ci.pCode = spirv_source;
49+
shader_module_ci.codeSize = sizeof(spirv_source);
50+
51+
VkPipelineShaderStageCreateInfo pipeline_stage_ci;
52+
pipeline_stage_ci.pNext = &shader_module_ci
53+
pipeline_stage_ci.module = VK_NULL_HANDLE;
54+
----
55+
56+
== Shader Module Identifier
57+
58+
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.
59+
60+
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
61+
62+
The following is an example of the workflow an application will use:
63+
64+
65+
[source,cpp]
66+
----
67+
// First time running an application
68+
VkShaderModule shader_module;
69+
vkCreateShaderModule(device, &shader_module_ci, NULL, &shader_module);
70+
71+
VkShaderModuleIdentifierEXT sm_identifier;
72+
vkGetShaderModuleIdentifierEXT(device, shader_module, &sm_identifier);
73+
74+
SaveToDisk(sm_identifier);
75+
76+
// -----------
77+
// Potentially a 2nd seperate application run
78+
// -----------
79+
80+
VkShaderModuleIdentifierEXT sm_identifier;
81+
LoadFromDisk(sm_identifier);
82+
83+
VkPipelineShaderStageModuleIdentifierCreateInfoEXT shader_module_id_ci;
84+
shader_module_id_ci.identifierSize = sm_identifier.identifierSize;
85+
shader_module_id_ci.pIdentifier = sm_identifier.identifier;
86+
87+
// Inline when creating the pipeline
88+
VkPipelineShaderStageCreateInfo pipeline_stage_ci;
89+
pipeline_stage_ci.pNext = &shader_module_id_ci
90+
pipeline_stage_ci.module = VK_NULL_HANDLE;
91+
----
92+
93+
== Graphics Pipeline Library
94+
95+
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.
96+
97+
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:
98+
99+
[source,cpp]
100+
----
101+
// Will be no SPIR-V
102+
VkPipeline vertex_input_lib;
103+
vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, create_info, NULL, vertex_input_lib);
104+
VkPipeline fragment_output_lib;
105+
vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, create_info, NULL, fragment_output_lib);
106+
107+
// Will be SPIR-V
108+
VkPipeline pre_raster_lib;
109+
vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, create_info, NULL, pre_raster_lib);
110+
111+
// May be SPIR-V (can have pipelines without fragment shaders)
112+
VkPipeline fragment_shader_lib;
113+
vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, create_info, NULL, fragment_shader_lib);
114+
115+
// Will be no SPIR-V when linking in pipeline library containing the SPIR-V already
116+
VkPipeline executable_pipeline;
117+
VkPipelineLibraryCreateInfoKHR library_ci;
118+
library_ci.pLibraries = [vertex_input_lib, pre_raster_lib, fragment_shader_lib, fragment_output_lib];
119+
create_info.pNext = &library_ci;
120+
vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, create_info, NULL, executable_pipeline);
121+
----
122+
123+
== Shader Objects
124+
125+
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.
126+
127+
128+
[source,cpp]
129+
----
130+
VkShaderCreateInfoEXT shader_ci;
131+
// Note that the SPIR-V can actually be passed in a binary blob
132+
shader_ci.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT;
133+
// Note that this is a void pointer unlike the uint32_t pointer found in VkShaderModuleCreateInfo
134+
shader_ci.pCode = spirv_source;
135+
shader_ci.codeSize = sizeof(spirv_source);
136+
137+
VkShaderEXT shader_object;
138+
vkCreateShadersEXT(device, 1, &shader_ci, NULL, &shader_object);
139+
----
140+
141+
== vkCreateRayTracingPipelinesKHR::deferredOperation
142+
143+
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.
144+
145+
The link:https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#deferred-host-operations-requesting[spec states]
146+
147+
> 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.
148+
149+
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.

guide.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ include::{chapters}descriptor_dynamic_offset.adoc[]
183183

184184
include::{chapters}push_constants.adoc[]
185185

186+
// === Ways to Provide SPIR-V
187+
188+
include::{chapters}ways_to_provide_spirv.adoc[]
189+
186190
// == Robustness
187191

188192
include::{chapters}robustness.adoc[]

0 commit comments

Comments
 (0)