Skip to content

Commit 2f89692

Browse files
committed
Refact PipelineCache
1 parent c88dca0 commit 2f89692

File tree

10 files changed

+346
-139
lines changed

10 files changed

+346
-139
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ build/
1111
/base_code.zip
1212
/CMakeUserPresets.json
1313
/tmp.cpp
14+
15+
/pipeline_cache.data
16+
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
export module GraphicsPipeline;
2+
3+
import std;
4+
import vulkan_hpp;
5+
6+
import DataLoader;
7+
import Tools;
8+
import Device;
9+
import RenderPass;
10+
11+
export namespace vht {
12+
13+
/**
14+
* @brief 图形管线相关
15+
* @details
16+
* - 依赖:
17+
* - m_device: 逻辑设备与队列
18+
* - m_render_pass: 渲染通道
19+
* - 工作:
20+
* - 创建描述符集布局
21+
* - 创建图形管线布局和图形管线
22+
* - 可访问成员:
23+
* - descriptor_set_layout(): 描述符集布局
24+
* - pipeline_layout(): 管线布局
25+
* - pipeline(): 图形管线
26+
*/
27+
class GraphicsPipeline {
28+
std::shared_ptr<vht::Device> m_device;
29+
std::shared_ptr<vht::RenderPass> m_render_pass;
30+
std::vector<vk::raii::DescriptorSetLayout> m_descriptor_set_layouts;
31+
vk::raii::PipelineCache m_pipeline_cache{ nullptr };
32+
vk::raii::PipelineLayout m_pipeline_layout{ nullptr };
33+
vk::raii::Pipeline m_pipeline{ nullptr };
34+
public:
35+
explicit GraphicsPipeline(std::shared_ptr<vht::Device> device, std::shared_ptr<vht::RenderPass> render_pass)
36+
: m_device(std::move(device)),
37+
m_render_pass(std::move(render_pass)) {
38+
init();
39+
}
40+
41+
[[nodiscard]]
42+
const std::vector<vk::raii::DescriptorSetLayout>& descriptor_set_layouts() const { return m_descriptor_set_layouts; }
43+
[[nodiscard]]
44+
const vk::raii::PipelineLayout& pipeline_layout() const { return m_pipeline_layout; }
45+
[[nodiscard]]
46+
const vk::raii::Pipeline& pipeline() const { return m_pipeline; }
47+
48+
private:
49+
void init() {
50+
create_descriptor_set_layout();
51+
create_pipeline_cache();
52+
create_graphics_pipeline();
53+
save_pipeline_cache();
54+
}
55+
56+
void create_pipeline_cache() {
57+
vk::PipelineCacheCreateInfo pipelineCacheInfo;
58+
std::vector<char> data;
59+
if (std::ifstream in("pipeline_cache.data", std::ios::binary | std::ios::ate);
60+
in
61+
) {
62+
const size_t size = in.tellg();
63+
in.seekg(0);
64+
data.resize(size);
65+
in.read(data.data(), size);
66+
pipelineCacheInfo.setInitialData<char>(data);
67+
std::println("Pipeline cache loaded from file.");
68+
}
69+
m_pipeline_cache = m_device->device().createPipelineCache(pipelineCacheInfo);
70+
}
71+
72+
void save_pipeline_cache() const {
73+
// std::vector<::uint8_t>
74+
const auto cacheData = m_pipeline_cache.getData();
75+
std::ofstream out("pipeline_cache.data", std::ios::binary);
76+
out.write(reinterpret_cast<const char*>(cacheData.data()), cacheData.size());
77+
}
78+
79+
// 创建描述符集布局
80+
void create_descriptor_set_layout() {
81+
vk::DescriptorSetLayoutBinding uboLayoutBinding;
82+
uboLayoutBinding.binding = 0;
83+
uboLayoutBinding.descriptorType = vk::DescriptorType::eUniformBuffer;
84+
uboLayoutBinding.descriptorCount = 1;
85+
uboLayoutBinding.stageFlags = vk::ShaderStageFlagBits::eVertex;
86+
87+
vk::DescriptorSetLayoutCreateInfo uboLayoutInfo;
88+
uboLayoutInfo.setBindings( uboLayoutBinding );
89+
m_descriptor_set_layouts.emplace_back( m_device->device().createDescriptorSetLayout( uboLayoutInfo ) );
90+
91+
vk::DescriptorSetLayoutBinding samplerLayoutBinding;
92+
samplerLayoutBinding.binding = 0;
93+
samplerLayoutBinding.descriptorType = vk::DescriptorType::eCombinedImageSampler;
94+
samplerLayoutBinding.descriptorCount = 1;
95+
samplerLayoutBinding.stageFlags = vk::ShaderStageFlagBits::eFragment;
96+
vk::DescriptorSetLayoutCreateInfo samplerLayoutInfo;
97+
98+
samplerLayoutInfo.setBindings( samplerLayoutBinding );
99+
m_descriptor_set_layouts.emplace_back( m_device->device().createDescriptorSetLayout( samplerLayoutInfo ) );
100+
}
101+
// 创建图形管线
102+
void create_graphics_pipeline() {
103+
const auto vertex_shader_code = vht::read_shader("shaders/graphics.vert.spv");
104+
const auto fragment_shader_code = vht::read_shader("shaders/graphics.frag.spv");
105+
const auto vertex_shader_module = vht::create_shader_module(m_device->device(), vertex_shader_code);
106+
const auto fragment_shader_module = vht::create_shader_module(m_device->device(), fragment_shader_code);
107+
vk::PipelineShaderStageCreateInfo vertex_shader_create_info;
108+
vertex_shader_create_info.stage = vk::ShaderStageFlagBits::eVertex;
109+
vertex_shader_create_info.module = vertex_shader_module;
110+
vertex_shader_create_info.pName = "main";
111+
112+
vk::PipelineShaderStageCreateInfo fragment_shader_create_info;
113+
fragment_shader_create_info.stage = vk::ShaderStageFlagBits::eFragment;
114+
fragment_shader_create_info.module = fragment_shader_module;
115+
fragment_shader_create_info.pName = "main";
116+
117+
const auto shader_stages = { vertex_shader_create_info, fragment_shader_create_info };
118+
119+
const auto dynamic_states = { vk::DynamicState::eViewport, vk::DynamicState::eScissor };
120+
vk::PipelineDynamicStateCreateInfo dynamic_state;
121+
dynamic_state.setDynamicStates(dynamic_states);
122+
123+
const auto binding_description = vht::Vertex::get_binding_description();
124+
const auto attribute_description = vht::Vertex::get_attribute_description();
125+
vk::PipelineVertexInputStateCreateInfo vertex_input;
126+
vertex_input.setVertexBindingDescriptions(binding_description);
127+
vertex_input.setVertexAttributeDescriptions(attribute_description);
128+
129+
vk::PipelineInputAssemblyStateCreateInfo input_assembly;
130+
input_assembly.topology = vk::PrimitiveTopology::eTriangleList;
131+
132+
vk::PipelineViewportStateCreateInfo viewport_state;
133+
viewport_state.viewportCount = 1;
134+
viewport_state.scissorCount = 1;
135+
136+
vk::PipelineDepthStencilStateCreateInfo depth_stencil;
137+
depth_stencil.depthTestEnable = true;
138+
depth_stencil.depthWriteEnable = true;
139+
depth_stencil.depthCompareOp = vk::CompareOp::eLess;
140+
depth_stencil.depthBoundsTestEnable = false; // Optional
141+
depth_stencil.stencilTestEnable = false; // Optional
142+
143+
vk::PipelineRasterizationStateCreateInfo rasterizer;
144+
rasterizer.depthClampEnable = false;
145+
rasterizer.rasterizerDiscardEnable = false;
146+
rasterizer.polygonMode = vk::PolygonMode::eFill;
147+
rasterizer.lineWidth = 1.0f;
148+
rasterizer.cullMode = vk::CullModeFlagBits::eBack;
149+
rasterizer.frontFace = vk::FrontFace::eCounterClockwise;
150+
rasterizer.depthBiasEnable = false;
151+
152+
vk::PipelineMultisampleStateCreateInfo multisampling;
153+
multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1;
154+
multisampling.sampleShadingEnable = false; // default
155+
156+
vk::PipelineColorBlendAttachmentState color_blend_attachment;
157+
color_blend_attachment.blendEnable = false; // default
158+
color_blend_attachment.colorWriteMask = vk::FlagTraits<vk::ColorComponentFlagBits>::allFlags;
159+
160+
vk::PipelineColorBlendStateCreateInfo color_blend;
161+
color_blend.logicOpEnable = false;
162+
color_blend.logicOp = vk::LogicOp::eCopy;
163+
color_blend.setAttachments( color_blend_attachment );
164+
165+
vk::PipelineLayoutCreateInfo layout_create_info;
166+
167+
// 将 raii 类型转换回 vk::DescriptorSetLayout
168+
const auto set_layouts = m_descriptor_set_layouts
169+
| std::views::transform([](const auto& layout) -> vk::DescriptorSetLayout { return layout; })
170+
| std::ranges::to<std::vector>();
171+
172+
layout_create_info.setSetLayouts( set_layouts );
173+
m_pipeline_layout = m_device->device().createPipelineLayout( layout_create_info );
174+
175+
vk::GraphicsPipelineCreateInfo create_info;
176+
create_info.layout = m_pipeline_layout;
177+
178+
create_info.setStages( shader_stages );
179+
create_info.pVertexInputState = &vertex_input;
180+
create_info.pInputAssemblyState = &input_assembly;
181+
create_info.pDynamicState = &dynamic_state;
182+
create_info.pViewportState = &viewport_state;
183+
create_info.pDepthStencilState = &depth_stencil;
184+
create_info.pRasterizationState = &rasterizer;
185+
create_info.pMultisampleState = &multisampling;
186+
create_info.pColorBlendState = &color_blend;
187+
188+
create_info.renderPass = m_render_pass->render_pass();
189+
create_info.subpass = 0;
190+
191+
m_pipeline = m_device->device().createGraphicsPipeline( m_pipeline_cache, create_info );
192+
}
193+
};
194+
}
195+
196+
Binary file not shown.
File renamed without changes.
File renamed without changes.

