Skip to content

Commit d05d31e

Browse files
committed
Cleanup, wireframe
1 parent ddb428e commit d05d31e

File tree

8 files changed

+257
-142
lines changed

8 files changed

+257
-142
lines changed

guide/src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@
2828
- [Locating Assets](pipeline/locating_assets.md)
2929
- [Shaders](pipeline/shaders.md)
3030
- [Pipeline Creation](pipeline/pipeline_creation.md)
31+
- [Drawing a Triangle](pipeline/drawing_triangle.md)
32+
- [Switching Pipelines](pipeline/switching_pipelines.md)
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Drawing a Triangle
2+
3+
We shall create two pipelines: one for standard draws, one for wireframe draws. Add new `App` members:
4+
5+
```cpp
6+
void create_pipeline_builder();
7+
void create_pipelines();
8+
9+
// ...
10+
std::optional<PipelineBuilder> m_pipeline_builder{};
11+
12+
vk::UniquePipelineLayout m_pipeline_layout{};
13+
struct {
14+
vk::UniquePipeline standard{};
15+
vk::UniquePipeline wireframe{};
16+
} m_pipelines{};
17+
float m_line_width{1.0f};
18+
bool m_wireframe{};
19+
```
20+
21+
Implement and call `create_pipeline_builder()`:
22+
23+
```cpp
24+
void App::create_pipeline_builder() {
25+
auto const pipeline_builder_ci = PipelineBuilder::CreateInfo{
26+
.device = *m_device,
27+
.samples = vk::SampleCountFlagBits::e1,
28+
.color_format = m_swapchain->get_format(),
29+
};
30+
m_pipeline_builder.emplace(pipeline_builder_ci);
31+
}
32+
```
33+
34+
Complete the implementation of `create_pipelines()`:
35+
36+
```cpp
37+
// ...
38+
m_pipeline_layout = m_device->createPipelineLayoutUnique({});
39+
40+
auto pipeline_state = PipelineState{
41+
.vertex_shader = *vertex,
42+
.fragment_shader = *fragment,
43+
};
44+
m_pipelines.standard =
45+
m_pipeline_builder->build(*m_pipeline_layout, pipeline_state);
46+
pipeline_state.polygon_mode = vk::PolygonMode::eLine;
47+
m_pipelines.wireframe =
48+
m_pipeline_builder->build(*m_pipeline_layout, pipeline_state);
49+
if (!m_pipelines.standard || !m_pipelines.wireframe) {
50+
throw std::runtime_error{"Failed to create Graphics Pipelines"};
51+
}
52+
```
53+
54+
Before `render()` grows to an unwieldy size, extract the higher level logic into two member functions:
55+
56+
```cpp
57+
// ImGui code goes here.
58+
void inspect();
59+
// Issue draw calls here.
60+
void draw(vk::Rect2D const& render_area,
61+
vk::CommandBuffer command_buffer) const;
62+
63+
// ...
64+
void App::inspect() {
65+
ImGui::ShowDemoWindow();
66+
// TODO
67+
}
68+
69+
// ...
70+
command_buffer.beginRendering(rendering_info);
71+
inspect();
72+
draw(render_area, command_buffer);
73+
command_buffer.endRendering();
74+
```
75+
76+
We can now bind a pipeline and use it to draw the triangle in the shader. Making `draw()` `const` forces us to ensure no `App` state is changed:
77+
78+
```cpp
79+
void App::draw(vk::Rect2D const& render_area,
80+
vk::CommandBuffer const command_buffer) const {
81+
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics,
82+
*m_pipelines.standard);
83+
// we are creating pipelines with dynamic viewport and scissor states.
84+
// they must be set here after binding (before drawing).
85+
auto viewport = vk::Viewport{};
86+
// flip the viewport about the X-axis (negative height):
87+
// https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/
88+
viewport.setX(0.0f)
89+
.setY(static_cast<float>(m_render_target->extent.height))
90+
.setWidth(static_cast<float>(m_render_target->extent.width))
91+
.setHeight(-viewport.y);
92+
command_buffer.setViewport(0, viewport);
93+
command_buffer.setScissor(0, render_area);
94+
// current shader has hard-coded logic for 3 vertices.
95+
command_buffer.draw(3, 1, 0, 0);
96+
}
97+
```
98+
99+
![White Triangle](./white_triangle.png)
100+
101+
Updating our shaders to use interpolated RGB on each vertex:
102+
103+
```glsl
104+
// shader.vert
105+
106+
layout (location = 0) out vec3 out_color;
107+
108+
// ...
109+
const vec3 colors[] = {
110+
vec3(1.0, 0.0, 0.0),
111+
vec3(0.0, 1.0, 0.0),
112+
vec3(0.0, 0.0, 1.0),
113+
};
114+
115+
// ...
116+
out_color = colors[gl_VertexIndex];
117+
118+
// shader.frag
119+
120+
layout (location = 0) in vec3 in_color;
121+
122+
// ...
123+
out_color = vec4(in_color, 1.0);
124+
```
125+
126+
> Make sure to recompile both the SPIR-V shaders in assets/.
127+
128+
And a black clear color:
129+
130+
```cpp
131+
// ...
132+
.setClearValue(vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f});
133+
```
134+
135+
Gives us the renowned Vulkan sRGB triangle:
136+
137+
![sRGB Triangle](./srgb_triangle.png)
138+

