From 653534ec35d2b43fe3eb74c32a4d152649a2c46b Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Sun, 23 Mar 2025 22:55:11 -0700 Subject: [PATCH 1/7] Load shaders --- assets/shader.frag | Bin 0 -> 408 bytes assets/shader.vert | Bin 0 -> 1228 bytes guide/src/SUMMARY.md | 3 + guide/src/pipeline/README.md | 14 +++ guide/src/pipeline/locating_assets.md | 83 ++++++++++++++++++ guide/src/pipeline/shaders.md | 119 ++++++++++++++++++++++++++ src/app.cpp | 44 +++++++++- src/app.hpp | 10 ++- src/glsl/shader.frag | 7 ++ src/glsl/shader.vert | 13 +++ src/main.cpp | 4 +- src/shader_loader.cpp | 33 +++++++ src/shader_loader.hpp | 20 +++++ 13 files changed, 347 insertions(+), 3 deletions(-) create mode 100644 assets/shader.frag create mode 100644 assets/shader.vert create mode 100644 guide/src/pipeline/README.md create mode 100644 guide/src/pipeline/locating_assets.md create mode 100644 guide/src/pipeline/shaders.md create mode 100644 src/glsl/shader.frag create mode 100644 src/glsl/shader.vert create mode 100644 src/shader_loader.cpp create mode 100644 src/shader_loader.hpp diff --git a/assets/shader.frag b/assets/shader.frag new file mode 100644 index 0000000000000000000000000000000000000000..33b437101d3cabdbac1815fb0f3a4df76a79b825 GIT binary patch literal 408 zcmYk2%}T>S6orpTC)UPa3UwzHmr8M=A_%&Y*#x@i0}Lg}Vjw1=O$*)mY(ABng6B+K zWZ>k^J^AiW5=VE(z!6fM;S`5+g@_L5B_Y~ukc-arLXnk zgt{ankIi|D*%A0*!2l!J%-Q+eW}hx^Tet3ZRp}~Um#*-uGVlDh6gTW~zRs&n(FY8P z{!9ZC<~Ezo`RFCD}9&P+X3x91;!rTze*oGD%a literal 0 HcmV?d00001 diff --git a/assets/shader.vert b/assets/shader.vert new file mode 100644 index 0000000000000000000000000000000000000000..60ac804274b49bff2e8b1cdb637b922eec422d09 GIT binary patch literal 1228 zcmYk5U279j5Qa~aZmg|st+lNmwN0$wmg0qqAX-Ev3V}un-iDYgvaoJLvX$zU|4i^# zc_a8dNlwX>w`b;^_spD`CDqE_g%IY#Qdka;!o=1>1t!4N#%Hs2+&UPQ{e!2^o~T#} z)v-{`YT!CH559G?LVpu9K$X7==O2XgVKFpYo#xR|v-Q5y8w@(5@<*O_@~lWZ{cM=_ z%ItfpZed236}|klKPyO3%=St`5-!qXa5Bou>}1?O;oZLBYu@>s4$JiHW6@8~0?H&@ zhtCYVUvk>P)WYsW+fIiQIU($X*QZW9maj3ddS@T=Sy3KmKP|31u;2U4V{Qe= ziKtn{#}1?BGJ)KzW=#baHCOQE)a}#B-s`~n-Z$@?uX5IbyK<(DsW1O>8v6}m{u;jT zw4OWJ=5!8s7-znPuilw=IL*uL;Q7ts9dp9Ibx!wg-oC~SAZK0Qa~o)JFL%b#uS4%Y zz*lSS=j_sNwf%1XPMpKonfxsnzaqBR7I5y@+&1U#-cZ52H-MZP_1?P) zAb$dSsw!T<4){pto|PrUV#6> CuwSzP literal 0 HcmV?d00001 diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index f04d2c3..a61b69b 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -24,3 +24,6 @@ - [Dear ImGui](dear_imgui/README.md) - [class DearImGui](dear_imgui/dear_imgui.md) - [ImGui Integration](dear_imgui/imgui_integration.md) +- [Graphics Pipeline](pipeline/README.md) + - [Locating Assets](pipeline/locating_assets.md) + - [Shaders](pipeline/shaders.md) diff --git a/guide/src/pipeline/README.md b/guide/src/pipeline/README.md new file mode 100644 index 0000000..75f8821 --- /dev/null +++ b/guide/src/pipeline/README.md @@ -0,0 +1,14 @@ +# Graphics Pipeline + +A [Vulkan Graphics Pipeline](https://docs.vulkan.org/spec/latest/chapters/pipelines.html) is a large object that encompasses the entire graphics pipeline. It consists of many stages - all this happens during a single `draw()` call. We again constrain ourselves to caring about a small subset: + +1. Input Assembly: vertex buffers are read here +1. Vertex Shader: shader is run for each vertex in the primitive +1. Early Fragment Tests (EFT): pre-shading tests +1. Fragment Shader: shader is run for each fragment +1. Late Fragment Tests (LFT): depth buffer is written here +1. Color Blending: transparency + +A Graphics Pipeline's specification broadly includes configuration of the vertex attributes and fixed state (baked into the pipeline), dynamic states (must be set at draw-time), shader code (SPIR-V), and its layout (Descriptor Sets and Push Constants). Creation of a pipeline is verbose _and_ expensive, most engines use some sort of "hash and cache" approach to optimize reuse of existing pipelines. The Descriptor Set Layouts of a shader program need to be explicitly specified, engines can either dictate a static layout or use runtime reflection via [SPIR-V Cross](https://github.com/KhronosGroup/SPIRV-Cross). + +We shall use a single Pipeline Layout that evolves over chapters. diff --git a/guide/src/pipeline/locating_assets.md b/guide/src/pipeline/locating_assets.md new file mode 100644 index 0000000..1dfaaeb --- /dev/null +++ b/guide/src/pipeline/locating_assets.md @@ -0,0 +1,83 @@ +# Locating Assets + +Before we can use shaders (and thus graphics pipelines), we need to load them as asset/data files. To do that correctly, first the asset directory needs to be located. There are a few ways to go about this, we will use the approach of looking for a particular subdirectory, starting from the working directory and walking up the parent directory tree. This enables `app` in any project/build subdirectory to locate `assets/` in the various examples below: + +``` +. +|-- assets/ +|-- app +|-- build/ + |-- app +|-- out/ + |-- default/Release/ + |-- app + |-- ubsan/Debug/ + |-- app +``` + +In a release package you would want to use the path to the executable instead (and probably not perform an "upfind" walk), the working directory could be anywhere whereas assets shipped with the package will be in the vicinity of the executable. + +## Assets Directory + +Add a member to `App` to store this path to `assets/`: + +```cpp +namespace fs = std::filesystem; + +// ... +fs::path m_assets_dir{}; +``` + +Add a helper function to locate the assets dir, and assign `m_assets_dir` to its return value at the top of `run()`: + +```cpp +[[nodiscard]] auto locate_assets_dir() -> fs::path { + // look for '/assets/', starting from the working + // directory and walking up the parent directory tree. + static constexpr std::string_view dir_name_v{"assets"}; + for (auto path = fs::current_path(); + !path.empty() && path.has_parent_path(); path = path.parent_path()) { + auto ret = path / dir_name_v; + if (fs::is_directory(ret)) { return ret; } + } + std::println("[lvk] Warning: could not locate 'assets' directory"); + return fs::current_path(); +} + +// ... +m_assets_dir = locate_assets_dir(); +``` + +We can also support a command line argument to override this algorithm: + +```cpp +// app.hpp +void run(std::string_view assets_dir); + +// app.cpp +[[nodiscard]] auto locate_assets_dir(std::string_view const in) -> fs::path { + if (!in.empty()) { + std::println("[lvk] Using custom assets directory: '{}'", in); + return in; + } + // ... +} + +// ... +void App::run(std::string_view const assets_dir) { + m_assets_dir = locate_assets_dir(assets_dir); + // ... +} + +// main.cpp +auto assets_dir = std::string_view{}; + +// ... +if (arg == "-x" || arg == "--force-x11") { + glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11); +} +if (arg == "-a" || arg == "--assets") { assets_dir = arg; } + +// ... +lvk::App{}.run(assets_dir); +``` diff --git a/guide/src/pipeline/shaders.md b/guide/src/pipeline/shaders.md new file mode 100644 index 0000000..795bfec --- /dev/null +++ b/guide/src/pipeline/shaders.md @@ -0,0 +1,119 @@ +# Shaders + +Shaders work in NDC space: -1 to +1 for X and Y. We set up a triangle's coordinates and output that in the vertex shader save it to `src/glsl/shader.vert`: + +```glsl +#version 450 core + +void main() { + const vec2 positions[] = { + vec2(-0.5, -0.5), + vec2(0.5, -0.5), + vec2(0.0, 0.5), + }; + + const vec2 position = positions[gl_VertexIndex]; + + gl_Position = vec4(position, 0.0, 1.0); +} +``` + +The fragment shader just outputs white for now, in `src/glsl/shader.frag`: + +```glsl +#version 450 core + +layout (location = 0) out vec4 out_color; + +void main() { + out_color = vec4(1.0); +} +``` + +Compile both shaders into `assets/`: + +``` +glslc src/glsl/shader.vert -o assets/shader.vert +glslc src/glsl/shader.frag -o assets/shader.frag +``` + +## Shader Modules + +SPIR-V modules are binary files with a stride/alignment of 4 bytes. The Vulkan API accepts a span of `std::uint32_t`s, so we need to load it into such a buffer (and _not_ `std::vector` or other 1-byte equivalents). + +Add a new `class ShaderLoader`: + +```cpp +class ShaderLoader { + public: + explicit ShaderLoader(vk::Device const device) : m_device(device) {} + + [[nodiscard]] auto load(fs::path const& path) -> vk::UniqueShaderModule; + + private: + vk::Device m_device{}; + std::vector m_code{}; +}; +``` + +Implement `load()`: + +```cpp +auto ShaderLoader::load(fs::path const& path) -> vk::UniqueShaderModule { + // open the file at the end, to get the total size. + auto file = std::ifstream{path, std::ios::binary | std::ios::ate}; + if (!file.is_open()) { + std::println(stderr, "Failed to open file: '{}'", + path.generic_string()); + return {}; + } + + auto const size = file.tellg(); + auto const usize = static_cast(size); + // file data must be uint32 aligned. + if (usize % sizeof(std::uint32_t) != 0) { + std::println(stderr, "Invalid SPIR-V size: {}", usize); + return {}; + } + + // seek to the beginning before reading. + file.seekg({}, std::ios::beg); + m_code.resize(usize / sizeof(std::uint32_t)); + void* data = m_code.data(); + file.read(static_cast(data), size); + + auto shader_module_ci = vk::ShaderModuleCreateInfo{}; + shader_module_ci.setCode(m_code); + return m_device.createShaderModuleUnique(shader_module_ci); +} +``` + +Add new members to `App`: + +```cpp +void create_pipeline(); + +[[nodiscard]] auto asset_path(std::string_view uri) const -> fs::path; +``` + +Implement and call `create_pipeline()` before starting the main loop: + +```cpp +auto App::asset_path(std::string_view const uri) const -> fs::path { + return m_assets_dir / uri; +} + +void App::create_pipeline() { + auto shader_loader = ShaderLoader{*m_device}; + // we only need shader modules to create the pipeline, thus no need to store + // them as members. + auto const vertex = shader_loader.load(asset_path("shader.vert")); + auto const fragment = shader_loader.load(asset_path("shader.frag")); + if (!vertex || !fragment) { + throw std::runtime_error{"Failed to load Shaders"}; + } + std::println("[lvk] Shaders loaded"); + + // TODO +} +``` diff --git a/src/app.cpp b/src/app.cpp index 16583f5..96b17db 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -9,7 +10,28 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE namespace lvk { using namespace std::chrono_literals; -void App::run() { +namespace { +[[nodiscard]] auto locate_assets_dir(std::string_view const in) -> fs::path { + if (!in.empty()) { + std::println("[lvk] Using custom assets directory: '{}'", in); + return in; + } + // look for '/assets/', starting from the working + // directory and walking up the parent directory tree. + static constexpr std::string_view dir_name_v{"assets"}; + for (auto path = fs::current_path(); + !path.empty() && path.has_parent_path(); path = path.parent_path()) { + auto ret = path / dir_name_v; + if (fs::is_directory(ret)) { return ret; } + } + std::println("[lvk] Warning: could not locate 'assets' directory"); + return fs::current_path(); +} +} // namespace + +void App::run(std::string_view const assets_dir) { + m_assets_dir = locate_assets_dir(assets_dir); + create_window(); create_instance(); create_surface(); @@ -19,6 +41,8 @@ void App::run() { create_render_sync(); create_imgui(); + create_pipeline(); + main_loop(); } @@ -153,6 +177,24 @@ void App::create_imgui() { m_imgui.emplace(imgui_ci); } +auto App::asset_path(std::string_view const uri) const -> fs::path { + return m_assets_dir / uri; +} + +void App::create_pipeline() { + auto shader_loader = ShaderLoader{*m_device}; + // we only need shader modules to create the pipeline, thus no need to store + // them as members. + auto const vertex = shader_loader.load(asset_path("shader.vert")); + auto const fragment = shader_loader.load(asset_path("shader.frag")); + if (!vertex || !fragment) { + throw std::runtime_error{"Failed to load Shaders"}; + } + std::println("[lvk] Shaders loaded"); + + // TODO +} + void App::main_loop() { while (glfwWindowShouldClose(m_window.get()) == GLFW_FALSE) { glfwPollEvents(); diff --git a/src/app.hpp b/src/app.hpp index 788f44b..5768e80 100644 --- a/src/app.hpp +++ b/src/app.hpp @@ -6,11 +6,14 @@ #include #include #include +#include namespace lvk { +namespace fs = std::filesystem; + class App { public: - void run(); + void run(std::string_view assets_dir); private: struct RenderSync { @@ -32,6 +35,9 @@ class App { void create_swapchain(); void create_render_sync(); void create_imgui(); + void create_pipeline(); + + [[nodiscard]] auto asset_path(std::string_view uri) const -> fs::path; void main_loop(); @@ -42,6 +48,8 @@ class App { void transition_for_present(vk::CommandBuffer command_buffer) const; void submit_and_present(); + fs::path m_assets_dir{}; + // the order of these RAII members is crucially important. glfw::Window m_window{}; vk::UniqueInstance m_instance{}; diff --git a/src/glsl/shader.frag b/src/glsl/shader.frag new file mode 100644 index 0000000..714e1a1 --- /dev/null +++ b/src/glsl/shader.frag @@ -0,0 +1,7 @@ +#version 450 core + +layout (location = 0) out vec4 out_color; + +void main() { + out_color = vec4(1.0); +} diff --git a/src/glsl/shader.vert b/src/glsl/shader.vert new file mode 100644 index 0000000..c620611 --- /dev/null +++ b/src/glsl/shader.vert @@ -0,0 +1,13 @@ +#version 450 core + +void main() { + const vec2 positions[] = { + vec2(-0.5, -0.5), + vec2(0.5, -0.5), + vec2(0.0, 0.5), + }; + + const vec2 position = positions[gl_VertexIndex]; + + gl_Position = vec4(position, 0.0, 1.0); +} diff --git a/src/main.cpp b/src/main.cpp index 238dd5c..ffeaee6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ auto main(int argc, char** argv) -> int { try { + auto assets_dir = std::string_view{}; // skip the first argument. auto args = std::span{argv, static_cast(argc)}.subspan(1); while (!args.empty()) { @@ -12,9 +13,10 @@ auto main(int argc, char** argv) -> int { if (arg == "-x" || arg == "--force-x11") { glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11); } + if (arg == "-a" || arg == "--assets") { assets_dir = arg; } args = args.subspan(1); } - lvk::App{}.run(); + lvk::App{}.run(assets_dir); } catch (std::exception const& e) { std::println(stderr, "PANIC: {}", e.what()); return EXIT_FAILURE; diff --git a/src/shader_loader.cpp b/src/shader_loader.cpp new file mode 100644 index 0000000..39eb8c9 --- /dev/null +++ b/src/shader_loader.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +namespace lvk { +auto ShaderLoader::load(fs::path const& path) -> vk::UniqueShaderModule { + // open the file at the end, to get the total size. + auto file = std::ifstream{path, std::ios::binary | std::ios::ate}; + if (!file.is_open()) { + std::println(stderr, "Failed to open file: '{}'", + path.generic_string()); + return {}; + } + + auto const size = file.tellg(); + auto const usize = static_cast(size); + // file data must be uint32 aligned. + if (usize % sizeof(std::uint32_t) != 0) { + std::println(stderr, "Invalid SPIR-V size: {}", usize); + return {}; + } + + // seek to the beginning before reading. + file.seekg({}, std::ios::beg); + m_code.resize(usize / sizeof(std::uint32_t)); + void* data = m_code.data(); + file.read(static_cast(data), size); + + auto shader_module_ci = vk::ShaderModuleCreateInfo{}; + shader_module_ci.setCode(m_code); + return m_device.createShaderModuleUnique(shader_module_ci); +} +} // namespace lvk diff --git a/src/shader_loader.hpp b/src/shader_loader.hpp new file mode 100644 index 0000000..77f5b04 --- /dev/null +++ b/src/shader_loader.hpp @@ -0,0 +1,20 @@ +#pragma once +#include +#include +#include +#include + +namespace lvk { +namespace fs = std::filesystem; + +class ShaderLoader { + public: + explicit ShaderLoader(vk::Device const device) : m_device(device) {} + + [[nodiscard]] auto load(fs::path const& path) -> vk::UniqueShaderModule; + + private: + vk::Device m_device{}; + std::vector m_code{}; +}; +} // namespace lvk From 87024ddd0865583b8d7fd2f9846e3942e12e9628 Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Sun, 23 Mar 2025 23:31:23 -0700 Subject: [PATCH 2/7] WIP --- guide/src/SUMMARY.md | 1 + guide/src/pipeline/creation.md | 1 + src/app.cpp | 34 +++++++++++-- src/app.hpp | 7 ++- src/pipeline_builder.cpp | 87 ++++++++++++++++++++++++++++++++++ src/pipeline_builder.hpp | 25 ++++++++++ src/pipeline_state.hpp | 29 ++++++++++++ 7 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 guide/src/pipeline/creation.md create mode 100644 src/pipeline_builder.cpp create mode 100644 src/pipeline_builder.hpp create mode 100644 src/pipeline_state.hpp diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index a61b69b..9c73d7a 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -27,3 +27,4 @@ - [Graphics Pipeline](pipeline/README.md) - [Locating Assets](pipeline/locating_assets.md) - [Shaders](pipeline/shaders.md) + - [Pipeline Creation](pipeline/creation.md) diff --git a/guide/src/pipeline/creation.md b/guide/src/pipeline/creation.md new file mode 100644 index 0000000..e8e8396 --- /dev/null +++ b/guide/src/pipeline/creation.md @@ -0,0 +1 @@ +# Pipeline Creation diff --git a/src/app.cpp b/src/app.cpp index 96b17db..7dd25b8 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -40,6 +40,7 @@ void App::run(std::string_view const assets_dir) { create_swapchain(); create_render_sync(); create_imgui(); + create_pipeline_builder(); create_pipeline(); @@ -177,8 +178,13 @@ void App::create_imgui() { m_imgui.emplace(imgui_ci); } -auto App::asset_path(std::string_view const uri) const -> fs::path { - return m_assets_dir / uri; +void App::create_pipeline_builder() { + auto const pipeline_builder_ci = PipelineBuilder::CreateInfo{ + .device = *m_device, + .samples = vk::SampleCountFlagBits::e1, + .color_format = m_swapchain->get_format(), + }; + m_pipeline_builder.emplace(pipeline_builder_ci); } void App::create_pipeline() { @@ -192,7 +198,19 @@ void App::create_pipeline() { } std::println("[lvk] Shaders loaded"); - // TODO + m_pipeline_layout = m_device->createPipelineLayoutUnique({}); + auto const pipeline_state = PipelineState{ + .vertex_shader = *vertex, + .fragment_shader = *fragment, + }; + m_pipeline = m_pipeline_builder->build(*m_pipeline_layout, pipeline_state); + if (!m_pipeline) { + throw std::runtime_error{"Failed to create Graphics Pipeline"}; + } +} + +auto App::asset_path(std::string_view const uri) const -> fs::path { + return m_assets_dir / uri; } void App::main_loop() { @@ -282,7 +300,15 @@ void App::render(vk::CommandBuffer const command_buffer) { command_buffer.beginRendering(rendering_info); ImGui::ShowDemoWindow(); - // draw stuff here. + command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *m_pipeline); + auto viewport = vk::Viewport{}; + viewport.setX(0.0f) + .setY(static_cast(m_render_target->extent.height)) + .setWidth(static_cast(m_render_target->extent.width)) + .setHeight(-viewport.y); + command_buffer.setViewport(0, viewport); + command_buffer.setScissor(0, render_area); + command_buffer.draw(3, 1, 0, 0); command_buffer.endRendering(); m_imgui->end_frame(); diff --git a/src/app.hpp b/src/app.hpp index 5768e80..a9d5824 100644 --- a/src/app.hpp +++ b/src/app.hpp @@ -1,10 +1,10 @@ #pragma once #include #include +#include #include #include #include -#include #include #include @@ -35,6 +35,7 @@ class App { void create_swapchain(); void create_render_sync(); void create_imgui(); + void create_pipeline_builder(); void create_pipeline(); [[nodiscard]] auto asset_path(std::string_view uri) const -> fs::path; @@ -67,6 +68,10 @@ class App { std::size_t m_frame_index{}; std::optional m_imgui{}; + std::optional m_pipeline_builder{}; + + vk::UniquePipelineLayout m_pipeline_layout{}; + vk::UniquePipeline m_pipeline{}; glm::ivec2 m_framebuffer_size{}; std::optional m_render_target{}; diff --git a/src/pipeline_builder.cpp b/src/pipeline_builder.cpp new file mode 100644 index 0000000..17d38d7 --- /dev/null +++ b/src/pipeline_builder.cpp @@ -0,0 +1,87 @@ +#include + +namespace lvk { +PipelineBuilder::PipelineBuilder(CreateInfo const& create_info) + : m_info(create_info) {} + +auto PipelineBuilder::build(vk::PipelineLayout const layout, + PipelineState const& state) const + -> vk::UniquePipeline { + auto shader_stages = std::array{}; + shader_stages[0] + .setStage(vk::ShaderStageFlagBits::eVertex) + .setPName("main") + .setModule(state.vertex_shader); + shader_stages[1] + .setStage(vk::ShaderStageFlagBits::eFragment) + .setPName("main") + .setModule(state.fragment_shader); + + auto prsci = vk::PipelineRasterizationStateCreateInfo{}; + prsci.setPolygonMode(state.polygon_mode).setCullMode(state.cull_mode); + + auto pdssci = vk::PipelineDepthStencilStateCreateInfo{}; + auto const depth_test = + (state.flags & PipelineFlag::DepthTest) == PipelineFlag::DepthTest; + pdssci.setDepthTestEnable(depth_test ? vk::True : vk::False) + .setDepthCompareOp(state.depth_compare); + + auto const piasci = + vk::PipelineInputAssemblyStateCreateInfo{{}, state.topology}; + + auto pcbas = vk::PipelineColorBlendAttachmentState{}; + auto const alpha_blend = + (state.flags & PipelineFlag::AlphaBlend) == PipelineFlag::AlphaBlend; + using CCF = vk::ColorComponentFlagBits; + pcbas.setColorWriteMask(CCF::eR | CCF::eG | CCF::eB | CCF::eA) + .setBlendEnable(alpha_blend ? vk::True : vk::False) + .setSrcColorBlendFactor(vk::BlendFactor::eSrcAlpha) + .setDstColorBlendFactor(vk::BlendFactor::eOneMinusSrcAlpha) + .setColorBlendOp(vk::BlendOp::eAdd) + .setSrcAlphaBlendFactor(vk::BlendFactor::eOne) + .setDstAlphaBlendFactor(vk::BlendFactor::eZero) + .setAlphaBlendOp(vk::BlendOp::eAdd); + auto pcbsci = vk::PipelineColorBlendStateCreateInfo{}; + pcbsci.setAttachments(pcbas); + + auto const pdscis = std::array{ + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + vk::DynamicState::eLineWidth, + }; + auto pdsci = vk::PipelineDynamicStateCreateInfo{}; + pdsci.setDynamicStates(pdscis); + + auto const pvsci = vk::PipelineViewportStateCreateInfo({}, 1, {}, 1); + + auto pmsci = vk::PipelineMultisampleStateCreateInfo{}; + pmsci.setRasterizationSamples(m_info.samples) + .setSampleShadingEnable(vk::False); + + auto prci = vk::PipelineRenderingCreateInfo{}; + if (m_info.color_format != vk::Format::eUndefined) { + prci.setColorAttachmentFormats(m_info.color_format); + } + prci.setDepthAttachmentFormat(m_info.depth_format); + + auto gpci = vk::GraphicsPipelineCreateInfo{}; + gpci.setStages(shader_stages) + .setPRasterizationState(&prsci) + .setPDepthStencilState(&pdssci) + .setPInputAssemblyState(&piasci) + .setPColorBlendState(&pcbsci) + .setPDynamicState(&pdsci) + .setPViewportState(&pvsci) + .setPMultisampleState(&pmsci) + .setLayout(layout) + .setPNext(&prci); + + auto ret = vk::Pipeline{}; + if (m_info.device.createGraphicsPipelines({}, 1, &gpci, {}, &ret) != + vk::Result::eSuccess) { + return {}; + } + + return vk::UniquePipeline{ret, m_info.device}; +} +} // namespace lvk diff --git a/src/pipeline_builder.hpp b/src/pipeline_builder.hpp new file mode 100644 index 0000000..02e8afc --- /dev/null +++ b/src/pipeline_builder.hpp @@ -0,0 +1,25 @@ +#pragma once +#include + +namespace lvk { +struct PipelineBuilderCreateInfo { + vk::Device device{}; + vk::SampleCountFlagBits samples{}; + vk::Format color_format{}; + vk::Format depth_format{}; +}; + +class PipelineBuilder { + public: + using CreateInfo = PipelineBuilderCreateInfo; + + explicit PipelineBuilder(CreateInfo const& create_info); + + [[nodiscard]] auto build(vk::PipelineLayout layout, + PipelineState const& state) const + -> vk::UniquePipeline; + + private: + CreateInfo m_info{}; +}; +} // namespace lvk diff --git a/src/pipeline_state.hpp b/src/pipeline_state.hpp new file mode 100644 index 0000000..f314a0b --- /dev/null +++ b/src/pipeline_state.hpp @@ -0,0 +1,29 @@ +#pragma once +#include + +namespace lvk { +struct PipelineFlag { + enum : std::uint8_t { + None = 0, + AlphaBlend = 1 << 0, + DepthTest = 1 << 1, + }; +}; + +struct PipelineState { + using Flag = PipelineFlag; + + [[nodiscard]] static constexpr auto default_flags() -> std::uint8_t { + return Flag::AlphaBlend | Flag::DepthTest; + } + + vk::ShaderModule vertex_shader; + vk::ShaderModule fragment_shader; + + vk::PrimitiveTopology topology{vk::PrimitiveTopology::eTriangleList}; + vk::PolygonMode polygon_mode{vk::PolygonMode::eFill}; + vk::CullModeFlags cull_mode{vk::CullModeFlagBits::eNone}; + vk::CompareOp depth_compare{vk::CompareOp::eLess}; + std::uint8_t flags{default_flags()}; +}; +} // namespace lvk From f6ddd0b8248e40f60d227b8b3696affda932c48d Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Mon, 24 Mar 2025 00:02:44 -0700 Subject: [PATCH 3/7] sRGB triangle --- assets/shader.frag | Bin 408 -> 572 bytes assets/shader.vert | Bin 1228 -> 1572 bytes guide/src/pipeline/creation.md | 253 ++++++++++++++++++++++++++ guide/src/pipeline/srgb_triangle.png | Bin 0 -> 84213 bytes guide/src/pipeline/white_triangle.png | Bin 0 -> 29161 bytes src/app.cpp | 8 +- src/glsl/shader.frag | 4 +- src/glsl/shader.vert | 9 + src/pipeline_builder.cpp | 9 + src/pipeline_state.hpp | 15 +- 10 files changed, 292 insertions(+), 6 deletions(-) create mode 100644 guide/src/pipeline/srgb_triangle.png create mode 100644 guide/src/pipeline/white_triangle.png diff --git a/assets/shader.frag b/assets/shader.frag index 33b437101d3cabdbac1815fb0f3a4df76a79b825..782c404c1bb50d695218f562a042a094c51bcc77 100644 GIT binary patch delta 287 zcmX|+y9&ZU6htR4gC^^`D1Db%fg`IV<@nifMKSXdwRu f*)P|yp3AyK$OSZs&MU9J9-MMS{e^mOjx!=34LTFH delta 144 zcmdnPGJ~0ynMs+Qfq{{Mn}L^sXCkkuFdG940|OZ6CT8XVnGBp0Biko_5tuB(7!ack zlwkvkae*Wl8tio#*nyA%s2w5(5{F?k237_pARlBN2rvWj51DK diff --git a/assets/shader.vert b/assets/shader.vert index 60ac804274b49bff2e8b1cdb637b922eec422d09..1d47401067651e26d7355fbb9845e57b9f251913 100644 GIT binary patch literal 1572 zcmZXU?N1Xy5XP65D+mH20=^&~qM}mk8z6~MW5k>$O~8=&^-`}Fv&ngF+N(i7`Jb8i zSNX-n=eKu@RhLX>=9y<_W@neH*EXk&nJ}|v&g`3VEt;ATBdp`DE8>ew7-MY1k|T-RSs`iIDqX2wMAI66Fx+OOlJ-;amImn@Alo2PNt z4$`EspHq0#V?3KD*{C~C2*en#7ns1DQH_3YXbanOJp}5VK0apg`*cvGCvWp^dSb*3 z%q8I#xAP%WIfv12Z&budFY65|Z0kQ>!?c7c=TSN+)5=w)62~Uy+h~|6$7fCsw|Q}F zzfw{=${4dz>{Vv_2Xo~*^kft5oZS1oc?3j{BZ$n-vp?}2V*DF8#2j8rGxWwR7=Z1t_?1Mvpx1{NxIR5R*r(e#qEzNm& z6aEgHvL__;e@~h?*t#sOJ-r3q7(KAS ze+mht$;G?l`@lKyxf8wt-sg;4mH$j}YOPAR$LGohm$&dz8Oe270tN>@d9Fyn;ZqCW z=ejA|=b{dCCKt23BLRa0pSkkC3{EcI0=}>FNH+Q3X+Lv)ECFMAY)3X2cW|Wcn9;5T ZE-`=a^uQdSNSHPAr6xZ7U&^pA`3)_5bHo4u delta 438 zcmX|-&q@MO6voe;e1XfP||UFeP#;`U=Bcxo^|DM`-IS z34Y&o^ulk>Ip6tn&z+zAF6-M?Gb5rTC%$wOzh6Ow_9WCjRk;eO z;(W~Bqs4c;j2Cn9B!lc!RaK~0=DStZLE~5`Lk#4Y9O#0f*up6m7>R3qxF%0)G_9;+ z`BcSD`;__XSZ_*?oc#J4k^%uPsKI)GS`8jsgH&UV)KOQ5S-VR$?i}Hlald=OTIMeFms std::uint8_t { + return Flag::AlphaBlend | Flag::DepthTest; + } + + vk::ShaderModule vertex_shader; // required. + vk::ShaderModule fragment_shader; // required. + + std::span vertex_attributes{}; + std::span vertex_bindings{}; + + vk::PrimitiveTopology topology{vk::PrimitiveTopology::eTriangleList}; + vk::PolygonMode polygon_mode{vk::PolygonMode::eFill}; + vk::CullModeFlags cull_mode{vk::CullModeFlagBits::eNone}; + vk::CompareOp depth_compare{vk::CompareOp::eLess}; + std::uint8_t flags{default_flags()}; +}; +``` + +Encapsulate the exhausting process of building a pipeline into its own class: + +```cpp +struct PipelineBuilderCreateInfo { + vk::Device device{}; + vk::SampleCountFlagBits samples{}; + vk::Format color_format{}; + vk::Format depth_format{}; +}; + +class PipelineBuilder { + public: + using CreateInfo = PipelineBuilderCreateInfo; + + explicit PipelineBuilder(CreateInfo const& create_info); + + [[nodiscard]] auto build(vk::PipelineLayout layout, + PipelineState const& state) const + -> vk::UniquePipeline; + + private: + CreateInfo m_info{}; +}; +``` + +Implement `build()`: + +```cpp +auto PipelineBuilder::build(vk::PipelineLayout const layout, + PipelineState const& state) const + -> vk::UniquePipeline { + // set vertex (0) and fragment (1) shader stages. + auto shader_stages = std::array{}; + shader_stages[0] + .setStage(vk::ShaderStageFlagBits::eVertex) + .setPName("main") + .setModule(state.vertex_shader); + shader_stages[1] + .setStage(vk::ShaderStageFlagBits::eFragment) + .setPName("main") + .setModule(state.fragment_shader); + + auto pvisci = vk::PipelineVertexInputStateCreateInfo{}; + pvisci.setVertexAttributeDescriptions(state.vertex_attributes) + .setVertexBindingDescriptions(state.vertex_bindings); + + auto prsci = vk::PipelineRasterizationStateCreateInfo{}; + prsci.setPolygonMode(state.polygon_mode).setCullMode(state.cull_mode); + + auto pdssci = vk::PipelineDepthStencilStateCreateInfo{}; + auto const depth_test = + (state.flags & PipelineFlag::DepthTest) == PipelineFlag::DepthTest; + pdssci.setDepthTestEnable(depth_test ? vk::True : vk::False) + .setDepthCompareOp(state.depth_compare); + + auto const piasci = + vk::PipelineInputAssemblyStateCreateInfo{{}, state.topology}; + + auto pcbas = vk::PipelineColorBlendAttachmentState{}; + auto const alpha_blend = + (state.flags & PipelineFlag::AlphaBlend) == PipelineFlag::AlphaBlend; + using CCF = vk::ColorComponentFlagBits; + pcbas.setColorWriteMask(CCF::eR | CCF::eG | CCF::eB | CCF::eA) + .setBlendEnable(alpha_blend ? vk::True : vk::False) + .setSrcColorBlendFactor(vk::BlendFactor::eSrcAlpha) + .setDstColorBlendFactor(vk::BlendFactor::eOneMinusSrcAlpha) + .setColorBlendOp(vk::BlendOp::eAdd) + .setSrcAlphaBlendFactor(vk::BlendFactor::eOne) + .setDstAlphaBlendFactor(vk::BlendFactor::eZero) + .setAlphaBlendOp(vk::BlendOp::eAdd); + auto pcbsci = vk::PipelineColorBlendStateCreateInfo{}; + pcbsci.setAttachments(pcbas); + + // these dynamic states are guaranteed to be available. + auto const pdscis = std::array{ + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + vk::DynamicState::eLineWidth, + }; + auto pdsci = vk::PipelineDynamicStateCreateInfo{}; + pdsci.setDynamicStates(pdscis); + + // single viewport and scissor. + auto const pvsci = vk::PipelineViewportStateCreateInfo({}, 1, {}, 1); + + auto pmsci = vk::PipelineMultisampleStateCreateInfo{}; + pmsci.setRasterizationSamples(m_info.samples) + .setSampleShadingEnable(vk::False); + + auto prci = vk::PipelineRenderingCreateInfo{}; + // could be a depth-only pass. + if (m_info.color_format != vk::Format::eUndefined) { + prci.setColorAttachmentFormats(m_info.color_format); + } + prci.setDepthAttachmentFormat(m_info.depth_format); + + auto gpci = vk::GraphicsPipelineCreateInfo{}; + gpci.setStages(shader_stages) + .setPRasterizationState(&prsci) + .setPDepthStencilState(&pdssci) + .setPInputAssemblyState(&piasci) + .setPColorBlendState(&pcbsci) + .setPDynamicState(&pdsci) + .setPViewportState(&pvsci) + .setPMultisampleState(&pmsci) + .setLayout(layout) + .setPNext(&prci); + + auto ret = vk::Pipeline{}; + // use non-throwing API. + if (m_info.device.createGraphicsPipelines({}, 1, &gpci, {}, &ret) != + vk::Result::eSuccess) { + return {}; + } + + return vk::UniquePipeline{ret, m_info.device}; +} +``` + +Add new `App` members: + +```cpp +void create_pipeline_builder(); +void create_pipeline(); + +// ... +std::optional m_pipeline_builder{}; + +vk::UniquePipelineLayout m_pipeline_layout{}; +vk::UniquePipeline m_pipeline{}; +``` + +Implement and call `create_pipeline_builder()`: + +```cpp +void App::create_pipeline_builder() { + auto const pipeline_builder_ci = PipelineBuilder::CreateInfo{ + .device = *m_device, + .samples = vk::SampleCountFlagBits::e1, + .color_format = m_swapchain->get_format(), + }; + m_pipeline_builder.emplace(pipeline_builder_ci); +} +``` + +Complete the implementation of `create_pipeline()`: + +```cpp +// ... +m_pipeline_layout = m_device->createPipelineLayoutUnique({}); + +auto const pipeline_state = PipelineState{ + .vertex_shader = *vertex, + .fragment_shader = *fragment, +}; +m_pipeline = m_pipeline_builder->build(*m_pipeline_layout, pipeline_state); +if (!m_pipeline) { + throw std::runtime_error{"Failed to create Graphics Pipeline"}; +} +``` + +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. + +```cpp +command_buffer.beginRendering(rendering_info); +ImGui::ShowDemoWindow(); + +command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *m_pipeline); +auto viewport = vk::Viewport{}; +// flip the viewport across the X-axis (negative height): +// https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/ +viewport.setX(0.0f) + .setY(static_cast(m_render_target->extent.height)) + .setWidth(static_cast(m_render_target->extent.width)) + .setHeight(-viewport.y); +command_buffer.setViewport(0, viewport); +command_buffer.setScissor(0, render_area); +// current shader has hard-coded logic for 3 vertices. +command_buffer.draw(3, 1, 0, 0); +``` + +![White Triangle](./white_triangle.png) + +Updating our shaders to use interpolated RGB on each vertex: + +```glsl +// shader.vert + +layout (location = 0) out vec3 out_color; + +// ... +const vec3 colors[] = { + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0), +}; + +// ... +out_color = colors[gl_VertexIndex]; + +// shader.frag + +layout (location = 0) in vec3 in_color; + +// ... +out_color = vec4(in_color, 1.0); +``` + +And a black clear color: + +```cpp +// ... +.setClearValue(vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f}); +``` + +Gives us the renowned Vulkan sRGB triangle: + +![sRGB Triangle](./srgb_triangle.png) diff --git a/guide/src/pipeline/srgb_triangle.png b/guide/src/pipeline/srgb_triangle.png new file mode 100644 index 0000000000000000000000000000000000000000..a160a616a082361d1436e0ee8ed3870c8b5c035c GIT binary patch literal 84213 zcmeGES6EY7*ai&8aTGf;7F3#|gE|O^bScrHje~#{q()I{2rcxI*a;$_gMicwprAsC z0tqbyq)Lsn1QG}(CZQvQ5K{SenD_k;{-f{gJ9)WKvv<~BTQ@XyYmzntJ8(7qPwpDk(X`xJnidqOSmg!%`1 z``*9je+Q(mW7HqH-Sq$Oz7t4S&#>pKKL@yfMA|;WH{d~F#4X=Ye^1a=&wC*O#~s4_ zq4xp~?jL*(0v!ikvABFI(tUm`rqbWZO+_&7e7jENd;i0J4J~H=zu&*V`1bQq;VaN% z-GX1Q9o#Xs^_0o4md*;aySk%3s|Cwh3YNEihVOZ-dz|$0-<{ef7IIg0f4LqNX7GJA zb#WlB*PWN*T(MSK!~W!ki&$tM;qqU8xxg0n)Hn>4dAw94UiG#F=K25nC(*C-7h#+C zUg-n0FwaNglkY|q&??ePMa9Ww<(;~04qtlJIAea7@FYyugd$#&0j?j0m9ITozPPPm zZnf$cX@f+kl2_~1&)-=uFI{}Gv{QO(AMN^{2PsoA&IyczH@|(HD6fUhB+3#3ChT z49nge(YhV6ty;((1-f~fUlNTjiSGSFsg?Juw6BuzgHHx-*B9S5^&VH^o)5Xb6nLBD zGv9A7c0K)5UPWNC-(S@f9*!cE|B^u&?y8se5>!jG398MA zk;hRe!*gOG>&*7lm?-<0nut74bltq*L`n2MX*f{XN(!~MO=@G>UCdq%WU4K79kYW)oU3n9p$i8*UV(4 z9q*x?H5uy?5o#kRuwz;Re1iDjGp4}2~7$|i(ySbyViIOmoXZtl65@$xyxseiYA z^MnMBaOIkLgR_ZbKfgIY+ylC6`L|=zH5lg3AB^qI#`X;P>EXf>$*RxiIOk$W<#W9G z9|hsEx^-_@_L*WGw4luDB79M48Uqy# zJKaUEm=~BDeYhJuLUC#pl;a1gi(D(_BTsjgLcBDj!z#YBH1Dyb{LU13w(Bvoa@*v% zR1;x6g0fHbf$N&yS}kHExGcvd+*PO2>qFkWF8DzePdeZw->S<@JKhirin~$c^Q*bP?%%+pul?>;*)&$U91ANL#GZ+s(Y+|OkF8^}{aEXf6=wNpOf(l`d zmNRnC)gt=BV#?7ceq12Ad%;@PxSTd7@Z{Jvup_xExMj`RD9<|+S2ISJY9Z+T*wZ&% z^qXan@SHu{8qLN1gfQczi1KcMCs`8)Zf^F>tDmM)Yx95N@%kQ-J=eenfjxwp(Ezq; zcplnYizn5DAi~1$Pe*6y`TYFo3kSdNNXA4aF~F#td}9gZ76%rv3jGm6(6GqI5~u&TrBmldh139Yd?n;bP+3IOl4^ z`@(E!*ooji_G0+hwIeQNHaF_=_;7oe8(oX`4v(LwwCR;NOThywsr>6sO}3tSKD0*D z78@QA@IgKg8f*W%``z>{l2j{SDmqefpjxDE9WJ zAt(+t8Qmh1Qa@-|mebUkU*lZwkoZ9ci*qh>t#Wz^XHxT>Z@4(FK^Z3!OI;rHl$Bn* z=DHeH=jOQErAtqaLrI(K!trZ!$?4*#xvpiYUa^!nyGtB?1P^wE)KnSAuK%4Flr^p4o%s-Z{3zZAjZ6 zd7H4E@jP^b@m*)_29^uurNKS3LoGFux|~O}Xs)m_SI6Y|bL9cA^=zENj#q!l#wd3W2Cb@UX^`5WI8V&gu@f{(bUj`7XOek zps7*N!!S694!MyGb-;O@E!neAK8jopJv_r^yEtE-&GA!-n>!(SuZ)%W)79XiP( zZO+lXd$-yqiExkO9J<=g0ch$x>C)$R3CdwN%p!^C>?VwvFs*yHR1`yr4deGk8sDJ8hmM>1FJLq0|XEP>1t=NFP3BDRg)C8$m@19P4{u&pCbG9(G z=n=YwGgq9A#w|^c_qOBMJA$MoHQ2%Idjv{3l$ZIS9!^(%2l^QsK zRoISZ8PpKOAvlM6N+km>M}smWzZJ}Fc78o^`#;Ru5Jhe98I9(_Txu*OkWOZCYDAxU zm<(rNz0PqTAUc5v`e?s{-0{WLm!9md{b8^97w&3cb6K6HP1L7#5Z@geEEx?LB-zP z*($p(LofT8_0m>|D}Qrii6))-1n<7+*$kKwTHu>gbp_T_a9`T9p^xV!qd*f~$Fhz& zCa_0?S}7?XOtFL&!8Kr|up1?^TpPedh37%ORi7*U9$?;;FYG*yJ6x%+t+-|-O(8hR7=;c?iF@%u*BKd%-|OKG*UtwAzg|*ZTu{U<2SRrYtD90{8#3o zFNXwVvZf$sCDRj(9b!EfL}Bzb7jz;2qcn!f;Rh}*hT0pU+dQ-1a6=pe>7*swNtnA9 zX?K9YnMN5f^bB>=Pxg0HKAypMa<>tS^zJ`$hDk1^AqNBYnBM=8_k;5hg1?eFw1P0} zjo`Alx>bK^H1xaHxKtlLl>xos661aWtB(aXXOqB7bPsx^b9Lz-^GFeCB{}Nm3}=jq zP9*&{J)6=QUAq!JV!u*D&=BgGbIiH#=PCOpbN}pg9(KZCsln^#Ibydpd`yq86T$iOsm#%LTsfxdD^(gdJhGf?j-fN8kftjRvT7^7r&~|2OrEMvu=X1IPE%fLyv#p z%(;HUZFT%u>XsZ>u#c#k)1f!70(}imCNEiX!vC^YDM;o=#q^uv7;>_RNuhDaLgQ8GPzXO3Kn;exckp?* z0Ph3_Pn~Eeo}0@a$$FEKM)Faex}~oY2d)oySJ{;~0Pi=|W9qO0!xAsIgYr%md)Q;~ zmbwsEvM035`Q5TuGjamH6q`|Jy85HT1&l1_^dkQPZ^|)8%`9#xZAm%BAVB!h9x<=9 z!7*2fjq-oi8uf3sLaYCo9P-egG^$S4Fj0TV4<4^3eiU;(#tM`Et|r!2xtr|Aj6G)X zB`!YDNvnAxWYoffhF>@;Z@P#Uw+>NU;Vg}+(ceznjrNak8tsqnZUnt2W8*85fb^G* z8hHJi=djD1wWr%BCOk@GDFZfwb@E#H)5`PS<*0AYWcC$~IY7tYK>7O4ONP(B()@UJ z6OTwqeQgG#p#!#3CI>016x-##D5ELn#?--=f|zM&L8?dX**3XUXG#?_)+l-S(-2D) zPm;yZuH-=%ov9izufn67nX!_~Ot>eKe*5{?!csDwuNe_Djj5s@{co~;fP|={%*6=g zgKn>9XB9LmnoZ)b&J7r5P`dabu2f&dxZ;nL&b{K-xM==YcBj@m*9=N{EwFa`JOa1P zvv*HGw%X#_Gr5S1#R(`)Gq}0DiqOA>^|wCkKc66UswDwcJ(j6GawaP|didoPtG7$r z?ZkLAiM_&~7S`NK`9MkojJ|;A3%p8ABfq<4X1^N$o5WXa$Ijh& zH5afB{*nss^<(UWL|BPZcee${(*mqQv*pGu251-%!fkh<~varAf}1rp?LvNV|;yy#Q~jM>L*IC(@aq8ir1P$@r}A`sNI z2?L^iL~{^v{fIOK05#t0dNOul-Ij{T@lJ}`;i|yyjOm|yk8tmrlkvzuANuUM*}E*0 z;T~*=CeHJxOPM{i^L%7GY2tBRU`vCzCXpc$nv~|e@Cqe&uM@O3$dU6Wm4B11wSIkM zQ?l`x?(G~`Z2>;q@{8tK${P;nTt&EN_#SiKm_p$P}j+kxf7OdeTk{8Ky49gVLL+m_Loo%2- zZBAveiIHSrgI71Oj1~1aB&Z<2a>@^ZK=ZARqdc?V$*o#vx^Bvw{d~OsLP#$@M@sd6 z8fEr2P*DTkVv_jMBWJM#8l{%m_TG8nlA^_NlhyV#Zb(iSxMgLlEhh@xz}nVqdzH9; z*3Ei5ej>7trxyRn0|I>|*NC?B;AMw3$n|G8lpqkSr; zqIR7aNDN!M2dLd394#(guOE-HbTa$LT}*2@3SPbsc1}O3I`vN?##Ey_|qxsU?qNy%~rj#Q>{PHfSlzPVdx^*f-#pU|%a@5;9 zuEjC;sqVK67g{q_0*PPq3`@)ADpRRaHCpjb#+-NG|7hIXC&9 z=~6@X{l&-)pFmD)Fk`$PXh>XEx3i=v4le`Y^0|qTUX`Ys337O*&y`ee^jIA6sX z@fL?L>IMH;N$g~&R%&j9+(ZV&{~T-ZIT&H$y&@M@9Pk2#TAMevv}nnU5Vut?#5EZA zR?ivO{W4Y2a6TYryuJM+2_jc)hP)kWD-Q_@mxqKCFC78>>zft15&_nh{M5^6t)uhT zHuomB%K$kLDgosFQi`J~SqYGP$rgg@K zD7||1pTv_MAy=DuCNn#io7mvfbcuCFyYcukYf}oN44Z}rEXj|az$llTC|2e&W@BBY zXHZ3;F|MeqUVg!Hi({BC;V;sN$v!37g!?h;U!tXag3Q z9N7#-g7enioVKS(eK-0d^+86Rf7O*O3l(`BQ6w3j*PzijmdGP6iFNXWl(#qV;+C{B zmQI1yR7vtT?XuV}n6;JqkPPovAM>by6D!+uZ(Ea|xr&987MMUaY*=|xFkl$h@evYM zOiL;jFaGF}lhc+wuCuZ@P*p>*{1P<<56+H(92{dMOxw9Cc+;6Gtr&{PHq_TK=)kgN z)qx#bK_&~rJjHQwOMAP`#!y3YB9W7@EQ5`SBh0hikMSDD9jz={5)Pc6YF6R<5T>VW zrfABX+~q)sq-41nyeq74a~f!@4qXVNw4y~-5v^5V0nKYRg}-Lqsz3xJHP%%i|lXW6RAXgu990OG-+Fn71JZ7V2mv=#Wg{(c=q&$N<9h zGg1nKkAtKvMfayN4mF7DmQR93PnNk5-m95opof-w7RqebK6iy%9sQoZW$}-9-jZze z#>lz>c%!0j?~{{WI$sCvNA4X3S(A$p?kZVbLw@mo=l8fZ5x{uSL?;d>RK=6iI8pVn zcKl~d$A#S(Q}1gLv*{?54~bx&6UK{gYg1}aE!*H^v>uHR%pMtE>|(ssioT@FptW=; zm|dvJPncGnVr6e@0yRcMY5GL=6?+tNL&aSVxyDAyq3Z>+>rW{4W)c>117A#`LQCD?}Kl(^WCca9b< z?PijMb)-Zb*fK5_gIlc41byoZj$-#%rsJqV8w0cP2pqvz7(XZ*^^?-VXXnbx$wf$} zw34y>S*&wMu$&vEm4X~IpX#^KOio13_D7DRVPg3-XjzTE`^IyF)^@NYp?a-$Zb}au z7dN`5UzJctXZDU9Pz8o0sM3W*Ui%?O*Q3}Nv4Rvn)1evKpvDW&n?^3QjA8QGd+HbE zuCX4QAN9!my(strY?h?Jeokh>7U=ncvcZjKCQwf;{KI_8-^kDfb9MGgFn=2>0V!$t z^_E-MLWl3h;9i%FXN+*!=*#{H$qU2f8F1oF1Ce)G z1CXEF*exBYrx^s}v9rCM;k{NCqM5pJ9UVK#S|Hd&hbXC?D}>Puh4U3tg1NxAnTe=K zUJP9xEm`%R>Kk7}MYaZR1jGspm5(~Qac3e)LP@D>ZDk#utGSE?6z*jeVPRpp3@ldB zf%Axz`l+M#NWWNS+|mI?5(fE#ZT$44s+s6@O7BZ--J-ydodfss1Cq3z%_CAKx3JJ_ zaU(yICEQ(vuTbK?CThBOX#P-%S+=)q$)h7Xe;Ls1)x;;iS@1CQzIp>6`Dv**W<~#O zd&!%I;s^N@=TrOXfjC)xz~T957HASW^pE6I3Uck1YgkW*;nJv@9sTE*VFLHLk3nyO zPgXTkXDMavC9O>Z&95*NDyJk?b12-1us%zR7)HnXJXa2hAi3ob zlq@1BB=pY^PQn2>ZN7|n#2bAGymkP5bmQljj*a~r@z|PkkYjRkr|qsvu=u|X#!QUp z(CgXR*~>)+4ZkZ7$uy-z|7d1NZJ~caK)Q|hxez?v(QUmkvIn|xp2$wvI7eT%4oqke zP5fwBA6?u{JdO}MB8?@B>1kk-mOGVTTBsT4ID zxV^?P<2-jLI%$xU<#+k>XqSu9o_vL#*m%VWjzT(-+WEJiH+aVVv}WqpY0@Q+NcP*^ zoMb;ykO>S#~xSdLSD8r*0W^dZi|Y+kz=}h%os0|443R6w@iF-n#h4{hRq_PDsuF` z%7KXbIk%~#Ktxx!D9M0+#2X#Ac6Gr<2Ju0YXB~)uiaHyF+{m%H@grbfKRTHd-51`i{i1-kZr|7#*p7MWOyxwx!n+0BX z+|ZFM8z67yASF@2lcO65$kFlOzA92_COZSkbYx^f%aStOqpwuL6dYZnfYgG#yxH9q z^C&ht!rg=ukNCHGH|Q?1o)?#@5@+o8&g~nHN9Y=B$9=Er-hI=wT>tEnE$*Ewx@+m| z-XX^Tn&WAgqotBPof6cHoE3iKn8^=A>KlDDJk?))Ao$lerD5P`WDn+TD#X0#&Ld&Xbb zy9HZDse?fEx#1_4Jb1weDjGB#cB8t~Nx!A)Ix3K|jYAU&uXD+RtS+!2&eOEP4;Pwt zIiR*vORFbEpuSYKRourwuhs`{e6!xL@8+aMD7EfZfoE6G$>+)~ERYgY4Mc8$eQ5c3 zBsT`_14t=AJPK()jWs*k@RJ-Y>N)h}^mS?I29lYl^J={bta3-wb876Z=ohFSqG=`< z3ZqovoG&IC_7n#rS}$o}ixKWxLw+^6tdmm{h+CT5h(Ize1Nu+7`}3BM>7+F1O7H@; zZ$r@XF|s$%O(iAjyCIRg#h@iB!p~#S4smIbwZU-=X5vmWQZ}M>=Qx=8Dv zpyY>dljwb#lf0$Uj&c6%Ti856LB?il0XOZgQ}9ZZ~bdcD=4d zRkHXA|X87chKKet1iV2Px)VFfR2(d74sgwRviIlsIH34o4xz-ypx=>i}2h%bwU!{hRN);54)3 zFF-h_3Z&08E<5fEa$TG?peVJ>G?! zN}#upKnSG)SYJBk=$b zKx`K4f<$RicQX%=%bW9o%&;HsB;(($lhk;(o7p8}#)CzY?-{p^FK(7<4e2YfpSxwD zj zd98@;O^F}Gk32D=^E8tb2sveKc&v+d?$%Qm6ZxZL1h)+`}Zt!?`OqiZ4W zYH~MQb6=Hz%~yxqT!V8~S_uxl8H}xAM;-_g4QcF`VsB}Yt%x-7Q zit~+R*|E#OIz`L;EawLDzBnVM{PnqFZE63X%{p_mxm~3}?vm=%l{{(LbD`um(3rpv z=?MULqZW8fh@F{ZNk*Ks`NX=*DfM&gROh@qWmh_*H;bi-rB0oHY@NLQF>Y9nD;qt% z`2lMnBrF1RKd;$0~ig zEz{KXw3Jy(tv6Tk0jhdyl5z+hXC`eNL`bW-o4GfA%jn+H0%%nDT;S_k@}E*u2nZ zz6m?il4}rgW}omI z%9VdP-H&6A6D6K)ct_o=)c|X(xr;$7d^%m1Ypg4CXor@dU8}att61x_hft)-lZp(~ zZj-k&oj-BS>kj><%K>d z16UXRW1_U`>Z^Ryg>3$HVK?aEz@G~$icuP*eKxpgO!xyO?j^%a-tVV4A&jx8m(n=S@3SNl63!s&Ly`8B#_JquR+ zi%fW3z;D~x{nfEzYIUgw^fjrpG>wDP12ulptxV8$dVp{;HieHXIUHB~!UM+^%LexG zqX2fj>nj`e&3vl7OEa-ctAUV1#g%0@%!Sik5rIZk3j7KO@#yU=US^L_fi)%r%2_+y zv6XjGdRhQ8cyD`?TnT^w;ixuMh%OLEw8YRkd6E68c?ad)La!U)PIJOBz1dw)$hYIm zINdS!GR!js8PG(fwwAf#CvP5^l8fNhFBVzu6>YI zq=ezGpZRNP=KMJ6yZ3g`cNaA+S1;HST=9O~d=4nzcYB`%@WF zFdNydK;Lg4LmlY+XnOVueUwns{6?iY=m@KRjbH@2yKpXRw`T2PWiY^D;^RNJf$omi zFTes5txcxnJfkQpW0lP7vWdNzzoS{@m;LyN{(8^2+kV(IfjOKrzh(x^|NQF47ulhR zlb-*SY{ZjxDXtxD(i0w^#ZE)R@uy;zWmkdWzWP`zZJ3)(qU;lRq)O}4Ou7Kdp@U6g!q<=@M= z4^*r^CTa@k<-Y=f)LDa)JiJ}|rE0afoyB1nWkHR7a6S|J2fzZQ@g{H+eY@h5C)2NQ zZ)u;k*<_Qo)-NQv6zFl2Q?jTOamH@xxZNL=#MfBQ+AkWrRso4j;O9 z1Q1@D@k@jI_fL8Nzp*m;0Hw}Z9^V6Mq#7bCk1~Q=d=#~nTi+px*N%W5a!>5v4!Wx` z3adO?n1l+y57=g(lloS2g>^fh0emm}%RL|Ma{g$}l!af8V-Bz{Z5p9sJq z)MTYcK=19;3|w)+Ld5ne2CDB&0qiuz=b(0gFt-V2A@uxH+JV7lGjd$^&A#N*mxGM6 z>a7;$^%k)#;^x;{n_u^fryf))xak7FBf)YD9}@Aou7#3gRcyqql(2fK>5(v@>VCJCG=7NM{fOhpRd4Br2`PdbI% z-R-9fXPPIxbZd&*Emt4?TveP5)k}qrTD{ChEk6t2xfO^Cq+D+3K#Y!^`|%c)q?y(C z!_V4HSF9~JR5Fv2;-f~fLMAm(&7}RwZ_c^xiFK8gPIAZM#PxB!OS{DnYpY6_1#Q)F zmRI-0+BYNUfOhm}shk<+(jEBn^Hm?F&%foz-`19u;$XS6dlt>di!v9A&qogOtmBRl z`XGhzI{?8ODoZyqXN_1^|9e)jd`~r4Syky!pI!2czZ_}deY!5zmRE(g;hxskFR7Ys zJwN;!7SmTew*cn*B(R?om1}z!G`P86TNfuX^=T(um5;s--$@DIRW*M6An^67+GZoJ zZI8x)&Xepu@(?RODC#}PkSkMmU1eXfdhXfL*i`AX2QmP~emKx?VEskMaQCyq9gjFa z4W2bIBU#q0tPb#-uJeWq2a^L2?*gP3eN5`V8Y<~3jQxOAFi_Lx6QMX*iYsy+*U{ff)MtGo!Y-Yn()7j))U&_w89y7akOVpyyPt!!duh>X?WM*Il*I8-JY zM#WbBTz9d569{a0vWc;De=sKgvG7m@w-&3b;oFN##<%2@Rh~3lSWOE5O=1_Z#~Vhw z8or}=tcf|aca?F^puJ-w)&WQ%%oJ!yL32wy(AQI8{G|O0?DWr2>mU_Su~Oqq&P1kL zlM}4gr5c-N{A@rkz$;=`ie#pTN&6M_@T84&?|dgG3$srOR9+_@+)?F}{jA<|KLG=A z)wvh+9<8GCni{+cz927G7{6P(eWsg8T2USeSlE*+DV;cSQL8$$at4LEu%kC(A8B57 zv6)_!DLPrR=LrXTP@LKH1$VPL?wwTRh4#|XUax&vEAr8=?a*5+I2zVhnW}E(W?>wo z2b1p$?b8+4I%m5rT%xtS8*iJ}`4fz`LEjG9tlTle3eD)K4U zc~Z>YF8l0j+}k>hXWDn`IPgS(T4ht%qCk46RR`8ocnY))$){o zXsXT=A^b^U(lqr7ZFS+)&;pj=fNverm&V6q773Do|u_=Jw<3@`N^n%L|9+nQ&l zQOgmVYUaEtrK96Uwc3E1X)mf;x#==Av%*6xk?(^C07Y}N7$EUrsa2lf5}YR{6Z0NH z3+-!h$o3mh7nG=^WNTXWwHgW}5%fEfOaNUzoBiz^*Q?c5UD&gTyrfCJlC82^j&C(zoJkT&07RXv9rmg9K?j-h*+9Jc=r^E}9ORQXJi5PcX-}Rcax`b`t1oY< zJ7CgZW~p26vvH(XoIg4+t3z`fI7ek~{5abS>KGK35mdpw4`--(D9ZI5cqRp=vJ_?U zQ&)nUe-I{;hLv|^oCrF+XxHoa?N2JgWN+fYq1MQRl0BKlTCXVsE?1y}uqZ><=U)|i ze!MunDJDfmIbd;R@<*l%x_b96mV+XxfohHv|DN}!!kyVQ=L(h6GUO@r5+y*F)4&GgHli54I6Iy&07@hDs{05f1`HrW7DTQPh>LN@li$ zF?YJ%!oS7ia?oLrL7c#Odo|7(C^4gJ3Vgas9G0L14-F6Res16altR`!q*}KxUCOgt zIdO$^1xF*~00{QIOTs4>jz|AX;@%W5t`igzSa+!m5idqKAxK+;wlhc2X4X%Rs6Cndwv%kg(*~}zMbE12kZ=S ze5txe2yjwte1MTMa7weUrPugzJgd|BYB;DkIJ>79LDe>y6$09lB+C7DJc5YZ1T$SUuh$5pK50eJYK!(seoz)ARf zCtxgw4K@fh;pOU)iWT7F6czc`;lJ49zpshC`pF0j%_T~>Oa?>k4_uTZEY4qrZEexr z%@bED7-DiI2_MJ(7xvrLJ!{t<(HIrMrE3>jOR1R{8SSdjV|5iko%!U!?ZrPMXxHUz zAvR)vg2wS=k5PyYulHk*t8a%Y4xy^To(x0;ek#@*xK_PnpOS=s&KjiYF$JBYcDQeISH1)kJJT%uSwQ|d6K3J> z1*H;+BUoR7-VCuU)3uOD=HgsV4*=VJW0O1UqubWAd|s%$cA5TwQk(O>_Y*(%H4aIS z{6N;#zD+K!>Mj{->p8WqA>=1dK1)tUNdR+>TGlB8G~te(6vXP>tqdqW`Ng;9iF2Yc zav+|1aDsI?U_`5ZAP9dU56CGM>$@21pdp?bD#cY7bTcp=Tc@afaxk5kyr7F0Jw#ij zd+e}jIXguY)9}8Rbtu4*@#(jsSq}8#2M`060}sUE7m^3l#UXLXp6=UiwyU^re!ejO z9`;;zEX;G%fkiDJU!8lwg@MOeVxp3@e4$r=GC;ZD^_rFY+d&WAqz79*{|43-jLxTg z2_^o556XJyfG_t`LTFq8l?-afE%jSJsAj?6j`&Z zz^4F@=&qhrOXf#(Xe8S|CxW|kEoSv$@$3S>PmD`YEX=PeHt^sU2@R26#AZ z5y}-X8{=29shJ3EhzqTyvEH2VR>={{X=|OcWvelL!C=(_yuSF~(Mt5t4W!^CJdTF! z?h~P;e#qFSaMel4B|`In`@&$kC{T{T)(Sk88WDJZVWT^$3Gh?SL&j1f$f23j`_e2mSzHtCQ zvgqX}bROUhxB@!R_AZH--ZXV9 zQB6(emTqcLs+)mQEUNLY!65tU@{Ex#UCq@VT#A$+o^LkMES!?Xu zIU6{Xwy42kt#P~$adhe)L2<*>qd8UlFLH%%=MO4dx2!{A^?@C83YVhm4f`edz?hXh zpzz__Qv~>h2<~Cnt|wi9C!$o3b2dUhrouRVJRzQ7&>yQ`g(0U5xXO<}55WiYm%KSl z=o{Q>w5`rggn=;pMh>jbKSx3YjsWy}PyOg>_yBBMV{g$03ve=XO(Iq^M{>V1-j3`f zU|@oYilsnkqU0Lv1NxU?H37mOGLYfDWdA;2Z^RXn*Uc4&*kZ;V$Shp=zqaC#;vm2F zo_V{;J~;RQAc+rBNnGmTSfMjT4@a&1#SdMxadFg4m5o=A#|YruIojA@mYQn8Qu{B^ zm6}qAid3Xn1 z)TvIwoUh*V>?SBDzR6cJiGv_p-SXqt>W)|OsW711Z*6e~K&AVNc*b=+g_<=_pV9dP zr#Aa2FgiR7T2T`cA4D4@9SlHgNfppr>aVZ_eQN`9n;~FJb)wgxWu==gOkR7IrfUZS z=wg*YOe%UiM2UGH6vixCY8H+vbtxzV^dr@nVhF5bj=Hx+nxBOs!LTA4L zSC`5{e-ppjBY<(Rl~?Gq6c(cCf+-N_-#?{X{;#|)x@>7rZ%V=$nwo+^YVfM@U2m#? z#7}(9V9MW^el*dlyO&LRTZT6171)9e+)`O9dTGEZ#_fxlBLHaPki|+(Fu+h~M(i@s z+7EF0`Kv&`mxgvTK1nw-p!}ra_=%h<+lE5?@V1<#-}1_Q%f~9CG#UaQQ7ctapY-9ZMM_|TTnJ0x>A-vdA`}GvKiVffUhfUB5TzG2dHYQKsFXCb{W%Gsel$H zOcxM~auP*!f7_OKhokD>LMq9dZh5qqLTXKA(E`PP4cfnNDm0LCmA>)e$oR#hzknL| zNCU57R(qSHZF0hN^Mt4x)lswUi?|wP_VcRst~B+Qcw(^(Lm94H7*T_{)(8Ut@tCJm-w!ktUHNhVm=Y;5KpZ`O*pt^7N^ zj}A`8x)7L95*kNlE#0TW)V?lxU8SxV0RhH{JwnuJ$VcX)f%8Cj;8F{DH?H0%lnEG( zr29~HKNS!gw(And?JK`}270zS7e6Ax*Aygq_5WA^_(+#SDAljm0t$-*)eXNN8sKz# zzo)JmwTcJ$&NLnre2(J9m`Um6l5)zHpt!Ln*wt_uwp3^O(g5RrMauqn?7`Z#At3=j zt^9!~-fO|`UZ6Xq95bXUs2MghSn!5N!MtEKYNl1wq|z?*b(foiox)<02F)4v2#6I5 zN{g>WzS-18K@uH&LaHxP^j;!V*QUd%hkn)!LsCRqntLqDhfbw^IkrO-psslR!grn8uB7PL?0Zfa_ z0nSYp@%9b*anf)@HN+$WBpW-}FSHeRm<#a0skEvwgYQL3Up)aL2kRHiAbD7o4C7}~ zJ6Q?1LJzsY!SL1}ITMoZ36b>={IkZWBROrh;4++XkAe1H>k_z=OBvcFQ4kAT2p!eR zTpJKOHRywHDfoABx5AVKWa>eQew@qmrn7r`iTy{~f4EMvfD`wi#*0#8R{9bB(%!P7 z&k`^{g{p01s~=E+7x~smvtTC0=uFC^Zz-?8&CT3-s;le+#g*H(o1VMXwb;Yu=r;TX`-~ZSq5mQnVPb)y>$U=7NJ~xz#sb{rX^+AgRm93omyOE+#xO6t zegSfC`6H<5Hn|?dg1MCE(*ifZ-X);a3VhXY9poWQjW6vM6%qdvA5FGHn-1EgTfBiM z4tn&rxeNF@LWvy?H4;+-yef$UyuQQS)oy&ka5oJx;-%w6U4_e~UY1Yg>HX1GwPpi= zOiOC8O7qdjkbWWVLC9Mc70uV>298&E2rM<8eVqB5k&k!C#%q6p!N6A;ey|(VT7x+Q zW3JHU29lLIQt=6gFoXx1IpT57BVKcxVU8;HeF>y)VH!nLG7vAzfjt35ZM;}+F2#ZC z1<8XJEBP_tEkK3Qwl$@ukFaw5Ohl8Hqr1Xv24fJ7zh23tei%Oxg@GLSh$it!Nu}d0 z7W`7wl#%bfwNfCtlI33}s#}RET0!XNIgRtG#>kWvw`P}A7nmShRc%R6RSljId%=#l zs_BvnL{~9V&7D|}?wC#;PHKNcdyzyb_I@Ba?d`h(r)Md2}EBuz@1SmHE*7`H<^mS*?LlV}blmCSKSZ^m3_baoQWgP4S{ zMZR|i4C4GT1GkrjiEI7#S0&8=y?>GB0}WZg8_3!thX`^&a5gI&%*pv{lfMBBr4=Az9xGm;t3i!fg^bapQr=kE$Vy+QI9 zZdCAC4S3jy+jvA-Jqi8VBBB3}IGLxt30HRH@BO8~!uO!XJB#ZFEnlcwzFygyyXDE( zyXWH@Yj_TlTqYr0%CuhuAh5_Osxd;sSY^nLZ6HoX5iON80?EZ5H z@NtScX^-E~q91*EKydFfHWXx{v^|D?&fYQ75bJPmb;ubg4*$5_6LX(wNU5NB|4S*z zm~yQkqa5>~a~cTnuK{C3ND==LHYT38(v6 zW0$_#h@0mW414uR>PcP|lJ9zpBf1FFUt^54AWD7nIJs$Ta&_~zh=pH7ACbGHOWUy= z64LuvI^@&HD?c4Aavmkd1&uz-+fE!lK+Nze0sl$uzE7Q)44YH`%e)g?2GYDUt~|Tjs@B%4oLk;!zJC++XsCmR@%JpI}#|&Z_MuUP)d7AZ6hT@y=K6EMEr>eYc=K z?`p+K;KaJ;f5ZNhzx97)`wAQ<37d%h+UkC2($V-Hc->8DpYU>EzO76Zel4|4o0Rtz z+I8!+sn>(R*Rt+t@7nhK>x#l4Qy%T=@m74<%8f4f)RW(!$!AGA{8xtip4dW*Y%3mK z;cT7iLQBF3%Nr7%``4wL_b1`!`qr)g?c?v0q#R^D^5LjbQN(uPmh)J|PX#E=kr=W- zy~u6ya5=5jHX8H1CWja5R*`m}m;MEF6S?(n$+Ir%n+mI%_Aic$9ds9p<&xFhbHp{1 z(j((%W8)>4;cPHxZc~{1wd61E|8Gwd)+>r3RrWIG;9t!O%|NUt6JE8LB#M_i?{^Io zM~B7`h+9?GoOVN(Fz1gdBXdmGlheYnmbg(JZg#QEGc@#efMfTHdQZZ!9wTQ1(#$T* z$i_RO2gI$!%qIZ4#(#p(+ma(G-S_O7bYOS2)y~beqB5Qa8pUnRsM}3dnz}6qYyK&j zAPZjL+c6{Rzv;)MJ)-O*yO`|t?cqLOJ|tVSls+oB9K81L3ZzeU`|}a=9FwXaHR!P8 zV=>oBQ0C-_GRx6=UHH^swO*eSGv3kkoyb&(j zqso|cM=pCvd>b-X9;i*Bu7by1E5c9%v)E+AErwN+gB@!c;}8D>>+(qX@y!9<&q+qt zk|Mo>2!D8(gF$8&4<;Wl@BeUE`9=I8;jW6dta?JN7HNw3i8yNwG4ita(PbajASq1! z=zxdN8@j_5u?|a#Lw-aDQzyog@#k@2n2URotGUg0k;OmP*u*XW!PTSC(ds`8HH!Fa z(CQqm;Qg`7r=QFmU-yV_MBvDJNJ|T7+_u(9r=i%^>E)7Dyn9|~RKJ zgBL#6m91BF-nhWxhXJP_@8%g`x;@$^U}EHK`>n+9k9)}NETzw;ZxemxK`9auc5(%| z0q@ux|AhO&BIL$S>1UMulZJ)&E4)6h-9(9v03@)(h|AM3>=` z*&Q1XYwF18!)=gtz5hTYf57y_IjNYJ3qm3;8*i0IUVOXnA%ExHO2mYL$)R~Y<2|h6 z02$u8$ZY$Es%W!kL$PImD#(zu{x6G;2Dl>b*zzpj%x*hTD%GxVbb^!)2|yI*M@^|u+$ zk*~<3sjHf&_MTnSx~&oAVY-slW-8UwHoC8S@<=MwtQk)B=bFr2*NQIYIwbimTfehK z#Me8U6cZB}-nW_!v*=Hb z)a#fm+-%M5hTloz66$O3JL;GZttBS`ySZ^E2xgA-Y)^MgL>K<+s#d*o4-U5u1jGY+uw|>v#A!_j7~tV+N+(py7`u2Y|(=^@{VMdmRPp z_T+1;k&D8hyf~A<+xFr!mCw!2kaj0IT#I}A|L}0-aY<)iyT&P-Oqr%~7p9!DGD9a# zEx;*%*{+$R<`TF}nwq&~iY!*9G@3Gv$WD zpMUuIaKHDS`<&-I&pFqzg94&M_L{FfT}}zYQ_LD>hn#6-R*^@(f2Eu?Xd_wyG0)F) zM2+Dqkus&M!|O**&@Vr>aiMs)ntp7R#lAa=wdZXV8J!pWq@9OnvoXtO}dFSms`^;;L)2oMj|Axkwv) za#4^<`~_Afd;Pg;>kN49_w`?`7c?es+c<0Voa0>M$muA_H8Y(b0er==${7>b7Eu(f z)>|K8Z{TO7RMItldHnb?9Xc%xe&ZdP!iWH$Gx9?gK%!LBpHf^A&Bs>n1r-*a_mJH)BEck1u;{h=O_o5m*^~iI$y7et)>POt#{QWNj+h%sTPE%@W`ukUYt?cnr z(Ckd_yy^GZdw$OMCsvzt=XPavIP+({e_kuI51fz1!Drts=2}xzLpzkVRMoWIVcUXP zmi&c6J)0yRyz3u|Hpu5s0qbnhm>5J#*fLis-1pW*;QzIQKhd^4WO$6aC43aP@x`vY z*OK>atOvls%Jf9t2V)%19V4oU6b8N03KG3i80R%v@AW=m!QfJRpzJ^bQ2c?KBY;|) zNdINkTCy)TQW|n$^v@yxrKF*hyJGYuip;Zp7X)U0joFV^edigfynr>m4Gdv|BF^6U zcEH2tBYn)84x$QG;rRWwF%ZxcqSF*qVrIQ`C2M7WZ&97Ey5A6^UvcUP^l)1Rd0RU*;v`+M9MIx)-028Z%b1i=tt?_H!tfuw@o|y zmRf6y%5tkd~%X^ zx2g19z#D%@V>~WtSh<_1erTqDxxG#XA8JRe9^4FT#qW6{V~%BAv!nlJU@_oX_0rV- z=?OQB`grk=eeZM_*bi!&ba9tkF)67g_g2BDM(nYOqm*2bBw^*gMIy_gf7~0y0))nf z7zD>%X1;O|ZhIX~ZiTSj_&?tbK^J$TwxEcH{8biqcYut)_U}&{Uq17jZ069$m&2%@ zQu-=4Z!v90yuN zRwXaI!$b2TE(>)L;7-|LRcJJ6U|fGno0nG5TPD3<}c zDDWT0)oK%q*XFx2H>!j_JX~WV+)pmB`oLRwO2_a2oMG|57QiE1<+HBk%$ww+wbql) z=~L1pAfLmfUwCTw4IEx$N*JOWopqkDBE_;W`Syo5V_iI%^w?JbN3`vEgl#x-ds9X3 z){*Jj5|`^9bGvK+Xsu=3r|3JyB&{u_vdJg$yc_6D^>Hz#SojsOw#`*m5s3t^*Aq`F z{`+1k6$}Los>SFhJOTYj##F^^E0M`wS$+_x zGs1mFWAQa|ts@cb$XdGI5gXczo5J*9TwR1ktimKcR&ttfzt3z9J{f?mn#FxA?oE=K z3c|G&Y0aB_yT0$y{~y8J3m$1Cn0f_tAS^RGN}#XnJhk>2lR@q2O+$|SFM%All)D{O z_J^oPHHxzz&ohdXz5|Qy13o2^A%3ap5$-Z~t77)>(DP2LTl2~%=_rt-2kwrV2jU>q zdlp5gh~H+W^NJC>MHmxM1646U`Pn)hlO(Cbq79s2J` zS_CZd+I_alqZHELbCq9x9rrmpC!}azq1By_nD*90#b~lZ40dIeFN{SjW+N>#$QkE9 zVa#oUG;w3j&JxroN^&8^K7Q&?KZi!b8J>m`=e2#%8z=JD4_k30yxBwce+w@Vxb-^_ z?)SZZb@{}BU@&OJoQ7a10Jh`q-u76BdL{o{Z{@d{bCTfKwk-m3xmM0t9x7ig)mufM zRmBQfK=~&DX5!G5YAud}>y(*adcXLEx`{@gSMe|8VH0dGQ}g}Ly)apap~HX}raDI- zy`27?U+I6{5W&h3A*#~iyqp$qc5qsSA{0np#nFhm``fOINMA2`E!)LB3 zbRGly^)pZogtPkc(F_T(F4kfCmVUxYQgTF$8R~G0iQDw)Hb{cB-c0WTv@_$vJE}W6 z^hbu8JmbC;W|mXhU=-wFz{l0&TzGnlH>A0&zhurFx&04H_2cAaMVr*? zcjsHf&c&tCz0xOv)nH~%@D<^bu1mcZeP#JcNQol1W{S6t0s9Hi?^#GgFwn;w3Jxny zdFx@e1CD)?@#6iw9S!>fOS4nWNOMbS7wu){?0jWyM1t{ac)Es3uYx@YEAt{LSzfi#qNu^zezT?^zjj{)LA2A2-RM{yY{1+>Wca8s*fqVLpDxC&#FjrT z*zeaNqr z{)cTzkvX=js{XM7ov}`f=kLwkJ<_5`lNc_ke7vbJS8?y~2EEPyPV@=;!cxq3P?vq5 zSbbpli%XNq_v;!?AqpKaQ86;&tc}pc__g_H9Q5$<)pvldJ<{+$H0);rzk+S-6y!!3KvH$&Cch=L{q7`!|=##2cU-#q|e6v!V)-s*+ zZ)GVKp|?1^Z8f6QWdjH7>enzE$&qj%Qo4ed?x~enjyl*V_H7NckiXtjcBv#U)@dZK zP3!5OVXM~!bd;U8c*II{!|ZlH&+kpul@g5b0I$a`g0!^VfY+1Gy3|;5%q`wK1p`FI zqrZl|tb&ZcgWW6)fzl`Ne(%;Z6a%3g$1Ku|Srb{rY#he&_)i;RPj-_yqec~UfMq={ zU&5KWxqF5Te}urt)vp?BP+0n;n3T6%8qoEJ_;$T^6E%iTeFyeqbK*XGmihS&re&Yh zV+>!tXf9v%H5Vx*9jlm}1R~!a4 zS(oD%(O_4Ek4q*4FrimS)@O1PM!b-G5%a=T1a#@Avw$}H_UDr#- zu7`jb^kW2DNle6>ui7M(g{lzOATc!a5G=XN$BleOa7{0$j+-RiM(KM&bYzaw$NYhJ zS^Gy2<!&O$g8iiW5&A<=PeUv@YEw?Q`f213o`u3F0J7 z>(YFyhb=kTagEijAc7TiS~;TF5s`;|Kav&wejSJ`j7s5&0>`8Iw}R{uK8=;B6Q>wj zqU^_>nV1zCclf8F$7@$~D=^?KKn#aDxWHIzrfgJ^v*(0-@cQC{6gW8I)^8FJOq|#A z&~W;rsmepsMTJ7IGXDs=;wQl#TVoie*R4t9Lic^m@J^@lYcN>?9VNWQ1w%Oz~TMu7@`J!nY?w)a!g-!J9V3->uw zNGp6&AtTn=1m$^Bk7CAYGU7lrJI&|qc@XleXu(jQZWQN1u(%C7;bWwhD!ygVjF`16 z=;kM`u3!*?!Y)g|Z6Wfa-=Q&T{==!0eortCyA}R(8Ajgj_C^LR1Si`0{4pNy@e(u( zv!0KU^*-^MHw@X0aIUFoO zF%P3L_FKFov8pXs>SaKNv3A-DQsvwL?lXO^ExS?Z(O@v|6||Cc#3K?@|C3aH?bp~g zmg@wiAFx@c1p%mQi*_TpPcL@kYC3VB%DilRBc^e6=@Um1;!u4+M@{E|zxB?RWXoTt zd7atEh~OSX+>SSn)w*SnZI*qTXWm1Iff-IqCLWNBNm{1;dbTMqTW=B7%u_ zhn=hF2Bg0cQ|97Z{zG*@nF9vk{Ln{K+M5z=!f7tkDmzl1#Q&RLGWl*&u4?Od66#KK z^=+os&VSyU1=5siMs_+QB$i{=oHe_RVy@!5J)yPqa3))1Q~E)a_Qc9LtFv&3Hf%Pw z&yzVif9ihxR1_|>>sb7UZ2L$Nx0{CZH74c1k*inzrYkFVnpbgt0BM?6LvP9lBJUa} zS9Hxk)?dfNv=~2y(iB4CghSG>7gP5h`i%|&{?US6Q5iuzN2^b#fp$r!bXQ1Rk-{dq zt=#ql$fNDLmUhrZSX<~*EA_6Cz1sK$*W zhl{&JJ*XlETP>C7?T9(%U*vecbU11(7S4MlV*&o?0Iq(t8J>?^<5wPCKct=C)9DM) z9SbK5jcHO`PGLFAdCJ^*w;GN%J)%(dE^MEpEI+dl6aj5sJ%atm%G?jBGYc-5b}1l> zTbXap1;{l1)0ejG+zV_2z2c(I+KQcZi3}Dw)3#~(C~(C!+pn=~Z-Bf4o_$fM8mXME z7u^St{k9P!nwwEwJg7L2Z;-3<*TTa!Q&l3tLA_;&m3x8V(?$9oZ8*3HL?6E*cs7Uo;Nv9bk{!y#6({v<|AWo7(7leCA5zJrzVB@d~fBK`|c zw~TGw&=RkZhs6Q6o#{=!%&xUBb9QxyaO|d$Z8bz6aAV|%;@a*x&T6&i)%EMPs}Xl?mON9+!I-);ReSG+yT_KqUe zuAgxBsmj#tLtaq7Lkviq=W+3^fm>}c>Vo{%qSiZw;`&P_oNBV#(~_9y`CJH6LVe4g z7M>EJVl&Wre4iU%m7M%eW_28+E|wO$6r^#D+>mM9+Sj*UD2bnj@VBE*rFukqR5~@u zwtfNMk4&jqvnS7UWT;Jy+%mq_^~-wduLr!S>6Oc8v?aFN=_M}X7o1+S|G5%cVYUv< zql1tmNTQ4uFTkQGK&{3IQK7Mm=U-j_nu_K-y z>jl)a7AcCnHT=#A)2ATgurn6BRGcF(rG~6T5kID{HP70|mEpgo?UqH`qFJ5?ZGaLQ`UFkp9e&<8*QKXdEJytdmf z#*U%SV`p7N;#(IS2#~NFwu?b)2H0H3G$nCctM|D(DXcs*w9!+qe@r7FBIh(U!Yfnh zcAKmkYwf;$k=&e&asQK^&%7wh?tTTXgfG%+bvB%CE?j-6xjns4McRuGYZ=2G90 zrXzo_QRwU;vg|qgK<~Za;pjXs!I|PDJ@6f{8%OOe59Y?-Hcq(Mr+AMnM^4S#q;ud# zt>yMwtyn;sF39awkk8`lq6s{9i7~%aBLldu`>EjvpUadFegn7pqw~5mjEP%W>I4-y z#k5j#@z0>8KC|PKJuE{%bKvo70hviB42T}HLSts$FzPmK)yzph0u5c@Cauywr0_Hg z&e#fq*&$0kk}p^UY3XP@DtA?-Y_GJ{wW9PXg*lQN*1C`s0Zn-BYLil98qSTY!TRF` z(#Mh`LwiIuV>9Qm+Uqxy^!vuekRL66kn!;JOUXAoAw2ZwJK46oO$AFw?6Wlg#d}a4 z2>WAdDbym)!U8Z@8*#_q2iS>QB4{#$ecBc>?7_xS3jd^iZ=J;n~ReML}Ij_8_La=BeZo@HeO|_8@Tz$cq|6tD{*DPH$=55J2JX zn-q}zzO?$XJR?#}QxQtLNq^s7Et>GizMkj!zsHP1`x&;;v_&;(ahmavcd6gGm*Kfk zXY9t&n>?Q;tYR0nUT3~Hl|Eb9%S)=kj|)OOX-52s6APDQH`J(=_}r9xuI+cWbz)S5 zr&moI$jsv{z?af%xUV>E4vmcy?ZX6@Gd&8xk zKRTDi15GK`ivFCN`>^BGUf^&SX()<&Max4By|`;cbs!}P=X*ZlwsvC0oZQbAZod@ZEJl&r*}}h z&jd=g2H#)aYol0!JsfJ#BI#6D^&EP4Pdj}1(_Be~n&?({1%mLFQ5;)GM`7@j)@teV?>WpsFdh*ZgC( zicK5f^_p-zbY|(Ve`VdBcVlNQJN=1OE0ioi3LVofN>?tGfMRey96A9N7ZaiV zQ-YpI19q(QO4%zj61?xh{5$vRVG_1vNeA(8eN}mnzwaUKw9Pj&v9cTYiH?f0QDJ(_Z@HQJ>VBMejwE6< z(|NIRVLpd=OWpvpmK zI|H%S1&HEr={IphrEDns^9!3DAH#3PZq8i(D0yFVppLjp#5&h{rifSY#?1O>0OSz1txdmv~v?^P*Vt=^5b#DA7qK=DSN#0Nh=KtNj23zM*;XO;ZrM9`h0wFqg z@H-X~Yq3jl$-`c-_OCfHqt*LcB!-PO|7`!w0=YEkZ}zy6@)qo=gp*L``d?o?@o)CR zag@sB8xlCnib7R6adal9i$-<+8MVb$d-h#g&}g*-$+!vb>=nPf@YH_VxZ0Iz#0+?Q zIukjJTqp@}x*SB`3+I2>73y=Xc3jS^$$sxz@a zbo&8K1!bcGa?ZdnfU1j$fO9p^FEcGY-3WkQ*6^jq6CaAemZ19qRV-Tyu9`WV#ZDc< zcI8y$nj;YShs(H;X0pm7n zzf)^iCl|Ayn6`tmDopdX-mH9y$o|hF7{38HRx2(SzH#^ATf+l9@T;%#i=9|YZ?C1{ zKASYAcdi5WnO%tBg0}cKJXkRp=z;++nuu;S*ZIJm!#wdl>8P{L%^n7iDZHE%zaHAC zq@qc?>+n;7Bc;U`@qW6DoB9MDDDyApVeV^kSqJ%rV`cJ$M7}F zsti($73g=?13`7odL{8BW%ovn^CTr9jELadD01A<0dp}^d}W~Z^X1MoXiw@ex&XbK z=5Z_{u+rp)W20)6%A>4np4+ z%%mgaazUf5OS4OZY9;W*&5;PvZ-`j{v(A(*_tZkKH5G%lx07~dxU8J2ouF(K`1OrZ z8!qCLlt<#bh%GHCs|usmCZq{w@&D!I$AYz6bDv??G+ zXYq>gON!7hw7qz)ezl52rd`o0N>iL(-R)9Gxoymug6oFgJ+8trWvRj*L}81h-Ja7y zS-oCo>mRQ*L8#s0dq}?8+!A25d|+LfoPYH1+Uk09(|p}Lbw){=g?`tR|Y<^(Cu`QpJ21p_V4aJ%3C8H0sSPJ2hoLz@o`6 zHA0gbOc1!=kin5{uadm}@?UTWMSyy3gq=?_IS|W$HyZt?n_i!tjn5eAm=d;LZyu?w zcV9><=FU6z`ASG@UM6@O?X0y+^9XRCl)7b|L8$3$-XOI!aT$|kv%UR{A!5EB%(!1x z&MROZI^=SW}s3}$oLz` zl#vpOQ|4re0oy>oy4rOFYdBEX(B0m0WpQS;L4T2>MB~Z(feG0 zkwSnkP4UY?exTtUr1mb;)0<7yXEvSS`Lhc$0q=p}R`}7%Qh)3d+MWUsg6o?EH4fpAUv^P4bQ*Pd9IzVyHMB+Q~ z#)XnqZ6BI%biRb*HSipJoZ5EG+eF_>wRf~k<7F!J@XE`|NjlU+iP3Lr#nI}9zJdLy z81w}RdI_3rz%@IOGf4$P^^ZeWUPgk{wxoz7RRUHXa*R|rI(N4WbD$f$xf5g-snWdG z=YRS~AnZbd%46LCRnxec;T2d}n_`?1sSLLo6$BuRfQhiMsnWVb(isCL&jH)?l*d^lColjYg&#CP`1)cP4|M zXW)U58>|Mdj2TzSY`7qo}ZS=#LQCPNHI z4=ZFd8xm`0espblV#!(f>~>nH0lQLY;M**B5p3dfl0A&?-Fbm_0FL$i0!ziCa-9=; zJdMb#$=?Yyc06V&*A zs8@DG*Nb4d5ljBs8S$Vz-m-h|rS?Pl|%oqzZx z_0=d|Kd;QoWL6-p`6vS%jstHw4q7_KURkXPFn&nXe4hUAb8j6GcVr^#!=bhTzSB(gGtHSBn4 zYW}lFB(cci2zq(Y`x{QlTWw$JJ^mIF^JnL3XHJaBMZ>fwL(tlEC}tcfk6lq9!4GZH ztu4L!JdAB&VER&P`u3{hM9iH!FKC&IN1dNS{8c_5b)1f*F z4ET9%=$41~jkDZ2?VrUBtFw)(QDW!Sv=$-xL(SGtX~I|I3er0|#*5(Hx^(}iwI(jv z8h1aMDXAx}tg7s#HpR&P3QW-Ax}*$$9{M=Ts=9&Q8Ymg`o?8-Q_Smn7F~Meqb80<| znFgRHXumUi8s#9WQnY``s-b6=}S zr$|YhZCwyvs&7?T>+NxMkoQa1XyhPIOxW9&ex|yj+R=H=XUY;mYcyH z*<*Uq6#&fkjkTWMY@J}5fP9IC#9ig`UIA28cGwP6)b(d0_sm?E1Fz*RUt3z()!0br z;B0fDcsUaWlt=xbFUBIS$b9kc;0}uTYf5M;maawZ=fm@q)3`wP`8ZUNP1g!6|A=3^ z*aL=0AU7^!LS2S{@ONtTj58r1VX*7W(AhGaR=7iwBh)o-*w@O7wuE&j^1GTB7<*61 zyzv61W#PCbv4Ja=F|szUpt;9}X3#oT;_4t{Yhr~UZ#8gP{<(p900Af2?sq#Pol-Z~|Xv6HM!9(WcqaxpMd*Ga?(fP78;m-pm((4hbAvwk}zLji$`LFq^C4`ZFqId|1=yz-|M|Aw- z+fLe%=YNF#VCdb@o(Fbu2%GuWhOiZ%_51}PZCa_2n*N*1`{y)PKf9g!F+nfN#iFQc z?r-H6)AgsLd*m-j#xKw`m+c)alsQ zsI`~-1QK9!8k2g)rNvD-mH&BiFbx#~7+ZxpA^CZIm6rIPLx7zY4wQ4uf}kV;toWx7 z+j~>|?6BAR(xCgNTd$D(<(JIh?pR1OU+@TP)+EstZ>-h3zGkC)m7oU;mUYOw2Y!;{ z0{wK9Zoq16sxb_rV!##W)_yWUR5EuUo^iBK8_bLtsX}Lm!nr{&Msxv+Jxon*=N-20 z1-6fB1EtOi%LnwuJBM1F=Qz%Sd}p~p(OaiK7TQjQ`&bd0=fBWS=-Y6K5yi7rswv1` zJ;@&!G@Rvp7$bJ9}^ia$ ztv;CXA-Ik>?cyu_x~r37-Fy!5_;VvFyuUTFY_7eo3M`whrCKm87BtyQP!3sb0~!>D zImR2yR{%jNC%=g~b2r$Fe+61*bQ@b#4M7<9=l9TWdtw$&-g8c=?AoRuV)Nv_Ev)IX zNkY0`UUvZmT>q6pFw_a(KJXWN&*2p)83vw0eGtuFAF#n#uGw{50S*uG(n|Y5dsmV8 zeAK^F)~`)lvYt6YJGB$D%41*bw-&qy2dAZoxpCqJq&#>(&MlHyBdj0jeXmpvv?$f&LInGr0a8sm zG{J=W+6j_9Spn_|@o)Ig5p(_&_cGI?#RO5GjEE0`HwhZWw-B@EZAUb4NZUqSEnHxE zYfX6X{RhwvPMLhvGRg zOqQ87@*MBJUF!cg<=L1mMDm_NvywgA;cneDMfvHkHVGg+pwhHh31&FAFCBB+1R-^`; zlTpQ9QQp^P%ao;xE%P|0$#hxs<4KcWWF-#i632Zj&+7Z= zjB=YUo9zK8?J7jG%43Mg(!KC29a&%0U-xjj88fQkRf(U>5bW=|2hw&?4_dG?5a**+ z--B1wd81_uNz%7vlUl>)0(yAxG1%%}PCh-QG@9S|&wZjS3xgwE3R^S4+*tZlGuceN zpT*D7i{$jA2nf?AzIdeVEM^;s_Ztow!jsR(}^TTqNPr z|E>OIsI$`s^czFZ=-#9Dv`$D^fGu8Kv257)FdDhNZC8t(!I(yYd!$G3J``U3)p@x) z(RaG4Hyy3N_Bu?`O=EXLVzXsH>$EpC=8{)Dud+p2b`PAN$sCsD-wFsEe15NxSGG`M z9qKxjtbiYFwktg0GyKyF@Lwqk_c`X`sjm!+ zhmDaU;`?>qf7pP&X0TD9B@2Sk-|QHbXz_V|k%dP8GF{$X9q*gY#qQ_Mf``#pVK_f( zQOd%2p0C4~Q*Ib-EeL5k?1(hhX$XN-xd%_dCO%ZKAv`bjflua%Dn)`16;?q)f!6Fn zIye8d4wBRyM{aY(cKL5pf&BHQ>_nt)dRIyu#SZMR)Ydp3QCEv}(eomDTkZFP?VC{$i%HvT*vbVE-K5=2rh@f-5i1 zuZPtud(HXN@+;09?B_V_4vR&evsuz7vToGSL-G<{I}-FMIJjxsq%4=z7PDNSIcCnK zzD)Zmb6?39e&(EmV73o+m7z@VqPkjg^}UMuzE5S*Bd#Igum|G`qUT$ARSh@%qoDh* zz&RX0@(&+9tBGTp1JZ4)fvJM;5`W`-UGFsC<^EGAN#BrhFFl9 zZ*O4x37`me&BUkGLS#n7l{8B79HDCaQ|S-tWBBM{Y9)cJKnIrY62{2#ju2E^LS|&z zGL8aqMV-BLvnKt=b-eIqZfeNhpY?1&Q=}(zdQnU{cON5Xg0>?wRB%g;Gt(P) zrUr%4(v1Jk6FU!xoD-BHr@2b37t^{-!_%yz0|yBKl0n3TPt8Tn8zCii*Ir;k9t;mK zP6+54Zan)<+3_H_h#o>lbwnefMxG{pLC}Th$lfsf%c#e=UqnGrX1Yi90c+SLT`>hc z45K3Zort#)UFPzCj`&hM>C_QO zuXy?n%;-t+HpB82l5IF@op05J8MSvm$E@nq$<4{S0^yQ}rYS*GB7Oyz32X8|yY7+8x3d^s7<53(sAW#K`W=ZAak)OY|NWB%>rU zL1G;qF5Ll)6!0%@cKhH zc8!*5DhQM3TWz5Z~z zNu2J%fXC;Ej2_zj;1KkQYjxM@f+XS;HR6x?jzp;>50D=dKZho#^ zt~8h(>?Q$7n#bX_H)%^SR`Q zYO(n#%U1bFn1vYF@z&`ArJMNs)5hRgeLlkukMv4V3b2gDqa9pkPA`%ob`nW%ILE}3 z+-_c_H<$0;1X%%^Q|-T56fep6x#z4DzZ3m-5}ZkM&_aeE1t+V+WdTL6<$J!Lkt$Z! zSyf_KHV;zVjrh=15;xA7ic=nGuietxTWFeRj>Rkdx7Lsi+qI&%MnAq$ADr84b%GeA z$qgZuN7;H0XCAOfD=O}AEy;mwnJbZ;*PqfaAFC+A3o^G)UJhzn?Cz?aDs#cWJztEB zewKVOZSr_A;({``5wW115CUQt>uUN~nTUI-Z@l!2SX%oaz^=WHQU}=%H9|g5Jr$bA zZVkaQo#Zw)tg*5C(NRKh+*JZPiZFIX@~Ye(-k67QcUc$<+Zhl~_+@+GCV6f8u5cSK zElOzZavNjn!1!-}nf-Qm_1Zy?Q*(4LlMe|CS(?s>2d`$8q&i}IWUMD%yZTlq&m^Hh zbtccb%iNma4(y^HDR*lPm}n+Ib++lQU5TO6VeQ9A@(B`Uv9-Z@)LSzL;IK2gCwkhV zR}baU0ga%jN3?vd=nYslH}LIY?F1RN5IveO9jAErkeTk{=#⋘zCv$mqyah3mE0n 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

