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