Skip to content

Commit f6ddd0b

Browse files
committed
sRGB triangle
1 parent 87024dd commit f6ddd0b

File tree

10 files changed

+292
-6
lines changed

10 files changed

+292
-6
lines changed

assets/shader.frag

164 Bytes
Binary file not shown.

assets/shader.vert

344 Bytes
Binary file not shown.

guide/src/pipeline/creation.md

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,254 @@
11
# Pipeline Creation
2+
3+
To constrain ourselves to a subset of pipeline state to worry about, create a custom `struct PipelineState`:
4+
5+
```cpp
6+
// bit flags for various binary Pipeline States.
7+
struct PipelineFlag {
8+
enum : std::uint8_t {
9+
None = 0,
10+
AlphaBlend = 1 << 0, // turn on alpha blending.
11+
DepthTest = 1 << 1, // turn on depth write and test.
12+
};
13+
};
14+
15+
// specification of a unique Graphics Pipeline.
16+
struct PipelineState {
17+
using Flag = PipelineFlag;
18+
19+
[[nodiscard]] static constexpr auto default_flags() -> std::uint8_t {
20+
return Flag::AlphaBlend | Flag::DepthTest;
21+
}
22+
23+
vk::ShaderModule vertex_shader; // required.
24+
vk::ShaderModule fragment_shader; // required.
25+
26+
std::span<vk::VertexInputAttributeDescription const> vertex_attributes{};
27+
std::span<vk::VertexInputBindingDescription const> vertex_bindings{};
28+
29+
vk::PrimitiveTopology topology{vk::PrimitiveTopology::eTriangleList};
30+
vk::PolygonMode polygon_mode{vk::PolygonMode::eFill};
31+
vk::CullModeFlags cull_mode{vk::CullModeFlagBits::eNone};
32+
vk::CompareOp depth_compare{vk::CompareOp::eLess};
33+
std::uint8_t flags{default_flags()};
34+
};
35+
```
36+
37+
Encapsulate the exhausting process of building a pipeline into its own class:
38+
39+
```cpp
40+
struct PipelineBuilderCreateInfo {
41+
vk::Device device{};
42+
vk::SampleCountFlagBits samples{};
43+
vk::Format color_format{};
44+
vk::Format depth_format{};
45+
};
46+
47+
class PipelineBuilder {
48+
public:
49+
using CreateInfo = PipelineBuilderCreateInfo;
50+
51+
explicit PipelineBuilder(CreateInfo const& create_info);
52+
53+
[[nodiscard]] auto build(vk::PipelineLayout layout,
54+
PipelineState const& state) const
55+
-> vk::UniquePipeline;
56+
57+
private:
58+
CreateInfo m_info{};
59+
};
60+
```
61+
62+
Implement `build()`:
63+
64+
```cpp
65+
auto PipelineBuilder::build(vk::PipelineLayout const layout,
66+
PipelineState const& state) const
67+
-> vk::UniquePipeline {
68+
// set vertex (0) and fragment (1) shader stages.
69+
auto shader_stages = std::array<vk::PipelineShaderStageCreateInfo, 2>{};
70+
shader_stages[0]
71+
.setStage(vk::ShaderStageFlagBits::eVertex)
72+
.setPName("main")
73+
.setModule(state.vertex_shader);
74+
shader_stages[1]
75+
.setStage(vk::ShaderStageFlagBits::eFragment)
76+
.setPName("main")
77+
.setModule(state.fragment_shader);
78+
79+
auto pvisci = vk::PipelineVertexInputStateCreateInfo{};
80+
pvisci.setVertexAttributeDescriptions(state.vertex_attributes)
81+
.setVertexBindingDescriptions(state.vertex_bindings);
82+
83+
auto prsci = vk::PipelineRasterizationStateCreateInfo{};
84+
prsci.setPolygonMode(state.polygon_mode).setCullMode(state.cull_mode);
85+
86+
auto pdssci = vk::PipelineDepthStencilStateCreateInfo{};
87+
auto const depth_test =
88+
(state.flags & PipelineFlag::DepthTest) == PipelineFlag::DepthTest;
89+
pdssci.setDepthTestEnable(depth_test ? vk::True : vk::False)
90+
.setDepthCompareOp(state.depth_compare);
91+
92+
auto const piasci =
93+
vk::PipelineInputAssemblyStateCreateInfo{{}, state.topology};
94+
95+
auto pcbas = vk::PipelineColorBlendAttachmentState{};
96+
auto const alpha_blend =
97+
(state.flags & PipelineFlag::AlphaBlend) == PipelineFlag::AlphaBlend;
98+
using CCF = vk::ColorComponentFlagBits;
99+
pcbas.setColorWriteMask(CCF::eR | CCF::eG | CCF::eB | CCF::eA)
100+
.setBlendEnable(alpha_blend ? vk::True : vk::False)
101+
.setSrcColorBlendFactor(vk::BlendFactor::eSrcAlpha)
102+
.setDstColorBlendFactor(vk::BlendFactor::eOneMinusSrcAlpha)
103+
.setColorBlendOp(vk::BlendOp::eAdd)
104+
.setSrcAlphaBlendFactor(vk::BlendFactor::eOne)
105+
.setDstAlphaBlendFactor(vk::BlendFactor::eZero)
106+
.setAlphaBlendOp(vk::BlendOp::eAdd);
107+
auto pcbsci = vk::PipelineColorBlendStateCreateInfo{};
108+
pcbsci.setAttachments(pcbas);
109+
110+
// these dynamic states are guaranteed to be available.
111+
auto const pdscis = std::array{
112+
vk::DynamicState::eViewport,
113+
vk::DynamicState::eScissor,
114+
vk::DynamicState::eLineWidth,
115+
};
116+
auto pdsci = vk::PipelineDynamicStateCreateInfo{};
117+
pdsci.setDynamicStates(pdscis);
118+
119+
// single viewport and scissor.
120+
auto const pvsci = vk::PipelineViewportStateCreateInfo({}, 1, {}, 1);
121+
122+
auto pmsci = vk::PipelineMultisampleStateCreateInfo{};
123+
pmsci.setRasterizationSamples(m_info.samples)
124+
.setSampleShadingEnable(vk::False);
125+
126+
auto prci = vk::PipelineRenderingCreateInfo{};
127+
// could be a depth-only pass.
128+
if (m_info.color_format != vk::Format::eUndefined) {
129+
prci.setColorAttachmentFormats(m_info.color_format);
130+
}
131+
prci.setDepthAttachmentFormat(m_info.depth_format);
132+
133+
auto gpci = vk::GraphicsPipelineCreateInfo{};
134+
gpci.setStages(shader_stages)
135+
.setPRasterizationState(&prsci)
136+
.setPDepthStencilState(&pdssci)
137+
.setPInputAssemblyState(&piasci)
138+
.setPColorBlendState(&pcbsci)
139+
.setPDynamicState(&pdsci)
140+
.setPViewportState(&pvsci)
141+
.setPMultisampleState(&pmsci)
142+
.setLayout(layout)
143+
.setPNext(&prci);
144+
145+
auto ret = vk::Pipeline{};
146+
// use non-throwing API.
147+
if (m_info.device.createGraphicsPipelines({}, 1, &gpci, {}, &ret) !=
148+
vk::Result::eSuccess) {
149+
return {};
150+
}
151+
152+
return vk::UniquePipeline{ret, m_info.device};
153+
}
154+
```
155+
156+
Add new `App` members:
157+
158+
```cpp
159+
void create_pipeline_builder();
160+
void create_pipeline();
161+
162+
// ...
163+
std::optional<PipelineBuilder> m_pipeline_builder{};
164+
165+
vk::UniquePipelineLayout m_pipeline_layout{};
166+
vk::UniquePipeline m_pipeline{};
167+
```
168+
169+
Implement and call `create_pipeline_builder()`:
170+
171+
```cpp
172+
void App::create_pipeline_builder() {
173+
auto const pipeline_builder_ci = PipelineBuilder::CreateInfo{
174+
.device = *m_device,
175+
.samples = vk::SampleCountFlagBits::e1,
176+
.color_format = m_swapchain->get_format(),
177+
};
178+
m_pipeline_builder.emplace(pipeline_builder_ci);
179+
}
180+
```
181+
182+
Complete the implementation of `create_pipeline()`:
183+
184+
```cpp
185+
// ...
186+
m_pipeline_layout = m_device->createPipelineLayoutUnique({});
187+
188+
auto const pipeline_state = PipelineState{
189+
.vertex_shader = *vertex,
190+
.fragment_shader = *fragment,
191+
};
192+
m_pipeline = m_pipeline_builder->build(*m_pipeline_layout, pipeline_state);
193+
if (!m_pipeline) {
194+
throw std::runtime_error{"Failed to create Graphics Pipeline"};
195+
}
196+
```
197+
198+
We can now bind it and use it to draw the triangle in the shader. Since we used dynamic viewport and scissor during pipeline creation, we need to set those after binding the pipeline.
199+
200+
```cpp
201+
command_buffer.beginRendering(rendering_info);
202+
ImGui::ShowDemoWindow();
203+
204+
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *m_pipeline);
205+
auto viewport = vk::Viewport{};
206+
// flip the viewport across the X-axis (negative height):
207+
// https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/
208+
viewport.setX(0.0f)
209+
.setY(static_cast<float>(m_render_target->extent.height))
210+
.setWidth(static_cast<float>(m_render_target->extent.width))
211+
.setHeight(-viewport.y);
212+
command_buffer.setViewport(0, viewport);
213+
command_buffer.setScissor(0, render_area);
214+
// current shader has hard-coded logic for 3 vertices.
215+
command_buffer.draw(3, 1, 0, 0);
216+
```
217+
218+
![White Triangle](./white_triangle.png)
219+
220+
Updating our shaders to use interpolated RGB on each vertex:
221+
222+
```glsl
223+
// shader.vert
224+
225+
layout (location = 0) out vec3 out_color;
226+
227+
// ...
228+
const vec3 colors[] = {
229+
vec3(1.0, 0.0, 0.0),
230+
vec3(0.0, 1.0, 0.0),
231+
vec3(0.0, 0.0, 1.0),
232+
};
233+
234+
// ...
235+
out_color = colors[gl_VertexIndex];
236+
237+
// shader.frag
238+
239+
layout (location = 0) in vec3 in_color;
240+
241+
// ...
242+
out_color = vec4(in_color, 1.0);
243+
```
244+
245+
And a black clear color:
246+
247+
```cpp
248+
// ...
249+
.setClearValue(vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f});
250+
```
251+
252+
Gives us the renowned Vulkan sRGB triangle:
253+
254+
![sRGB Triangle](./srgb_triangle.png)