guide/src/pipeline/pipeline_creation.md

Lines changed: 2 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ struct PipelineFlag {
88
enum : std::uint8_t {
99
None = 0,
1010
AlphaBlend = 1 << 0, // turn on alpha blending.
11-
DepthTest = 1 << 1, // turn on depth write and test.
11+
DepthTest = 1 << 1, // turn on depth write and test.
1212
};
1313
};
1414

@@ -20,7 +20,7 @@ struct PipelineState {
2020
return Flag::AlphaBlend | Flag::DepthTest;
2121
}
2222

23-
vk::ShaderModule vertex_shader; // required.
23+
vk::ShaderModule vertex_shader; // required.
2424
vk::ShaderModule fragment_shader; // required.
2525

2626
std::span<vk::VertexInputAttributeDescription const> vertex_attributes{};
@@ -200,107 +200,3 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout,
200200
return vk::UniquePipeline{ret, m_info.device};
201201
}
202202
```
203-
204-
Add new `App` members:
205-
206-
```cpp
207-
void create_pipeline_builder();
208-
void create_pipeline();
209-
210-
// ...
211-
std::optional<PipelineBuilder> m_pipeline_builder{};
212-
213-
vk::UniquePipelineLayout m_pipeline_layout{};
214-
vk::UniquePipeline m_pipeline{};
215-
```
216-
217-
Implement and call `create_pipeline_builder()`:
218-
219-
```cpp
220-
void App::create_pipeline_builder() {
221-
auto const pipeline_builder_ci = PipelineBuilder::CreateInfo{
222-
.device = *m_device,
223-
.samples = vk::SampleCountFlagBits::e1,
224-
.color_format = m_swapchain->get_format(),
225-
};
226-
m_pipeline_builder.emplace(pipeline_builder_ci);
227-
}
228-
```
229-
230-
Complete the implementation of `create_pipeline()`:
231-
232-
```cpp
233-
// ...
234-
m_pipeline_layout = m_device->createPipelineLayoutUnique({});
235-
236-
auto const pipeline_state = PipelineState{
237-
.vertex_shader = *vertex,
238-
.fragment_shader = *fragment,
239-
};
240-
m_pipeline = m_pipeline_builder->build(*m_pipeline_layout, pipeline_state);
241-
if (!m_pipeline) {
242-
throw std::runtime_error{"Failed to create Graphics Pipeline"};
243-
}
244-
```
245-
246-
We can now bind it and use it to draw the triangle in the shader:
247-
248-
```cpp
249-
command_buffer.beginRendering(rendering_info);
250-
ImGui::ShowDemoWindow();
251-
252-
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *m_pipeline);
253-
// we are creating pipelines with dynamic viewport and scissor states.
254-
// they must be set here after binding (before drawing).
255-
auto viewport = vk::Viewport{};
256-
// flip the viewport about the X-axis (negative height):
257-
// https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/
258-
viewport.setX(0.0f)
259-
.setY(static_cast<float>(m_render_target->extent.height))
260-
.setWidth(static_cast<float>(m_render_target->extent.width))
261-
.setHeight(-viewport.y);
262-
command_buffer.setViewport(0, viewport);
263-
command_buffer.setScissor(0, render_area);
264-
// current shader has hard-coded logic for 3 vertices.
265-
command_buffer.draw(3, 1, 0, 0);
266-
```
267-
268-
![White Triangle](./white_triangle.png)
269-
270-
Updating our shaders to use interpolated RGB on each vertex:
271-
272-
```glsl
273-
// shader.vert
274-
275-
layout (location = 0) out vec3 out_color;
276-
277-
// ...
278-
const vec3 colors[] = {
279-
vec3(1.0, 0.0, 0.0),
280-
vec3(0.0, 1.0, 0.0),
281-
vec3(0.0, 0.0, 1.0),
282-
};
283-
284-
// ...
285-
out_color = colors[gl_VertexIndex];
286-
287-
// shader.frag
288-
289-
layout (location = 0) in vec3 in_color;
290-
291-
// ...
292-
out_color = vec4(in_color, 1.0);
293-
```
294-
295-
> Make sure to recompile both the SPIR-V shaders in assets/.
296-
297-
And a black clear color:
298-
299-
```cpp
300-
// ...
301-
.setClearValue(vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f});
302-
```
303-
304-
Gives us the renowned Vulkan sRGB triangle:
305-
306-
![sRGB Triangle](./srgb_triangle.png)

guide/src/pipeline/shaders.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,22 +93,18 @@ auto ShaderLoader::load(fs::path const& path) -> vk::UniqueShaderModule {
9393
Add new members to `App`:
9494

9595
```cpp
96-
void create_pipeline();
96+
void create_pipelines();
9797

