MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1#include "mxvk/argz.hpp"
2#include "mxvk/mxvk.hpp"
4
5#include <array>
6#include <chrono>
7#include <cstdlib>
8#include <format>
9#include <iostream>
10#include <string>
11#include <vector>
12
13namespace example {
15 struct PushConstants {
16 float time;
17 float aspect;
18 };
19
20 public:
21 ExampleWindow(const std::string path, const std::string &text, int width, int height, bool fullscreen, bool enable_vsync) : mxvk::VK_Window(text, width, height, fullscreen, MXVK_VALIDATION, enable_vsync), shader_root((path.empty() ? std::string(HELLO_WORLD_ASSET_DIR) : path) + "/data") { setClearColor(0.02f, 0.03f, 0.06f, 1.0f); }
22
23 ~ExampleWindow() override {
24 if (device != VK_NULL_HANDLE) {
25 vkDeviceWaitIdle(device);
26 }
27 destroyGraphicsPipeline();
28 }
29
30 void onSwapchainAboutToRecreate() override { destroyGraphicsPipeline(); }
31
32 void onRecordCustomRendering(VkCommandBuffer cmd, [[maybe_unused]] uint32_t image_index) override {
33 if (!ensureGraphicsPipeline()) {
34 return;
35 }
36
37 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
38
39 const auto now = std::chrono::steady_clock::now();
40 const float elapsed_seconds = std::chrono::duration<float>(now - start_time).count();
41 const VkExtent2D extent = getSwapchainExtent();
42 const float aspect = (extent.height > 0U) ? static_cast<float>(extent.width) / static_cast<float>(extent.height) : 1.0f;
43
44 const PushConstants push_constants{elapsed_seconds, aspect};
45 vkCmdPushConstants(cmd, pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstants), &push_constants);
46
47 vkCmdDraw(cmd, 3, 1, 0, 0);
48 }
49
50 private:
51 std::string shader_root;
52 std::chrono::steady_clock::time_point start_time{std::chrono::steady_clock::now()};
53
54 void destroyGraphicsPipeline() {
55 if (device == VK_NULL_HANDLE) {
56 pipeline_layout = VK_NULL_HANDLE;
57 graphics_pipeline = VK_NULL_HANDLE;
58 return;
59 }
60
61 if (graphics_pipeline != VK_NULL_HANDLE) {
62 vkDestroyPipeline(device, graphics_pipeline, nullptr);
63 graphics_pipeline = VK_NULL_HANDLE;
64 }
65 if (pipeline_layout != VK_NULL_HANDLE) {
66 vkDestroyPipelineLayout(device, pipeline_layout, nullptr);
67 pipeline_layout = VK_NULL_HANDLE;
68 }
69 }
70
71 bool ensureGraphicsPipeline() {
72 if (graphics_pipeline != VK_NULL_HANDLE && pipeline_layout != VK_NULL_HANDLE) {
73 return true;
74 }
75
76 createGraphicsPipeline();
77 return graphics_pipeline != VK_NULL_HANDLE && pipeline_layout != VK_NULL_HANDLE;
78 }
79
80 void createGraphicsPipeline() {
81 if (device == VK_NULL_HANDLE || swapchain_format == VK_FORMAT_UNDEFINED) {
82 return;
83 }
84
85 const std::string vert_path = shader_root + "/triangle.vert.spv";
86 const std::string frag_path = shader_root + "/triangle.frag.spv";
87 const std::vector<char> vert_bytes = loadSpv(vert_path);
88 const std::vector<char> frag_bytes = loadSpv(frag_path);
89
90 const VkShaderModule vert_module = createShaderModule(device, vert_bytes);
91 VkShaderModule frag_module = VK_NULL_HANDLE;
92 try {
93 frag_module = createShaderModule(device, frag_bytes);
94
95 std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
96 shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
97 shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
98 shader_stages[0].module = vert_module;
99 shader_stages[0].pName = "main";
100 shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
101 shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
102 shader_stages[1].module = frag_module;
103 shader_stages[1].pName = "main";
104
105 VkPipelineVertexInputStateCreateInfo vertex_input{};
106 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
107
108 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
109 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
110 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
111 input_assembly.primitiveRestartEnable = VK_FALSE;
112
113 VkPipelineViewportStateCreateInfo viewport_state{};
114 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
115 viewport_state.viewportCount = 1;
116 viewport_state.scissorCount = 1;
117
118 const VkDynamicState dynamic_states[] = {
119 VK_DYNAMIC_STATE_VIEWPORT,
120 VK_DYNAMIC_STATE_SCISSOR,
121 };
122 VkPipelineDynamicStateCreateInfo dynamic_state{};
123 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
124 dynamic_state.dynamicStateCount = 2;
125 dynamic_state.pDynamicStates = dynamic_states;
126
127 VkPipelineRasterizationStateCreateInfo rasterizer{};
128 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
129 rasterizer.depthClampEnable = VK_FALSE;
130 rasterizer.rasterizerDiscardEnable = VK_FALSE;
131 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
132 rasterizer.lineWidth = 1.0f;
133 rasterizer.cullMode = VK_CULL_MODE_NONE;
134 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
135 rasterizer.depthBiasEnable = VK_FALSE;
136
137 VkPipelineMultisampleStateCreateInfo multisampling{};
138 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
139 multisampling.sampleShadingEnable = VK_FALSE;
140 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
141
142 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
143 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
144 depth_stencil.depthTestEnable = VK_FALSE;
145 depth_stencil.depthWriteEnable = VK_FALSE;
146 depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
147 depth_stencil.depthBoundsTestEnable = VK_FALSE;
148 depth_stencil.stencilTestEnable = VK_FALSE;
149
150 VkPipelineColorBlendAttachmentState color_blend_attachment{};
151 color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
152 color_blend_attachment.blendEnable = VK_FALSE;
153
154 VkPipelineColorBlendStateCreateInfo color_blending{};
155 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
156 color_blending.logicOpEnable = VK_FALSE;
157 color_blending.attachmentCount = 1;
158 color_blending.pAttachments = &color_blend_attachment;
159
160 VkPushConstantRange push_constant_range{};
161 push_constant_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
162 push_constant_range.offset = 0;
163 push_constant_range.size = sizeof(PushConstants);
164
165 VkPipelineLayoutCreateInfo pipeline_layout_info{};
166 pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
167 pipeline_layout_info.pushConstantRangeCount = 1;
168 pipeline_layout_info.pPushConstantRanges = &push_constant_range;
169 if (vkCreatePipelineLayout(device, &pipeline_layout_info, nullptr, &pipeline_layout) != VK_SUCCESS) {
170 throw mxvk::Exception("Failed to create pipeline layout");
171 }
172
173 VkPipelineRenderingCreateInfo pipeline_rendering_info{};
174 pipeline_rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
175 pipeline_rendering_info.viewMask = 0;
176 pipeline_rendering_info.colorAttachmentCount = 1;
177 pipeline_rendering_info.pColorAttachmentFormats = &swapchain_format;
178 const VkFormat depth_format = getDepthFormat();
179 if (depth_format != VK_FORMAT_UNDEFINED) {
180 pipeline_rendering_info.depthAttachmentFormat = depth_format;
181 }
182
183 VkGraphicsPipelineCreateInfo pipeline_info{};
184 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
185 pipeline_info.pNext = &pipeline_rendering_info;
186 pipeline_info.stageCount = static_cast<uint32_t>(shader_stages.size());
187 pipeline_info.pStages = shader_stages.data();
188 pipeline_info.pVertexInputState = &vertex_input;
189 pipeline_info.pInputAssemblyState = &input_assembly;
190 pipeline_info.pViewportState = &viewport_state;
191 pipeline_info.pRasterizationState = &rasterizer;
192 pipeline_info.pMultisampleState = &multisampling;
193 pipeline_info.pDepthStencilState = &depth_stencil;
194 pipeline_info.pColorBlendState = &color_blending;
195 pipeline_info.pDynamicState = &dynamic_state;
196 pipeline_info.layout = pipeline_layout;
197 pipeline_info.renderPass = VK_NULL_HANDLE;
198 pipeline_info.subpass = 0;
199 pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
200 pipeline_info.basePipelineIndex = -1;
201
202 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &graphics_pipeline) != VK_SUCCESS) {
203 throw mxvk::Exception("Failed to create graphics pipeline");
204 }
205 } catch (...) {
206 if (graphics_pipeline != VK_NULL_HANDLE) {
207 vkDestroyPipeline(device, graphics_pipeline, nullptr);
208 graphics_pipeline = VK_NULL_HANDLE;
209 }
210 if (pipeline_layout != VK_NULL_HANDLE) {
211 vkDestroyPipelineLayout(device, pipeline_layout, nullptr);
212 pipeline_layout = VK_NULL_HANDLE;
213 }
214 if (frag_module != VK_NULL_HANDLE) {
215 vkDestroyShaderModule(device, frag_module, nullptr);
216 }
217 vkDestroyShaderModule(device, vert_module, nullptr);
218 throw;
219 }
220
221 vkDestroyShaderModule(device, frag_module, nullptr);
222 vkDestroyShaderModule(device, vert_module, nullptr);
223 }
224
225 VkPipeline graphics_pipeline = VK_NULL_HANDLE;
226 VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
227 };
228} // namespace example
229
230int main(int argc, char **argv) {
231 try {
232 const Arguments args = proc_args(argc, argv);
233 example::ExampleWindow ex_window(args.path, "VK_Example", args.width, args.height, args.fullscreen, args.enable_vsync);
234 ex_window.loop();
235 } catch (mxvk::Exception &e) {
236 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
237 return EXIT_FAILURE;
238 } catch (ArgException<std::string> &e) {
239 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
240 }
241 return EXIT_SUCCESS;
242}
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Definition argz.hpp:854
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:169
void onSwapchainAboutToRecreate() override
Called right before swapchain-dependent resources are recreated.
Definition main.cpp:30
ExampleWindow(const std::string path, const std::string &text, int width, int height, bool fullscreen, bool enable_vsync)
Definition main.cpp:21
~ExampleWindow() override
Definition main.cpp:23
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override
Optional hook for derived classes to record extra draw commands.
Definition main.cpp:32
std::string text() const
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:39
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:222
void loop()
Run the main event/render loop.
Definition mxvk.cpp:651
VkDevice device
Definition mxvk.hpp:606
VkFormat swapchain_format
Definition mxvk.hpp:614
VkFormat depth_format
Definition mxvk.hpp:615
static VkShaderModule createShaderModule(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
Definition mxvk.cpp:154
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
Definition mxvk.cpp:637
VK_Window()=default
Construct an empty window object.
VkFormat getDepthFormat() const noexcept
Get the depth format used for dynamic rendering attachments.
Definition mxvk.hpp:236
static std::vector< char > loadSpv(const std::string &path)
Load a SPIR-V file from disk.
Definition mxvk.cpp:152
#define MXVK_VALIDATION
Definition mxvk.hpp:28
int main()
Definition main.py:165
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:718
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:724
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:738
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:721
std::string path
Asset root; proc_args() defaults it to the executable directory.
Definition argz.hpp:723
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:720