Skip to content

Commit afc1a36

Browse files
committed
Add page on using pipelines
1 parent 0605ba7 commit afc1a36

File tree

3 files changed

+259
-1
lines changed

3 files changed

+259
-1
lines changed

guide/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@
2929
- [Shader Program](shader_objects/shader_program.md)
3030
- [GLSL to SPIR-V](shader_objects/glsl_to_spir_v.md)
3131
- [Drawing a Triangle](shader_objects/drawing_triangle.md)
32+
- [Graphics Pipelines](shader_objects/pipelines.md)

guide/src/shader_objects/pipelines.md

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# Graphics Pipelines
2+
3+
This page describes the usage of Graphics Pipelines _instead of_ Shader Objects. While the guide assumes Shader Object usage, not much should change in the rest of the code if you instead choose to use Graphics Pipelines. A notable exception is the setup of Descriptor Set Layouts: with pipelines it needs to be specified as part of the Pipeline Layout, whereas with Shader Objects it is part of each ShaderEXT's CreateInfo.
4+
5+
## Pipeline State
6+
7+
Most dynamic state with Shader Objects is static with pipelines: specified at pipeline creation time. Pipelines also require additional parameters, like attachment formats and sample count: these will be considered constant and stored in the builder later. Expose a subset of dynamic states through a struct:
8+
9+
```cpp
10+
// bit flags for various binary Pipeline States.
11+
struct PipelineFlag {
12+
enum : std::uint8_t {
13+
None = 0,
14+
AlphaBlend = 1 << 0, // turn on alpha blending.
15+
DepthTest = 1 << 1, // turn on depth write and test.
16+
};
17+
};
18+
19+
// specification of a unique Graphics Pipeline.
20+
struct PipelineState {
21+
using Flag = PipelineFlag;
22+
23+
[[nodiscard]] static constexpr auto default_flags() -> std::uint8_t {
24+
return Flag::AlphaBlend | Flag::DepthTest;
25+
}
26+
27+
vk::ShaderModule vertex_shader; // required.
28+
vk::ShaderModule fragment_shader; // required.
29+
30+
std::span<vk::VertexInputAttributeDescription const> vertex_attributes{};
31+
std::span<vk::VertexInputBindingDescription const> vertex_bindings{};
32+
33+
vk::PrimitiveTopology topology{vk::PrimitiveTopology::eTriangleList};
34+
vk::PolygonMode polygon_mode{vk::PolygonMode::eFill};
35+
vk::CullModeFlags cull_mode{vk::CullModeFlagBits::eNone};
36+
vk::CompareOp depth_compare{vk::CompareOp::eLess};
37+
std::uint8_t flags{default_flags()};
38+
};
39+
```
40+
41+
Encapsulate building pipelines into a class:
42+
43+
```cpp
44+
struct PipelineBuilderCreateInfo {
45+
vk::Device device{};
46+
vk::SampleCountFlagBits samples{};
47+
vk::Format color_format{};
48+
vk::Format depth_format{};
49+
};
50+
51+
class PipelineBuilder {
52+
public:
53+
using CreateInfo = PipelineBuilderCreateInfo;
54+
55+
explicit PipelineBuilder(CreateInfo const& create_info)
56+
: m_info(create_info) {}
57+
58+
[[nodiscard]] auto build(vk::PipelineLayout layout,
59+
PipelineState const& state) const
60+
-> vk::UniquePipeline;
61+
62+
private:
63+
CreateInfo m_info{};
64+
};
65+
```
66+
67+
The implementation is quite verbose, splitting it into multiple functions helps a bit:
68+
69+
```cpp
70+
// single viewport and scissor.
71+
constexpr auto viewport_state_v =
72+
vk::PipelineViewportStateCreateInfo({}, 1, {}, 1);
73+
74+
// these dynamic states are guaranteed to be available.
75+
constexpr auto dynamic_states_v = std::array{
76+
vk::DynamicState::eViewport,
77+
vk::DynamicState::eScissor,
78+
vk::DynamicState::eLineWidth,
79+
};
80+
81+
[[nodiscard]] auto create_shader_stages(vk::ShaderModule const vertex,
82+
vk::ShaderModule const fragment) {
83+
// set vertex (0) and fragment (1) shader stages.
84+
auto ret = std::array<vk::PipelineShaderStageCreateInfo, 2>{};
85+
ret[0]
86+
.setStage(vk::ShaderStageFlagBits::eVertex)
87+
.setPName("main")
88+
.setModule(vertex);
89+
ret[1]
90+
.setStage(vk::ShaderStageFlagBits::eFragment)
91+
.setPName("main")
92+
.setModule(fragment);
93+
return ret;
94+
}
95+
96+
[[nodiscard]] constexpr auto
97+
create_depth_stencil_state(std::uint8_t flags,
98+
vk::CompareOp const depth_compare) {
99+
auto ret = vk::PipelineDepthStencilStateCreateInfo{};
100+
auto const depth_test =
101+
(flags & PipelineFlag::DepthTest) == PipelineFlag::DepthTest;
102+
ret.setDepthTestEnable(depth_test ? vk::True : vk::False)
103+
.setDepthCompareOp(depth_compare);
104+
return ret;
105+
}
106+
107+
[[nodiscard]] constexpr auto
108+
create_color_blend_attachment(std::uint8_t const flags) {
109+
auto ret = vk::PipelineColorBlendAttachmentState{};
110+
auto const alpha_blend =
111+
(flags & PipelineFlag::AlphaBlend) == PipelineFlag::AlphaBlend;
112+
using CCF = vk::ColorComponentFlagBits;
113+
ret.setColorWriteMask(CCF::eR | CCF::eG | CCF::eB | CCF::eA)
114+
.setBlendEnable(alpha_blend ? vk::True : vk::False)
115+
// standard alpha blending:
116+
// (alpha * src) + (1 - alpha) * dst
117+
.setSrcColorBlendFactor(vk::BlendFactor::eSrcAlpha)
118+
.setDstColorBlendFactor(vk::BlendFactor::eOneMinusSrcAlpha)
119+
.setColorBlendOp(vk::BlendOp::eAdd)
120+
.setSrcAlphaBlendFactor(vk::BlendFactor::eOne)
121+
.setDstAlphaBlendFactor(vk::BlendFactor::eZero)
122+
.setAlphaBlendOp(vk::BlendOp::eAdd);
123+
return ret;
124+
}
125+
126+
// ...
127+
auto PipelineBuilder::build(vk::PipelineLayout const layout,
128+
PipelineState const& state) const
129+
-> vk::UniquePipeline {
130+
auto const shader_stage_ci =
131+
create_shader_stages(state.vertex_shader, state.fragment_shader);
132+
133+
auto vertex_input_ci = vk::PipelineVertexInputStateCreateInfo{};
134+
vertex_input_ci.setVertexAttributeDescriptions(state.vertex_attributes)
135+
.setVertexBindingDescriptions(state.vertex_bindings);
136+
137+
auto multisample_state_ci = vk::PipelineMultisampleStateCreateInfo{};
138+
multisample_state_ci.setRasterizationSamples(m_info.samples)
139+
.setSampleShadingEnable(vk::False);
140+
141+
auto const input_assembly_ci =
142+
vk::PipelineInputAssemblyStateCreateInfo{{}, state.topology};
143+
144+
auto rasterization_state_ci = vk::PipelineRasterizationStateCreateInfo{};
145+
rasterization_state_ci.setPolygonMode(state.polygon_mode)
146+
.setCullMode(state.cull_mode);
147+
148+
auto const depth_stencil_state_ci =
149+
create_depth_stencil_state(state.flags, state.depth_compare);
150+
151+
auto const color_blend_attachment =
152+
create_color_blend_attachment(state.flags);
153+
auto color_blend_state_ci = vk::PipelineColorBlendStateCreateInfo{};
154+
color_blend_state_ci.setAttachments(color_blend_attachment);
155+
156+
auto dynamic_state_ci = vk::PipelineDynamicStateCreateInfo{};
157+
dynamic_state_ci.setDynamicStates(dynamic_states_v);
158+
159+
// Dynamic Rendering requires passing this in the pNext chain.
160+
auto rendering_ci = vk::PipelineRenderingCreateInfo{};
161+
// could be a depth-only pass, argument is span-like (notice the plural
162+
// `Formats()`), only set if not Undefined.
163+
if (m_info.color_format != vk::Format::eUndefined) {
164+
rendering_ci.setColorAttachmentFormats(m_info.color_format);
165+
}
166+
// single depth attachment format, ok to set to Undefined.
167+
rendering_ci.setDepthAttachmentFormat(m_info.depth_format);
168+
169+
auto pipeline_ci = vk::GraphicsPipelineCreateInfo{};
170+
pipeline_ci.setLayout(layout)
171+
.setStages(shader_stage_ci)
172+
.setPVertexInputState(&vertex_input_ci)
173+
.setPViewportState(&viewport_state_v)
174+
.setPMultisampleState(&multisample_state_ci)
175+
.setPInputAssemblyState(&input_assembly_ci)
176+
.setPRasterizationState(&rasterization_state_ci)
177+
.setPDepthStencilState(&depth_stencil_state_ci)
178+
.setPColorBlendState(&color_blend_state_ci)
179+
.setPDynamicState(&dynamic_state_ci)
180+
.setPNext(&rendering_ci);
181+
182+
auto ret = vk::Pipeline{};
183+
// use non-throwing API.
184+
if (m_info.device.createGraphicsPipelines({}, 1, &pipeline_ci, {}, &ret) !=
185+
vk::Result::eSuccess) {
186+
std::println(stderr, "[lvk] Failed to create Graphics Pipeline");
187+
return {};
188+
}
189+
190+
return vk::UniquePipeline{ret, m_info.device};
191+
}
192+
```
193+
194+
`App` will need to store a builder, a Pipeline Layout, and the Pipeline(s):
195+
196+
```cpp
197+
std::optional<PipelineBuilder> m_pipeline_builder{};
198+
vk::UniquePipelineLayout m_pipeline_layout{};
199+
vk::UniquePipeline m_pipeline{};
200+
201+
// ...
202+
void create_pipeline() {
203+
auto const vertex_spirv = to_spir_v(asset_path("shader.vert"));
204+
auto const fragment_spirv = to_spir_v(asset_path("shader.frag"));
205+
if (vertex_spirv.empty() || fragment_spirv.empty()) {
206+
throw std::runtime_error{"Failed to load shaders"};
207+
}
208+
209+
auto pipeline_layout_ci = vk::PipelineLayoutCreateInfo{};
210+
pipeline_layout_ci.setSetLayouts({});
211+
m_pipeline_layout =
212+
m_device->createPipelineLayoutUnique(pipeline_layout_ci);
213+
214+
auto const pipeline_builder_ci = PipelineBuilder::CreateInfo{
215+
.device = *m_device,
216+
.samples = vk::SampleCountFlagBits::e1,
217+
.color_format = m_swapchain->get_format(),
218+
};
219+
m_pipeline_builder.emplace(pipeline_builder_ci);
220+
221+
auto vertex_ci = vk::ShaderModuleCreateInfo{};
222+
vertex_ci.setCode(vertex_spirv);
223+
auto fragment_ci = vk::ShaderModuleCreateInfo{};
224+
fragment_ci.setCode(fragment_spirv);
225+
226+
auto const vertex_shader =
227+
m_device->createShaderModuleUnique(vertex_ci);
228+
auto const fragment_shader =
229+
m_device->createShaderModuleUnique(fragment_ci);
230+
auto const pipeline_state = PipelineState{
231+
.vertex_shader = *vertex_shader,
232+
.fragment_shader = *fragment_shader,
233+
};
234+
m_pipeline =
235+
m_pipeline_builder->build(*m_pipeline_layout, pipeline_state);
236+
}
237+
```
238+
239+
Finally, `App::draw()`:
240+
241+
```cpp
242+
void draw(vk::CommandBuffer const command_buffer) const {
243+
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics,
244+
*m_pipeline);
245+
auto viewport = vk::Viewport{};
246+
viewport.setX(0.0f)
247+
.setY(static_cast<float>(m_render_target->extent.height))
248+
.setWidth(static_cast<float>(m_render_target->extent.width))
249+
.setHeight(-viewport.y);
250+
command_buffer.setViewport(0, viewport);
251+
command_buffer.setScissor(0, vk::Rect2D{{}, m_render_target->extent});
252+
command_buffer.draw(3, 1, 0, 0);
253+
}
254+
```

guide/src/shader_objects/shader_program.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ struct ShaderProgramCreateInfo {
7777
};
7878
```
7979

80+
> Descriptor Sets and their Layouts will be covered later.
81+
8082
Start with a skeleton definition:
8183

8284
```cpp
@@ -178,7 +180,8 @@ struct ShaderVertexInput {
178180

179181
struct ShaderProgramCreateInfo {
180182
// ...
181-
ShaderVertexInput vertex_input{};
183+
ShaderVertexInput vertex_input{};
184+
// ...
182185
};
183186

184187
class ShaderProgram {

0 commit comments

Comments
 (0)