9898
[[nodiscard]] auto asset_path(std::string_view uri) const -> fs::path;
9999
```
100100
101-
Implement and call `create_pipeline()` before starting the main loop:
101+
Add code to load shaders in `create_pipelines()` and call it before starting the main loop:
102102
103103
```cpp
104-
auto App::asset_path(std::string_view const uri) const -> fs::path {
105-
return m_assets_dir / uri;
106-
}
107-
108-
void App::create_pipeline() {
104+
void App::create_pipelines() {
109105
auto shader_loader = ShaderLoader{*m_device};
110-
// we only need shader modules to create the pipeline, thus no need to store
111-
// them as members.
106+
// we only need shader modules to create the pipelines, thus no need to
107+
// store them as members.
112108
auto const vertex = shader_loader.load(asset_path("shader.vert"));
113109
auto const fragment = shader_loader.load(asset_path("shader.frag"));
114110
if (!vertex || !fragment) {
@@ -118,4 +114,8 @@ void App::create_pipeline() {
118114
119115
// TODO
120116
}
117+
118+
auto App::asset_path(std::string_view const uri) const -> fs::path {
119+
return m_assets_dir / uri;
120+
}
121121
```
30.4 KB
Loading
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Switching Pipelines
2+
3+
We can use an ImGui window to inspect / tweak some pipeline state:
4+
5+
```cpp
6+
ImGui::ShowDemoWindow();
7+
8+
ImGui::SetNextWindowSize({200.0f, 100.0f}, ImGuiCond_Once);
9+
if (ImGui::Begin("Inspect")) {
10+
ImGui::Checkbox("wireframe", &m_wireframe);
11+
if (m_wireframe) {
12+
auto const& line_width_range =
13+
m_gpu.properties.limits.lineWidthRange;
14+
ImGui::SetNextItemWidth(100.0f);
15+
ImGui::DragFloat("line width", &m_line_width, 0.25f,
16+
line_width_range[0], line_width_range[1]);
17+
}
18+
}
19+
ImGui::End();
20+
```
21+
22+
Modify `draw()` to use the selected pipeline, and also set the line width:
23+
24+
```cpp
25+
auto const pipeline =
26+
m_wireframe ? *m_pipelines.wireframe : *m_pipelines.standard;
27+
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
28+
29+
// ...
30+
// line width is also a dynamic state in our pipelines, but setting it is
31+
// not required (defaults to 1.0f or the minimum reported limit).
32+
command_buffer.setLineWidth(m_line_width);
33+
```
34+
35+
And that's it.
36+
37+
![sRGB Triangle (wireframe)](./srgb_triangle_wireframe.png)
38+
39+
In a system with dynamic pipeline creation, the first frame where `m_wireframe == true` will trigger the complex pipeline building step. This can cause stutters, especially if many different shaders/pipelines are involved in a short period of "experience" time. This is why many modern games/engines have the dreaded "Building/Compiling Shaders" screen, where all the pipelines are being built and cached.

0 commit comments

Comments
 (0)