guide/src/pipeline/srgb_triangle.png

82.2 KB
Loading

guide/src/pipeline/white_triangle.png

28.5 KB
Loading

src/app.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ void App::create_pipeline() {
199199
std::println("[lvk] Shaders loaded");
200200

201201
m_pipeline_layout = m_device->createPipelineLayoutUnique({});
202+
202203
auto const pipeline_state = PipelineState{
203204
.vertex_shader = *vertex,
204205
.fragment_shader = *fragment,
@@ -290,7 +291,7 @@ void App::render(vk::CommandBuffer const command_buffer) {
290291
.setLoadOp(vk::AttachmentLoadOp::eClear)
291292
.setStoreOp(vk::AttachmentStoreOp::eStore)
292293
// temporarily red.
293-
.setClearValue(vk::ClearColorValue{1.0f, 0.0f, 0.0f, 1.0f});
294+
.setClearValue(vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f});
294295
auto rendering_info = vk::RenderingInfo{};
295296
auto const render_area =
296297
vk::Rect2D{vk::Offset2D{}, m_render_target->extent};
@@ -300,15 +301,20 @@ void App::render(vk::CommandBuffer const command_buffer) {
300301

301302
command_buffer.beginRendering(rendering_info);
302303
ImGui::ShowDemoWindow();
304+
303305
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *m_pipeline);
304306
auto viewport = vk::Viewport{};
307+
// flip the viewport across the X-axis (negative height):
308+
// https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/
305309
viewport.setX(0.0f)
306310
.setY(static_cast<float>(m_render_target->extent.height))
307311
.setWidth(static_cast<float>(m_render_target->extent.width))
308312
.setHeight(-viewport.y);
309313
command_buffer.setViewport(0, viewport);
310314
command_buffer.setScissor(0, render_area);
315+
// current shader has hard-coded logic for 3 vertices.
311316
command_buffer.draw(3, 1, 0, 0);
317+
312318
command_buffer.endRendering();
313319

314320
m_imgui->end_frame();

src/glsl/shader.frag

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#version 450 core
22

3+
layout (location = 0) in vec3 in_color;
4+
35
layout (location = 0) out vec4 out_color;
46

57
void main() {
6-
out_color = vec4(1.0);
8+
out_color = vec4(in_color, 1.0);
79
}

src/glsl/shader.vert

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
#version 450 core
22

3+
layout (location = 0) out vec3 out_color;
4+
35
void main() {
46
const vec2 positions[] = {
57
vec2(-0.5, -0.5),
68
vec2(0.5, -0.5),
79
vec2(0.0, 0.5),
810
};
911

12+
const vec3 colors[] = {
13+
vec3(1.0, 0.0, 0.0),
14+
vec3(0.0, 1.0, 0.0),
15+
vec3(0.0, 0.0, 1.0),
16+
};
17+
1018
const vec2 position = positions[gl_VertexIndex];
1119

20+
out_color = colors[gl_VertexIndex];
1221
gl_Position = vec4(position, 0.0, 1.0);
1322
}

src/pipeline_builder.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ PipelineBuilder::PipelineBuilder(CreateInfo const& create_info)
77
auto PipelineBuilder::build(vk::PipelineLayout const layout,
88
PipelineState const& state) const
99
-> vk::UniquePipeline {
10+
// set vertex (0) and fragment (1) shader stages.
1011
auto shader_stages = std::array<vk::PipelineShaderStageCreateInfo, 2>{};
1112
shader_stages[0]
1213
.setStage(vk::ShaderStageFlagBits::eVertex)
@@ -17,6 +18,10 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout,
1718
.setPName("main")
1819
.setModule(state.fragment_shader);
1920

21+
auto pvisci = vk::PipelineVertexInputStateCreateInfo{};
22+
pvisci.setVertexAttributeDescriptions(state.vertex_attributes)
23+
.setVertexBindingDescriptions(state.vertex_bindings);
24+
2025
auto prsci = vk::PipelineRasterizationStateCreateInfo{};
2126
prsci.setPolygonMode(state.polygon_mode).setCullMode(state.cull_mode);
2227

@@ -44,6 +49,7 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout,
4449
auto pcbsci = vk::PipelineColorBlendStateCreateInfo{};
4550
pcbsci.setAttachments(pcbas);
4651

52+
// these dynamic states are guaranteed to be available.
4753
auto const pdscis = std::array{
4854
vk::DynamicState::eViewport,
4955
vk::DynamicState::eScissor,
@@ -52,13 +58,15 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout,
5258
auto pdsci = vk::PipelineDynamicStateCreateInfo{};
5359
pdsci.setDynamicStates(pdscis);
5460

61+
// single viewport and scissor.
5562
auto const pvsci = vk::PipelineViewportStateCreateInfo({}, 1, {}, 1);
5663

5764
auto pmsci = vk::PipelineMultisampleStateCreateInfo{};
5865
pmsci.setRasterizationSamples(m_info.samples)
5966
.setSampleShadingEnable(vk::False);
6067

6168
auto prci = vk::PipelineRenderingCreateInfo{};
69+
// could be a depth-only pass.
6270
if (m_info.color_format != vk::Format::eUndefined) {
6371
prci.setColorAttachmentFormats(m_info.color_format);
6472
}
@@ -77,6 +85,7 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout,
7785
.setPNext(&prci);
7886

7987
auto ret = vk::Pipeline{};
88+
// use non-throwing API.
8089
if (m_info.device.createGraphicsPipelines({}, 1, &gpci, {}, &ret) !=
8190
vk::Result::eSuccess) {
8291
return {};

0 commit comments

Comments
 (0)