x4Yz&1iGz3fCU(!^KQ_ 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 literal 0 HcmV?d00001 diff --git a/src/app.cpp b/src/app.cpp index 7dd25b8..f744bcc 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -199,6 +199,7 @@ void App::create_pipeline() { std::println("[lvk] Shaders loaded"); m_pipeline_layout = m_device->createPipelineLayoutUnique({}); + auto const pipeline_state = PipelineState{ .vertex_shader = *vertex, .fragment_shader = *fragment, @@ -290,7 +291,7 @@ void App::render(vk::CommandBuffer const command_buffer) { .setLoadOp(vk::AttachmentLoadOp::eClear) .setStoreOp(vk::AttachmentStoreOp::eStore) // temporarily red. - .setClearValue(vk::ClearColorValue{1.0f, 0.0f, 0.0f, 1.0f}); + .setClearValue(vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f}); auto rendering_info = vk::RenderingInfo{}; auto const render_area = vk::Rect2D{vk::Offset2D{}, m_render_target->extent}; @@ -300,15 +301,20 @@ void App::render(vk::CommandBuffer const command_buffer) { command_buffer.beginRendering(rendering_info); ImGui::ShowDemoWindow(); + command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *m_pipeline); auto viewport = vk::Viewport{}; + // flip the viewport across the X-axis (negative height): + // https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/ viewport.setX(0.0f) .setY(static_cast(m_render_target->extent.height)) .setWidth(static_cast(m_render_target->extent.width)) .setHeight(-viewport.y); command_buffer.setViewport(0, viewport); command_buffer.setScissor(0, render_area); + // current shader has hard-coded logic for 3 vertices. command_buffer.draw(3, 1, 0, 0); + command_buffer.endRendering(); m_imgui->end_frame(); diff --git a/src/glsl/shader.frag b/src/glsl/shader.frag index 714e1a1..93094d4 100644 --- a/src/glsl/shader.frag +++ b/src/glsl/shader.frag @@ -1,7 +1,9 @@ #version 450 core +layout (location = 0) in vec3 in_color; + layout (location = 0) out vec4 out_color; void main() { - out_color = vec4(1.0); + out_color = vec4(in_color, 1.0); } diff --git a/src/glsl/shader.vert b/src/glsl/shader.vert index c620611..8efde56 100644 --- a/src/glsl/shader.vert +++ b/src/glsl/shader.vert @@ -1,5 +1,7 @@ #version 450 core +layout (location = 0) out vec3 out_color; + void main() { const vec2 positions[] = { vec2(-0.5, -0.5), @@ -7,7 +9,14 @@ void main() { vec2(0.0, 0.5), }; + const vec3 colors[] = { + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0), + }; + const vec2 position = positions[gl_VertexIndex]; + out_color = colors[gl_VertexIndex]; gl_Position = vec4(position, 0.0, 1.0); } diff --git a/src/pipeline_builder.cpp b/src/pipeline_builder.cpp index 17d38d7..00c7e6a 100644 --- a/src/pipeline_builder.cpp +++ b/src/pipeline_builder.cpp @@ -7,6 +7,7 @@ PipelineBuilder::PipelineBuilder(CreateInfo const& create_info) auto PipelineBuilder::build(vk::PipelineLayout const layout, PipelineState const& state) const -> vk::UniquePipeline { + // set vertex (0) and fragment (1) shader stages. auto shader_stages = std::array{}; shader_stages[0] .setStage(vk::ShaderStageFlagBits::eVertex) @@ -17,6 +18,10 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout, .setPName("main") .setModule(state.fragment_shader); + auto pvisci = vk::PipelineVertexInputStateCreateInfo{}; + pvisci.setVertexAttributeDescriptions(state.vertex_attributes) + .setVertexBindingDescriptions(state.vertex_bindings); + auto prsci = vk::PipelineRasterizationStateCreateInfo{}; prsci.setPolygonMode(state.polygon_mode).setCullMode(state.cull_mode); @@ -44,6 +49,7 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout, auto pcbsci = vk::PipelineColorBlendStateCreateInfo{}; pcbsci.setAttachments(pcbas); + // these dynamic states are guaranteed to be available. auto const pdscis = std::array{ vk::DynamicState::eViewport, vk::DynamicState::eScissor, @@ -52,6 +58,7 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout, auto pdsci = vk::PipelineDynamicStateCreateInfo{}; pdsci.setDynamicStates(pdscis); + // single viewport and scissor. auto const pvsci = vk::PipelineViewportStateCreateInfo({}, 1, {}, 1); auto pmsci = vk::PipelineMultisampleStateCreateInfo{}; @@ -59,6 +66,7 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout, .setSampleShadingEnable(vk::False); auto prci = vk::PipelineRenderingCreateInfo{}; + // could be a depth-only pass. if (m_info.color_format != vk::Format::eUndefined) { prci.setColorAttachmentFormats(m_info.color_format); } @@ -77,6 +85,7 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout, .setPNext(&prci); auto ret = vk::Pipeline{}; + // use non-throwing API. if (m_info.device.createGraphicsPipelines({}, 1, &gpci, {}, &ret) != vk::Result::eSuccess) { return {}; diff --git a/src/pipeline_state.hpp b/src/pipeline_state.hpp index f314a0b..ed70b83 100644 --- a/src/pipeline_state.hpp +++ b/src/pipeline_state.hpp @@ -1,15 +1,19 @@ #pragma once #include +#include +#include namespace lvk { +// bit flags for various binary Pipeline States. struct PipelineFlag { enum : std::uint8_t { None = 0, - AlphaBlend = 1 << 0, - DepthTest = 1 << 1, + AlphaBlend = 1 << 0, // turn on alpha blending. + DepthTest = 1 << 1, // turn on depth write and test. }; }; +// specification of a unique Graphics Pipeline. struct PipelineState { using Flag = PipelineFlag; @@ -17,8 +21,11 @@ struct PipelineState { return Flag::AlphaBlend | Flag::DepthTest; } - vk::ShaderModule vertex_shader; - vk::ShaderModule fragment_shader; + vk::ShaderModule vertex_shader; // required. + vk::ShaderModule fragment_shader; // required. + + std::span vertex_attributes{}; + std::span vertex_bindings{}; vk::PrimitiveTopology topology{vk::PrimitiveTopology::eTriangleList}; vk::PolygonMode polygon_mode{vk::PolygonMode::eFill}; From 21d4adceb1338054b1f8e0d512dc75c87bdbf590 Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Tue, 25 Mar 2025 22:11:53 -0700 Subject: [PATCH 4/7] Split up pipeline creation, update docs --- guide/src/SUMMARY.md | 2 +- .../{creation.md => pipeline_creation.md} | 168 ++++++++++++------ guide/src/pipeline/shaders.md | 2 + src/app.cpp | 4 +- src/pipeline_builder.cpp | 156 +++++++++------- 5 files changed, 211 insertions(+), 121 deletions(-) rename guide/src/pipeline/{creation.md => pipeline_creation.md} (56%) diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 9c73d7a..588ac6f 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -27,4 +27,4 @@ - [Graphics Pipeline](pipeline/README.md) - [Locating Assets](pipeline/locating_assets.md) - [Shaders](pipeline/shaders.md) - - [Pipeline Creation](pipeline/creation.md) + - [Pipeline Creation](pipeline/pipeline_creation.md) diff --git a/guide/src/pipeline/creation.md b/guide/src/pipeline/pipeline_creation.md similarity index 56% rename from guide/src/pipeline/creation.md rename to guide/src/pipeline/pipeline_creation.md index 9e9feff..464ac0f 100644 --- a/guide/src/pipeline/creation.md +++ b/guide/src/pipeline/pipeline_creation.md @@ -59,92 +59,140 @@ class PipelineBuilder { }; ``` -Implement `build()`: +Before implementing `build()`, add some helper functions/constants, starting with the viewport and dynamic states: ```cpp -auto PipelineBuilder::build(vk::PipelineLayout const layout, - PipelineState const& state) const - -> vk::UniquePipeline { +// single viewport and scissor. +constexpr auto viewport_state_v = + vk::PipelineViewportStateCreateInfo({}, 1, {}, 1); + +// these dynamic states are guaranteed to be available. +constexpr auto dynamic_states_v = std::array{ + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + vk::DynamicState::eLineWidth, +}; +``` + +The shader stages: + +```cpp +[[nodiscard]] constexpr auto +create_shader_stages(vk::ShaderModule const vertex, + vk::ShaderModule const fragment) { // set vertex (0) and fragment (1) shader stages. - auto shader_stages = std::array{}; - shader_stages[0] + auto ret = std::array{}; + ret[0] .setStage(vk::ShaderStageFlagBits::eVertex) .setPName("main") - .setModule(state.vertex_shader); - shader_stages[1] + .setModule(vertex); + ret[1] .setStage(vk::ShaderStageFlagBits::eFragment) .setPName("main") - .setModule(state.fragment_shader); - - auto pvisci = vk::PipelineVertexInputStateCreateInfo{}; - pvisci.setVertexAttributeDescriptions(state.vertex_attributes) - .setVertexBindingDescriptions(state.vertex_bindings); + .setModule(fragment); + return ret; +} +``` - auto prsci = vk::PipelineRasterizationStateCreateInfo{}; - prsci.setPolygonMode(state.polygon_mode).setCullMode(state.cull_mode); +The depth/stencil state: - auto pdssci = vk::PipelineDepthStencilStateCreateInfo{}; +```cpp +[[nodiscard]] constexpr auto +create_depth_stencil_state(std::uint8_t flags, + vk::CompareOp const depth_compare) { + auto ret = vk::PipelineDepthStencilStateCreateInfo{}; auto const depth_test = - (state.flags & PipelineFlag::DepthTest) == PipelineFlag::DepthTest; - pdssci.setDepthTestEnable(depth_test ? vk::True : vk::False) - .setDepthCompareOp(state.depth_compare); + (flags & PipelineFlag::DepthTest) == PipelineFlag::DepthTest; + ret.setDepthTestEnable(depth_test ? vk::True : vk::False) + .setDepthCompareOp(depth_compare); + return ret; +} +``` - auto const piasci = - vk::PipelineInputAssemblyStateCreateInfo{{}, state.topology}; +And a color blend attachment: - auto pcbas = vk::PipelineColorBlendAttachmentState{}; +```cpp +[[nodiscard]] constexpr auto +create_color_blend_attachment(std::uint8_t const flags) { + auto ret = vk::PipelineColorBlendAttachmentState{}; auto const alpha_blend = - (state.flags & PipelineFlag::AlphaBlend) == PipelineFlag::AlphaBlend; + (flags & PipelineFlag::AlphaBlend) == PipelineFlag::AlphaBlend; using CCF = vk::ColorComponentFlagBits; - pcbas.setColorWriteMask(CCF::eR | CCF::eG | CCF::eB | CCF::eA) + ret.setColorWriteMask(CCF::eR | CCF::eG | CCF::eB | CCF::eA) .setBlendEnable(alpha_blend ? vk::True : vk::False) + // standard alpha blending: + // (alpha * src) + (1 - alpha) * dst .setSrcColorBlendFactor(vk::BlendFactor::eSrcAlpha) .setDstColorBlendFactor(vk::BlendFactor::eOneMinusSrcAlpha) .setColorBlendOp(vk::BlendOp::eAdd) .setSrcAlphaBlendFactor(vk::BlendFactor::eOne) .setDstAlphaBlendFactor(vk::BlendFactor::eZero) .setAlphaBlendOp(vk::BlendOp::eAdd); - auto pcbsci = vk::PipelineColorBlendStateCreateInfo{}; - pcbsci.setAttachments(pcbas); - - // these dynamic states are guaranteed to be available. - auto const pdscis = std::array{ - vk::DynamicState::eViewport, - vk::DynamicState::eScissor, - vk::DynamicState::eLineWidth, - }; - auto pdsci = vk::PipelineDynamicStateCreateInfo{}; - pdsci.setDynamicStates(pdscis); + return ret; +} +``` + +Now we can implement `build()`: + +```cpp +auto PipelineBuilder::build(vk::PipelineLayout const layout, + PipelineState const& state) const + -> vk::UniquePipeline { + auto const shader_stage_ci = + create_shader_stages(state.vertex_shader, state.fragment_shader); - // single viewport and scissor. - auto const pvsci = vk::PipelineViewportStateCreateInfo({}, 1, {}, 1); + auto vertex_input_ci = vk::PipelineVertexInputStateCreateInfo{}; + vertex_input_ci.setVertexAttributeDescriptions(state.vertex_attributes) + .setVertexBindingDescriptions(state.vertex_bindings); - auto pmsci = vk::PipelineMultisampleStateCreateInfo{}; - pmsci.setRasterizationSamples(m_info.samples) + auto multisample_state_ci = vk::PipelineMultisampleStateCreateInfo{}; + multisample_state_ci.setRasterizationSamples(m_info.samples) .setSampleShadingEnable(vk::False); - auto prci = vk::PipelineRenderingCreateInfo{}; - // could be a depth-only pass. + auto const input_assembly_ci = + vk::PipelineInputAssemblyStateCreateInfo{{}, state.topology}; + + auto rasterization_state_ci = vk::PipelineRasterizationStateCreateInfo{}; + rasterization_state_ci.setPolygonMode(state.polygon_mode) + .setCullMode(state.cull_mode); + + auto const depth_stencil_state_ci = + create_depth_stencil_state(state.flags, state.depth_compare); + + auto const color_blend_attachment = + create_color_blend_attachment(state.flags); + auto color_blend_state_ci = vk::PipelineColorBlendStateCreateInfo{}; + color_blend_state_ci.setAttachments(color_blend_attachment); + + auto dynamic_state_ci = vk::PipelineDynamicStateCreateInfo{}; + dynamic_state_ci.setDynamicStates(dynamic_states_v); + + // Dynamic Rendering requires passing this in the pNext chain. + auto rendering_ci = vk::PipelineRenderingCreateInfo{}; + // could be a depth-only pass, argument is span-like (notice the plural + // `Formats()`), only set if not Undefined. if (m_info.color_format != vk::Format::eUndefined) { - prci.setColorAttachmentFormats(m_info.color_format); + rendering_ci.setColorAttachmentFormats(m_info.color_format); } - prci.setDepthAttachmentFormat(m_info.depth_format); - - auto gpci = vk::GraphicsPipelineCreateInfo{}; - gpci.setStages(shader_stages) - .setPRasterizationState(&prsci) - .setPDepthStencilState(&pdssci) - .setPInputAssemblyState(&piasci) - .setPColorBlendState(&pcbsci) - .setPDynamicState(&pdsci) - .setPViewportState(&pvsci) - .setPMultisampleState(&pmsci) - .setLayout(layout) - .setPNext(&prci); + // single depth attachment format, ok to set to Undefined. + rendering_ci.setDepthAttachmentFormat(m_info.depth_format); + + auto pipeline_ci = vk::GraphicsPipelineCreateInfo{}; + pipeline_ci.setLayout(layout) + .setStages(shader_stage_ci) + .setPVertexInputState(&vertex_input_ci) + .setPViewportState(&viewport_state_v) + .setPMultisampleState(&multisample_state_ci) + .setPInputAssemblyState(&input_assembly_ci) + .setPRasterizationState(&rasterization_state_ci) + .setPDepthStencilState(&depth_stencil_state_ci) + .setPColorBlendState(&color_blend_state_ci) + .setPDynamicState(&dynamic_state_ci) + .setPNext(&rendering_ci); auto ret = vk::Pipeline{}; // use non-throwing API. - if (m_info.device.createGraphicsPipelines({}, 1, &gpci, {}, &ret) != + if (m_info.device.createGraphicsPipelines({}, 1, &pipeline_ci, {}, &ret) != vk::Result::eSuccess) { return {}; } @@ -195,15 +243,17 @@ if (!m_pipeline) { } ``` -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. +We can now bind it and use it to draw the triangle in the shader: ```cpp command_buffer.beginRendering(rendering_info); ImGui::ShowDemoWindow(); command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *m_pipeline); +// we are creating pipelines with dynamic viewport and scissor states. +// they must be set here after binding (before drawing). auto viewport = vk::Viewport{}; -// flip the viewport across the X-axis (negative height): +// flip the viewport about the X-axis (negative height): // https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/ viewport.setX(0.0f) .setY(static_cast(m_render_target->extent.height)) @@ -242,6 +292,8 @@ layout (location = 0) in vec3 in_color; out_color = vec4(in_color, 1.0); ``` +> Make sure to recompile both the SPIR-V shaders in assets/. + And a black clear color: ```cpp diff --git a/guide/src/pipeline/shaders.md b/guide/src/pipeline/shaders.md index 795bfec..c884ffd 100644 --- a/guide/src/pipeline/shaders.md +++ b/guide/src/pipeline/shaders.md @@ -37,6 +37,8 @@ glslc src/glsl/shader.vert -o assets/shader.vert glslc src/glsl/shader.frag -o assets/shader.frag ``` +> glslc is part of the Vulkan SDK. + ## Shader Modules SPIR-V modules are binary files with a stride/alignment of 4 bytes. The Vulkan API accepts a span of `std::uint32_t`s, so we need to load it into such a buffer (and _not_ `std::vector` or other 1-byte equivalents). diff --git a/src/app.cpp b/src/app.cpp index f744bcc..13fed38 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -303,8 +303,10 @@ void App::render(vk::CommandBuffer const command_buffer) { ImGui::ShowDemoWindow(); command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *m_pipeline); + // we are creating pipelines with dynamic viewport and scissor states. + // they must be set here after binding (before drawing). auto viewport = vk::Viewport{}; - // flip the viewport across the X-axis (negative height): + // flip the viewport about the X-axis (negative height): // https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/ viewport.setX(0.0f) .setY(static_cast(m_render_target->extent.height)) diff --git a/src/pipeline_builder.cpp b/src/pipeline_builder.cpp index 00c7e6a..9ac4e0e 100644 --- a/src/pipeline_builder.cpp +++ b/src/pipeline_builder.cpp @@ -1,92 +1,126 @@ #include namespace lvk { -PipelineBuilder::PipelineBuilder(CreateInfo const& create_info) - : m_info(create_info) {} +namespace { +// single viewport and scissor. +constexpr auto viewport_state_v = + vk::PipelineViewportStateCreateInfo({}, 1, {}, 1); -auto PipelineBuilder::build(vk::PipelineLayout const layout, - PipelineState const& state) const - -> vk::UniquePipeline { +// these dynamic states are guaranteed to be available. +constexpr auto dynamic_states_v = std::array{ + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + vk::DynamicState::eLineWidth, +}; + +[[nodiscard]] constexpr auto +create_shader_stages(vk::ShaderModule const vertex, + vk::ShaderModule const fragment) { // set vertex (0) and fragment (1) shader stages. - auto shader_stages = std::array{}; - shader_stages[0] + auto ret = std::array{}; + ret[0] .setStage(vk::ShaderStageFlagBits::eVertex) .setPName("main") - .setModule(state.vertex_shader); - shader_stages[1] + .setModule(vertex); + ret[1] .setStage(vk::ShaderStageFlagBits::eFragment) .setPName("main") - .setModule(state.fragment_shader); - - auto pvisci = vk::PipelineVertexInputStateCreateInfo{}; - pvisci.setVertexAttributeDescriptions(state.vertex_attributes) - .setVertexBindingDescriptions(state.vertex_bindings); - - auto prsci = vk::PipelineRasterizationStateCreateInfo{}; - prsci.setPolygonMode(state.polygon_mode).setCullMode(state.cull_mode); + .setModule(fragment); + return ret; +} - auto pdssci = vk::PipelineDepthStencilStateCreateInfo{}; +[[nodiscard]] constexpr auto +create_depth_stencil_state(std::uint8_t flags, + vk::CompareOp const depth_compare) { + auto ret = vk::PipelineDepthStencilStateCreateInfo{}; auto const depth_test = - (state.flags & PipelineFlag::DepthTest) == PipelineFlag::DepthTest; - pdssci.setDepthTestEnable(depth_test ? vk::True : vk::False) - .setDepthCompareOp(state.depth_compare); - - auto const piasci = - vk::PipelineInputAssemblyStateCreateInfo{{}, state.topology}; + (flags & PipelineFlag::DepthTest) == PipelineFlag::DepthTest; + ret.setDepthTestEnable(depth_test ? vk::True : vk::False) + .setDepthCompareOp(depth_compare); + return ret; +} - auto pcbas = vk::PipelineColorBlendAttachmentState{}; +[[nodiscard]] constexpr auto +create_color_blend_attachment(std::uint8_t const flags) { + auto ret = vk::PipelineColorBlendAttachmentState{}; auto const alpha_blend = - (state.flags & PipelineFlag::AlphaBlend) == PipelineFlag::AlphaBlend; + (flags & PipelineFlag::AlphaBlend) == PipelineFlag::AlphaBlend; using CCF = vk::ColorComponentFlagBits; - pcbas.setColorWriteMask(CCF::eR | CCF::eG | CCF::eB | CCF::eA) + ret.setColorWriteMask(CCF::eR | CCF::eG | CCF::eB | CCF::eA) .setBlendEnable(alpha_blend ? vk::True : vk::False) + // standard alpha blending: + // (alpha * src) + (1 - alpha) * dst .setSrcColorBlendFactor(vk::BlendFactor::eSrcAlpha) .setDstColorBlendFactor(vk::BlendFactor::eOneMinusSrcAlpha) .setColorBlendOp(vk::BlendOp::eAdd) .setSrcAlphaBlendFactor(vk::BlendFactor::eOne) .setDstAlphaBlendFactor(vk::BlendFactor::eZero) .setAlphaBlendOp(vk::BlendOp::eAdd); - auto pcbsci = vk::PipelineColorBlendStateCreateInfo{}; - pcbsci.setAttachments(pcbas); - - // these dynamic states are guaranteed to be available. - auto const pdscis = std::array{ - vk::DynamicState::eViewport, - vk::DynamicState::eScissor, - vk::DynamicState::eLineWidth, - }; - auto pdsci = vk::PipelineDynamicStateCreateInfo{}; - pdsci.setDynamicStates(pdscis); - - // single viewport and scissor. - auto const pvsci = vk::PipelineViewportStateCreateInfo({}, 1, {}, 1); - - auto pmsci = vk::PipelineMultisampleStateCreateInfo{}; - pmsci.setRasterizationSamples(m_info.samples) + return ret; +} +} // namespace + +PipelineBuilder::PipelineBuilder(CreateInfo const& create_info) + : m_info(create_info) {} + +auto PipelineBuilder::build(vk::PipelineLayout const layout, + PipelineState const& state) const + -> vk::UniquePipeline { + auto const shader_stage_ci = + create_shader_stages(state.vertex_shader, state.fragment_shader); + + auto vertex_input_ci = vk::PipelineVertexInputStateCreateInfo{}; + vertex_input_ci.setVertexAttributeDescriptions(state.vertex_attributes) + .setVertexBindingDescriptions(state.vertex_bindings); + + auto multisample_state_ci = vk::PipelineMultisampleStateCreateInfo{}; + multisample_state_ci.setRasterizationSamples(m_info.samples) .setSampleShadingEnable(vk::False); - auto prci = vk::PipelineRenderingCreateInfo{}; - // could be a depth-only pass. + auto const input_assembly_ci = + vk::PipelineInputAssemblyStateCreateInfo{{}, state.topology}; + + auto rasterization_state_ci = vk::PipelineRasterizationStateCreateInfo{}; + rasterization_state_ci.setPolygonMode(state.polygon_mode) + .setCullMode(state.cull_mode); + + auto const depth_stencil_state_ci = + create_depth_stencil_state(state.flags, state.depth_compare); + + auto const color_blend_attachment = + create_color_blend_attachment(state.flags); + auto color_blend_state_ci = vk::PipelineColorBlendStateCreateInfo{}; + color_blend_state_ci.setAttachments(color_blend_attachment); + + auto dynamic_state_ci = vk::PipelineDynamicStateCreateInfo{}; + dynamic_state_ci.setDynamicStates(dynamic_states_v); + + // Dynamic Rendering requires passing this in the pNext chain. + auto rendering_ci = vk::PipelineRenderingCreateInfo{}; + // could be a depth-only pass, argument is span-like (notice the plural + // `Formats()`), only set if not Undefined. if (m_info.color_format != vk::Format::eUndefined) { - prci.setColorAttachmentFormats(m_info.color_format); + rendering_ci.setColorAttachmentFormats(m_info.color_format); } - prci.setDepthAttachmentFormat(m_info.depth_format); - - auto gpci = vk::GraphicsPipelineCreateInfo{}; - gpci.setStages(shader_stages) - .setPRasterizationState(&prsci) - .setPDepthStencilState(&pdssci) - .setPInputAssemblyState(&piasci) - .setPColorBlendState(&pcbsci) - .setPDynamicState(&pdsci) - .setPViewportState(&pvsci) - .setPMultisampleState(&pmsci) - .setLayout(layout) - .setPNext(&prci); + // single depth attachment format, ok to set to Undefined. + rendering_ci.setDepthAttachmentFormat(m_info.depth_format); + + auto pipeline_ci = vk::GraphicsPipelineCreateInfo{}; + pipeline_ci.setLayout(layout) + .setStages(shader_stage_ci) + .setPVertexInputState(&vertex_input_ci) + .setPViewportState(&viewport_state_v) + .setPMultisampleState(&multisample_state_ci) + .setPInputAssemblyState(&input_assembly_ci) + .setPRasterizationState(&rasterization_state_ci) + .setPDepthStencilState(&depth_stencil_state_ci) + .setPColorBlendState(&color_blend_state_ci) + .setPDynamicState(&dynamic_state_ci) + .setPNext(&rendering_ci); auto ret = vk::Pipeline{}; // use non-throwing API. - if (m_info.device.createGraphicsPipelines({}, 1, &gpci, {}, &ret) != + if (m_info.device.createGraphicsPipelines({}, 1, &pipeline_ci, {}, &ret) != vk::Result::eSuccess) { return {}; } From 6336a0e4128fc5c311b6f360194124ca83337aeb Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Tue, 25 Mar 2025 22:24:43 -0700 Subject: [PATCH 5/7] Fix constexpr error --- src/pipeline_builder.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pipeline_builder.cpp b/src/pipeline_builder.cpp index 9ac4e0e..7d65ffe 100644 --- a/src/pipeline_builder.cpp +++ b/src/pipeline_builder.cpp @@ -13,9 +13,8 @@ constexpr auto dynamic_states_v = std::array{ vk::DynamicState::eLineWidth, }; -[[nodiscard]] constexpr auto -create_shader_stages(vk::ShaderModule const vertex, - vk::ShaderModule const fragment) { +[[nodiscard]] auto create_shader_stages(vk::ShaderModule const vertex, + vk::ShaderModule const fragment) { // set vertex (0) and fragment (1) shader stages. auto ret = std::array{}; ret[0] From ddb428e757933558b425f961c5dd265cbb2ae9e2 Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Wed, 26 Mar 2025 13:38:34 -0700 Subject: [PATCH 6/7] Log error on pipeline creation failure --- src/pipeline_builder.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pipeline_builder.cpp b/src/pipeline_builder.cpp index 7d65ffe..0e76862 100644 --- a/src/pipeline_builder.cpp +++ b/src/pipeline_builder.cpp @@ -1,4 +1,5 @@ #include +#include namespace lvk { namespace { @@ -121,6 +122,7 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout, // use non-throwing API. if (m_info.device.createGraphicsPipelines({}, 1, &pipeline_ci, {}, &ret) != vk::Result::eSuccess) { + std::println(stderr, "[lvk] Failed to create Graphics Pipeline"); return {}; } From d05d31e26866485dbe1a16283ee524b0fc9cbb35 Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Wed, 26 Mar 2025 19:01:02 -0700 Subject: [PATCH 7/7] Cleanup, wireframe --- guide/src/SUMMARY.md | 2 + guide/src/pipeline/drawing_triangle.md | 138 ++++++++++++++++++ guide/src/pipeline/pipeline_creation.md | 108 +------------- guide/src/pipeline/shaders.md | 18 +-- .../src/pipeline/srgb_triangle_wireframe.png | Bin 0 -> 31164 bytes guide/src/pipeline/switching_pipelines.md | 39 +++++ src/app.cpp | 79 ++++++---- src/app.hpp | 15 +- 8 files changed, 257 insertions(+), 142 deletions(-) create mode 100644 guide/src/pipeline/drawing_triangle.md create mode 100644 guide/src/pipeline/srgb_triangle_wireframe.png create mode 100644 guide/src/pipeline/switching_pipelines.md diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 588ac6f..d86d249 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -28,3 +28,5 @@ - [Locating Assets](pipeline/locating_assets.md) - [Shaders](pipeline/shaders.md) - [Pipeline Creation](pipeline/pipeline_creation.md) + - [Drawing a Triangle](pipeline/drawing_triangle.md) + - [Switching Pipelines](pipeline/switching_pipelines.md) diff --git a/guide/src/pipeline/drawing_triangle.md b/guide/src/pipeline/drawing_triangle.md new file mode 100644 index 0000000..e4c22ad --- /dev/null +++ b/guide/src/pipeline/drawing_triangle.md @@ -0,0 +1,138 @@ +# Drawing a Triangle + +We shall create two pipelines: one for standard draws, one for wireframe draws. Add new `App` members: + +```cpp +void create_pipeline_builder(); +void create_pipelines(); + +// ... +std::optional m_pipeline_builder{}; + +vk::UniquePipelineLayout m_pipeline_layout{}; +struct { + vk::UniquePipeline standard{}; + vk::UniquePipeline wireframe{}; +} m_pipelines{}; +float m_line_width{1.0f}; +bool m_wireframe{}; +``` + +Implement and call `create_pipeline_builder()`: + +```cpp +void App::create_pipeline_builder() { + auto const pipeline_builder_ci = PipelineBuilder::CreateInfo{ + .device = *m_device, + .samples = vk::SampleCountFlagBits::e1, + .color_format = m_swapchain->get_format(), + }; + m_pipeline_builder.emplace(pipeline_builder_ci); +} +``` + +Complete the implementation of `create_pipelines()`: + +```cpp +// ... +m_pipeline_layout = m_device->createPipelineLayoutUnique({}); + +auto pipeline_state = PipelineState{ + .vertex_shader = *vertex, + .fragment_shader = *fragment, +}; +m_pipelines.standard = + m_pipeline_builder->build(*m_pipeline_layout, pipeline_state); +pipeline_state.polygon_mode = vk::PolygonMode::eLine; +m_pipelines.wireframe = + m_pipeline_builder->build(*m_pipeline_layout, pipeline_state); +if (!m_pipelines.standard || !m_pipelines.wireframe) { + throw std::runtime_error{"Failed to create Graphics Pipelines"}; +} +``` + +Before `render()` grows to an unwieldy size, extract the higher level logic into two member functions: + +```cpp +// ImGui code goes here. +void inspect(); +// Issue draw calls here. +void draw(vk::Rect2D const& render_area, + vk::CommandBuffer command_buffer) const; + +// ... +void App::inspect() { + ImGui::ShowDemoWindow(); + // TODO +} + +// ... +command_buffer.beginRendering(rendering_info); +inspect(); +draw(render_area, command_buffer); +command_buffer.endRendering(); +``` + +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: + +```cpp +void App::draw(vk::Rect2D const& render_area, + vk::CommandBuffer const command_buffer) const { + command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, + *m_pipelines.standard); + // we are creating pipelines with dynamic viewport and scissor states. + // they must be set here after binding (before drawing). + auto viewport = vk::Viewport{}; + // flip the viewport about the X-axis (negative height): + // https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/ + viewport.setX(0.0f) + .setY(static_cast(m_render_target->extent.height)) + .setWidth(static_cast(m_render_target->extent.width)) + .setHeight(-viewport.y); + command_buffer.setViewport(0, viewport); + command_buffer.setScissor(0, render_area); + // current shader has hard-coded logic for 3 vertices. + command_buffer.draw(3, 1, 0, 0); +} +``` + +![White Triangle](./white_triangle.png) + +Updating our shaders to use interpolated RGB on each vertex: + +```glsl +// shader.vert + +layout (location = 0) out vec3 out_color; + +// ... +const vec3 colors[] = { + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0), +}; + +// ... +out_color = colors[gl_VertexIndex]; + +// shader.frag + +layout (location = 0) in vec3 in_color; + +// ... +out_color = vec4(in_color, 1.0); +``` + +> Make sure to recompile both the SPIR-V shaders in assets/. + +And a black clear color: + +```cpp +// ... +.setClearValue(vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f}); +``` + +Gives us the renowned Vulkan sRGB triangle: + +![sRGB Triangle](./srgb_triangle.png) + diff --git a/guide/src/pipeline/pipeline_creation.md b/guide/src/pipeline/pipeline_creation.md index 464ac0f..d1c44b2 100644 --- a/guide/src/pipeline/pipeline_creation.md +++ b/guide/src/pipeline/pipeline_creation.md @@ -8,7 +8,7 @@ struct PipelineFlag { enum : std::uint8_t { None = 0, AlphaBlend = 1 << 0, // turn on alpha blending. - DepthTest = 1 << 1, // turn on depth write and test. + DepthTest = 1 << 1, // turn on depth write and test. }; }; @@ -20,7 +20,7 @@ struct PipelineState { return Flag::AlphaBlend | Flag::DepthTest; } - vk::ShaderModule vertex_shader; // required. + vk::ShaderModule vertex_shader; // required. vk::ShaderModule fragment_shader; // required. std::span vertex_attributes{}; @@ -200,107 +200,3 @@ auto PipelineBuilder::build(vk::PipelineLayout const layout, return vk::UniquePipeline{ret, m_info.device}; } ``` - -Add new `App` members: - -```cpp -void create_pipeline_builder(); -void create_pipeline(); - -// ... -std::optional m_pipeline_builder{}; - -vk::UniquePipelineLayout m_pipeline_layout{}; -vk::UniquePipeline m_pipeline{}; -``` - -Implement and call `create_pipeline_builder()`: - -```cpp -void App::create_pipeline_builder() { - auto const pipeline_builder_ci = PipelineBuilder::CreateInfo{ - .device = *m_device, - .samples = vk::SampleCountFlagBits::e1, - .color_format = m_swapchain->get_format(), - }; - m_pipeline_builder.emplace(pipeline_builder_ci); -} -``` - -Complete the implementation of `create_pipeline()`: - -```cpp -// ... -m_pipeline_layout = m_device->createPipelineLayoutUnique({}); - -auto const pipeline_state = PipelineState{ - .vertex_shader = *vertex, - .fragment_shader = *fragment, -}; -m_pipeline = m_pipeline_builder->build(*m_pipeline_layout, pipeline_state); -if (!m_pipeline) { - throw std::runtime_error{"Failed to create Graphics Pipeline"}; -} -``` - -We can now bind it and use it to draw the triangle in the shader: - -```cpp -command_buffer.beginRendering(rendering_info); -ImGui::ShowDemoWindow(); - -command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *m_pipeline); -// we are creating pipelines with dynamic viewport and scissor states. -// they must be set here after binding (before drawing). -auto viewport = vk::Viewport{}; -// flip the viewport about the X-axis (negative height): -// https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/ -viewport.setX(0.0f) - .setY(static_cast(m_render_target->extent.height)) - .setWidth(static_cast(m_render_target->extent.width)) - .setHeight(-viewport.y); -command_buffer.setViewport(0, viewport); -command_buffer.setScissor(0, render_area); -// current shader has hard-coded logic for 3 vertices. -command_buffer.draw(3, 1, 0, 0); -``` - -![White Triangle](./white_triangle.png) - -Updating our shaders to use interpolated RGB on each vertex: - -```glsl -// shader.vert - -layout (location = 0) out vec3 out_color; - -// ... -const vec3 colors[] = { - vec3(1.0, 0.0, 0.0), - vec3(0.0, 1.0, 0.0), - vec3(0.0, 0.0, 1.0), -}; - -// ... -out_color = colors[gl_VertexIndex]; - -// shader.frag - -layout (location = 0) in vec3 in_color; - -// ... -out_color = vec4(in_color, 1.0); -``` - -> Make sure to recompile both the SPIR-V shaders in assets/. - -And a black clear color: - -```cpp -// ... -.setClearValue(vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f}); -``` - -Gives us the renowned Vulkan sRGB triangle: - -![sRGB Triangle](./srgb_triangle.png) diff --git a/guide/src/pipeline/shaders.md b/guide/src/pipeline/shaders.md index c884ffd..dfc0571 100644 --- a/guide/src/pipeline/shaders.md +++ b/guide/src/pipeline/shaders.md @@ -93,22 +93,18 @@ auto ShaderLoader::load(fs::path const& path) -> vk::UniqueShaderModule { Add new members to `App`: ```cpp -void create_pipeline(); +void create_pipelines(); [[nodiscard]] auto asset_path(std::string_view uri) const -> fs::path; ``` -Implement and call `create_pipeline()` before starting the main loop: +Add code to load shaders in `create_pipelines()` and call it before starting the main loop: ```cpp -auto App::asset_path(std::string_view const uri) const -> fs::path { - return m_assets_dir / uri; -} - -void App::create_pipeline() { +void App::create_pipelines() { auto shader_loader = ShaderLoader{*m_device}; - // we only need shader modules to create the pipeline, thus no need to store - // them as members. + // we only need shader modules to create the pipelines, thus no need to + // store them as members. auto const vertex = shader_loader.load(asset_path("shader.vert")); auto const fragment = shader_loader.load(asset_path("shader.frag")); if (!vertex || !fragment) { @@ -118,4 +114,8 @@ void App::create_pipeline() { // TODO } + +auto App::asset_path(std::string_view const uri) const -> fs::path { + return m_assets_dir / uri; +} ``` diff --git a/guide/src/pipeline/srgb_triangle_wireframe.png b/guide/src/pipeline/srgb_triangle_wireframe.png new file mode 100644 index 0000000000000000000000000000000000000000..92b9c6bfa843b1776f44cf90e9c25006dd9a368b GIT binary patch literal 31164 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 - auto const pipeline_state = PipelineState{ + auto pipeline_state = PipelineState{ .vertex_shader = *vertex, .fragment_shader = *fragment, }; - m_pipeline = m_pipeline_builder->build(*m_pipeline_layout, pipeline_state); - if (!m_pipeline) { - throw std::runtime_error{"Failed to create Graphics Pipeline"}; + m_pipelines.standard = + m_pipeline_builder->build(*m_pipeline_layout, pipeline_state); + pipeline_state.polygon_mode = vk::PolygonMode::eLine; + m_pipelines.wireframe = + m_pipeline_builder->build(*m_pipeline_layout, pipeline_state); + if (!m_pipelines.standard || !m_pipelines.wireframe) { + throw std::runtime_error{"Failed to create Graphics Pipelines"}; } } @@ -300,23 +304,8 @@ void App::render(vk::CommandBuffer const command_buffer) { .setLayerCount(1); command_buffer.beginRendering(rendering_info); - ImGui::ShowDemoWindow(); - - command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *m_pipeline); - // we are creating pipelines with dynamic viewport and scissor states. - // they must be set here after binding (before drawing). - auto viewport = vk::Viewport{}; - // flip the viewport about the X-axis (negative height): - // https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/ - viewport.setX(0.0f) - .setY(static_cast(m_render_target->extent.height)) - .setWidth(static_cast(m_render_target->extent.width)) - .setHeight(-viewport.y); - command_buffer.setViewport(0, viewport); - command_buffer.setScissor(0, render_area); - // current shader has hard-coded logic for 3 vertices. - command_buffer.draw(3, 1, 0, 0); - + inspect(); + draw(render_area, command_buffer); command_buffer.endRendering(); m_imgui->end_frame(); @@ -372,4 +361,44 @@ void App::submit_and_present() { m_swapchain->recreate(m_framebuffer_size); } } + +void App::inspect() { + ImGui::ShowDemoWindow(); + + ImGui::SetNextWindowSize({200.0f, 100.0f}, ImGuiCond_Once); + if (ImGui::Begin("Inspect")) { + ImGui::Checkbox("wireframe", &m_wireframe); + if (m_wireframe) { + auto const& line_width_range = + m_gpu.properties.limits.lineWidthRange; + ImGui::SetNextItemWidth(100.0f); + ImGui::DragFloat("line width", &m_line_width, 0.25f, + line_width_range[0], line_width_range[1]); + } + } + ImGui::End(); +} + +void App::draw(vk::Rect2D const& render_area, + vk::CommandBuffer const command_buffer) const { + auto const pipeline = + m_wireframe ? *m_pipelines.wireframe : *m_pipelines.standard; + command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); + // we are creating pipelines with dynamic viewport and scissor states. + // they must be set here after binding (before drawing). + auto viewport = vk::Viewport{}; + // flip the viewport about the X-axis (negative height): + // https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/ + viewport.setX(0.0f) + .setY(static_cast(m_render_target->extent.height)) + .setWidth(static_cast(m_render_target->extent.width)) + .setHeight(-viewport.y); + command_buffer.setViewport(0, viewport); + command_buffer.setScissor(0, render_area); + // line width is also a dynamic state in our pipelines, but setting it is + // not required (defaults to 1.0f). + command_buffer.setLineWidth(m_line_width); + // current shader has hard-coded logic for 3 vertices. + command_buffer.draw(3, 1, 0, 0); +} } // namespace lvk diff --git a/src/app.hpp b/src/app.hpp index a9d5824..9b479d8 100644 --- a/src/app.hpp +++ b/src/app.hpp @@ -36,7 +36,7 @@ class App { void create_render_sync(); void create_imgui(); void create_pipeline_builder(); - void create_pipeline(); + void create_pipelines(); [[nodiscard]] auto asset_path(std::string_view uri) const -> fs::path; @@ -49,6 +49,12 @@ class App { void transition_for_present(vk::CommandBuffer command_buffer) const; void submit_and_present(); + // ImGui code goes here. + void inspect(); + // Issue draw calls here. + void draw(vk::Rect2D const& render_area, + vk::CommandBuffer command_buffer) const; + fs::path m_assets_dir{}; // the order of these RAII members is crucially important. @@ -71,7 +77,12 @@ class App { std::optional m_pipeline_builder{}; vk::UniquePipelineLayout m_pipeline_layout{}; - vk::UniquePipeline m_pipeline{}; + struct { + vk::UniquePipeline standard{}; + vk::UniquePipeline wireframe{}; + } m_pipelines{}; + float m_line_width{1.0f}; + bool m_wireframe{}; glm::ivec2 m_framebuffer_size{}; std::optional m_render_target{};