docs/md/04/10_specialization.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ comments: true
1212
需要注意,SPIR-V 依然只是一种中间语言,而我们为其嵌入了常量值,因此它可以在生成目标 ISA 时进行优化,如常量折叠、死代码消除等。
1313
因此,它可用于(部分)代替高级着色器语言(GLSL/HLSL)中的预处理宏,实现更优雅的着色器代码。
1414

15+
> 关于特化常量:[Vulkan-Guide \[specialization constants\]](https://docs.vulkan.org/guide/latest/mapping_data_to_shaders.html#specialization-constants)
16+
1517
## **基础代码**
1618

1719
请下载并阅读下面的基础代码,这是“C++模块化”章节的第二部分代码:
@@ -59,7 +61,7 @@ void main() {
5961

6062
现在运行程序可以看到这样的图像:
6163

62-
![yellow_room](../../images/0411/yellow_room.png)
64+
![yellow_room](../../images/0410/yellow_room.png)
6365

6466
你可以尝试调整 `myColor` 的默认值,可以看到奇怪色彩的图像。
6567

@@ -112,7 +114,7 @@ fragment_shader_create_info.pSpecializationInfo = &specializationInfo;
112114

113115
经过基础章节的学习,这些字段你应该能够轻松理解。现在运行程序,你将看到这样的图像:
114116

115-
![blue_room](../../images/0411/blue_room.png)
117+
![blue_room](../../images/0410/blue_room.png)
116118

117119

118120
## **最后**

docs/md/04/11_pipelinecache.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
title: 管线缓存
3+
comments: true
4+
---
5+
# **管线缓存**
6+
7+
## **前言**
8+
9+
管线缓存(Pipeline Cache)是一种用于加速管线对象创建的机制。
10+
11+
Vulkan 管线对象的创建过程非常复杂且耗时,尤其是在首次创建时。
12+
而管线缓存可以将创建过程中产生的中间数据缓存下来,避免每次启动程序或重新创建管线时都从头开始编译和优化。
13+
14+
> 关于管线缓存:[Vulkan-Guide \[pipeline cache\]](https://docs.vulkan.org/guide/latest/pipeline_cache.html)
15+
16+
## **基础代码**
17+
18+
请下载并阅读下面的基础代码,这是“C++模块化”章节的第二部分代码:
19+
20+
**[点击下载](../../codes/04/00_cxxmodule/module_code.zip)**
21+
22+
## **创建管线缓存**
23+
24+
我们目前只有一个管线,为其添加管线缓存的方式非常简单。
25+
26+
### 1. 添加成员
27+
28+
转到 `GraphicsPipeline.cppm` 文件,添加一个成员变量管理缓存句柄,将它放在管线对象的上方:
29+
30+
```cpp
31+
vk::raii::PipelineCache m_pipeline_cache{ nullptr };
32+
```
33+
34+
> 它只在图形管线创建时使用,你也可以将他作为管线创建函数的局部变量。
35+
36+
### 2. 创建
37+
38+
在管线创建函数 `create_graphics_pipeline` 之前,添加一个函数 `create_pipeline_cache` :
39+
40+
```cpp
41+
void init() {
42+
create_descriptor_set_layout();
43+
create_pipeline_cache();
44+
create_graphics_pipeline();
45+
}
46+
47+
void create_pipeline_cache() {
48+
vk::PipelineCacheCreateInfo pipelineCacheInfo;
49+
m_pipeline_cache = m_device->device().createPipelineCache(pipelineCacheInfo);
50+
}
51+
```
52+
53+
目前我们还没有保存任何缓存数据,无需读取本地文件,直接创建即可。
54+
55+
### 3. 使用
56+
57+
58+
在“管线创建”章节的最后,我提到过管线创建函数的第一个参数就是管线缓存,当时我们使用了 `nullptr` 表示无缓存。
59+
现在回到 `create_graphics_pipeline` 函数,将其改为我们刚刚创建的管线缓存对象:
60+
61+
```cpp
62+
void create_graphics_pipeline() {
63+
......
64+
65+
m_pipeline = m_device->device().createGraphicsPipeline( m_pipeline_cache, create_info );
66+
}
67+
```
68+
69+
### 4. 测试
70+
71+
现在重新运行程序,应该和上一章的效果没有任何区别。
72+
73+
## **保存与加载缓存**
74+
75+
上面虽然使用了管线缓存,但缓存的内容是空的,没有起到任何作用。
76+
我们需要在管线创建完成后,将管线信息保存到本地,以便下一次运行时读取。
77+
78+
### 1. 保存管线信息
79+
80+
创建一个 `save_pipeline_cache` 函数用于保存管线数据,在 `create_graphics_pipeline` 后立即调用:
81+
82+
```cpp
83+
void init() {
84+
create_descriptor_set_layout();
85+
create_pipeline_cache();
86+
create_graphics_pipeline();
87+
save_pipeline_cache();
88+
}
89+
void save_pipeline_cache() {
90+
// std::vector<uint8_t>
91+
const auto cacheData = m_pipeline_cache.getData();
92+
std::ofstream out("pipeline_cache.data", std::ios::binary); // 文件后缀任意
93+
out.write(reinterpret_cast<const char*>(cacheData.data()), cacheData.size());
94+
}
95+
```
96+
97+
注意到,我们直接从缓存对象 `m_pipeline_cache` 中读取的数据,因为我们在创建管线时绑定了缓存,管线会将相关数据存入缓存对象。
98+
99+
### 2. 读取本地缓存
100+
101+
现在可以修改 `create_pipeline_cache` 函数,查询本地是否存在缓冲文件。
102+
如果存在就读取缓冲,如果不存在就直接创建。
103+
104+
```cpp
105+
void create_pipeline_cache() {
106+
vk::PipelineCacheCreateInfo pipelineCacheInfo;
107+
std::vector<char> data;
108+
if (std::ifstream in("pipeline_cache.data", std::ios::binary | std::ios::ate);
109+
in
110+
) {
111+
const size_t size = in.tellg();
112+
in.seekg(0);
113+
data.resize(size);
114+
in.read(data.data(), size);
115+
pipelineCacheInfo.setInitialData<char>(data);
116+
std::println("Pipeline cache loaded from file.");
117+
}
118+
m_pipeline_cache = m_device->device().createPipelineCache(pipelineCacheInfo);
119+
}
120+
```
121+
122+
如果文件存在,会读取本地数据并存入 `data` 变量中,并通过 `setInitialData` 模板函数设置数据的指针和长度。
123+
`createInfo` 保存的是指针和长度信息,所以要保证 `data` 在创建缓存时还有效,不能放在 `if` 块中。
124+
125+
## **测试**
126+
127+
现在运行程序,应该看到一样的内容。
128+
129+
![right_room](../../images/0310/right_room.png)
130+
131+
由于我们的程序比较简单,你可能察觉不到创建速度的差异,但它在实际项目中非常重要。
132+
133+
---
134+
135+
**[GraphicsPipeline.cppm](../../codes/04/11_pipelinecache/GraphicsPipeline.cppm)**
136+
137+
**[GraphicsPipeline.diff\(差异文件\)](../../codes/04/11_pipelinecache/GraphicsPipeline.diff)**
138+
139+
---
140+

0 commit comments

Comments
 (0)