|
1 | 1 | # 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 | + |
| 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 | + |
0 commit comments