MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
bluesky.cpp
Go to the documentation of this file.
1#include "mxvk/argz.hpp"
2#include "mxvk/mxvk.hpp"
5
6#include <SDL3/SDL.h>
7
8#include <algorithm>
9#include <array>
10#include <chrono>
11#include <cmath>
12#include <cstddef>
13#include <cstdint>
14#include <cstdlib>
15#include <cstring>
16#include <format>
17#include <iostream>
18#include <string>
19#include <vector>
20
21#include <glm/ext/matrix_clip_space.hpp>
22#include <glm/ext/matrix_transform.hpp>
23#include <glm/glm.hpp>
24
25namespace {
26 struct SceneVertex {
27 glm::vec3 position{};
28 glm::vec2 texCoord{};
29 glm::vec3 normal{0.0f, 1.0f, 0.0f};
30 glm::vec4 color{1.0f};
31 };
32
33 struct MeshData {
34 std::vector<SceneVertex> vertices;
35 std::vector<std::uint32_t> indices;
36 };
37
43
45 VkPipeline pipeline = VK_NULL_HANDLE;
46 VkPipelineLayout layout = VK_NULL_HANDLE;
47 };
48
50 alignas(16) glm::mat4 viewProjection{1.0f};
51 alignas(16) glm::vec4 cameraTime{0.0f};
52 alignas(16) glm::vec4 viewport{1.0f};
53 };
54
55 constexpr int WATER_GRID_RESOLUTION = 2048;
56 constexpr float WATER_SIZE = 320.0f;
57 constexpr float SCENE_REFERENCE_ASPECT = 16.0f / 9.0f;
58
59 void check_vk(VkResult result, const std::string &message) {
60 if (result != VK_SUCCESS) {
61 throw mxvk::Exception(message);
62 }
63 }
64
65} // namespace
66
67namespace example {
69 public:
70 WaterWindow(const std::string &path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync) : mxvk::VK_Window(title, width, height, fullscreen, MXVK_VALIDATION, enable_vsync), shader_root((path.empty() ? std::string(WATER_ASSET_DIR) : path) + "/data") { setClearColor(0.60f, 0.78f, 0.96f, 1.0f); }
71
72 ~WaterWindow() override {
73 if (device != VK_NULL_HANDLE) {
74 vkDeviceWaitIdle(device);
75 }
76 destroyPipeline(sky_pipeline);
77 destroyPipeline(water_pipeline);
78 destroyMesh(water_mesh);
79 }
80
81 void event(SDL_Event &e) override {
82 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
83 exit();
84 return;
85 }
86
87 if (e.type == SDL_EVENT_KEY_DOWN || e.type == SDL_EVENT_KEY_UP) {
88 const bool pressed = e.type == SDL_EVENT_KEY_DOWN;
89 switch (e.key.key) {
90 case SDLK_LEFT:
91 rotate_left = pressed;
92 return;
93 case SDLK_RIGHT:
94 rotate_right = pressed;
95 return;
96 case SDLK_UP:
97 case SDLK_PAGEUP:
98 zoom_in = pressed;
99 return;
100 case SDLK_DOWN:
101 case SDLK_PAGEDOWN:
102 zoom_out = pressed;
103 return;
104 default:
105 break;
106 }
107 }
108
109 if (e.type == SDL_EVENT_QUIT) {
110 exit();
111 }
112 }
113
115 destroyPipeline(sky_pipeline);
116 destroyPipeline(water_pipeline);
117 }
118
119 void onPrepareFrameRendering([[maybe_unused]] VkCommandBuffer cmd, [[maybe_unused]] uint32_t image_index) override {
120 if (water_mesh_uploaded) {
121 return;
122 }
123
124 uploadMesh(generateWaterMesh(), water_mesh);
125 water_mesh_uploaded = true;
126 }
127
128 void onRecordCustomRendering(VkCommandBuffer cmd, [[maybe_unused]] uint32_t image_index) override {
129 if (!water_mesh_uploaded || !ensurePipelines()) {
130 return;
131 }
132
133 const auto now = std::chrono::steady_clock::now();
134 const float delta_seconds = std::chrono::duration<float>(now - last_frame_time).count();
135 last_frame_time = now;
136 updateCamera(delta_seconds);
137
138 const float elapsed_seconds = std::chrono::duration<float>(now - start_time).count();
139 const VkExtent2D extent = getSwapchainExtent();
140 const float aspect = (extent.height > 0U) ? static_cast<float>(extent.width) / static_cast<float>(extent.height) : 1.0f;
141
142 const glm::vec3 camera_target(0.0f, -0.15f, -18.0f);
143 const float yaw = glm::radians(camera_yaw_degrees);
144 const float pitch = glm::radians(camera_pitch_degrees);
145 const glm::vec3 camera_offset(std::sin(yaw) * std::cos(pitch) * camera_distance, std::sin(pitch) * camera_distance, std::cos(yaw) * std::cos(pitch) * camera_distance);
146 const glm::vec3 camera_pos = camera_target + camera_offset;
147 const glm::mat4 view = glm::lookAt(camera_pos, camera_target, glm::vec3(0.0f, 1.0f, 0.0f));
148 glm::mat4 projection = glm::perspective(glm::radians(62.0f), aspect, 0.1f, 260.0f);
149 projection[1][1] *= -1.0f;
150
151 const PushConstants push_constants{
152 projection * view,
153 glm::vec4(camera_pos, elapsed_seconds),
154 glm::vec4(aspect, SCENE_REFERENCE_ASPECT, 0.0f, 0.0f),
155 };
156
157 drawSky(cmd, sky_pipeline, push_constants);
158 drawMesh(cmd, water_mesh, water_pipeline, push_constants);
159 }
160
161 private:
162 std::string shader_root;
163 std::chrono::steady_clock::time_point start_time{std::chrono::steady_clock::now()};
164 std::chrono::steady_clock::time_point last_frame_time{start_time};
165 MeshResources water_mesh;
166 PipelineResources water_pipeline;
167 PipelineResources sky_pipeline;
168 bool water_mesh_uploaded = false;
169 float camera_yaw_degrees = 0.0f;
170 float camera_pitch_degrees = 10.5f;
171 float camera_distance = 27.5f;
172 bool rotate_left = false;
173 bool rotate_right = false;
174 bool zoom_in = false;
175 bool zoom_out = false;
176
177 void updateCamera(float delta_seconds) {
178 constexpr float ROTATION_SPEED_DEGREES = 42.0f;
179 constexpr float ZOOM_SPEED = 14.0f;
180
181 if (rotate_left) {
182 camera_yaw_degrees -= ROTATION_SPEED_DEGREES * delta_seconds;
183 }
184 if (rotate_right) {
185 camera_yaw_degrees += ROTATION_SPEED_DEGREES * delta_seconds;
186 }
187 if (zoom_in) {
188 camera_distance -= ZOOM_SPEED * delta_seconds;
189 }
190 if (zoom_out) {
191 camera_distance += ZOOM_SPEED * delta_seconds;
192 }
193
194 camera_pitch_degrees = std::clamp(camera_pitch_degrees, -4.0f, 46.0f);
195 camera_distance = std::clamp(camera_distance, 10.0f, 72.0f);
196 }
197
198 static MeshData generateWaterMesh() {
199 MeshData mesh;
200 mesh.vertices.reserve(static_cast<std::size_t>(WATER_GRID_RESOLUTION + 1) * static_cast<std::size_t>(WATER_GRID_RESOLUTION + 1));
201 mesh.indices.reserve(static_cast<std::size_t>(WATER_GRID_RESOLUTION) * static_cast<std::size_t>(WATER_GRID_RESOLUTION) * 6U);
202
203 for (int z = 0; z <= WATER_GRID_RESOLUTION; ++z) {
204 const float vz = static_cast<float>(z) / static_cast<float>(WATER_GRID_RESOLUTION);
205 for (int x = 0; x <= WATER_GRID_RESOLUTION; ++x) {
206 const float vx = static_cast<float>(x) / static_cast<float>(WATER_GRID_RESOLUTION);
207 mesh.vertices.push_back({
208 glm::vec3((vx - 0.5f) * WATER_SIZE, 0.0f, (vz - 0.5f) * WATER_SIZE),
209 glm::vec2(vx * 72.0f, vz * 72.0f),
210 glm::vec3(0.0f, 1.0f, 0.0f),
211 glm::vec4(0.30f, 0.72f, 0.92f, 1.0f),
212 });
213 }
214 }
215
216 const auto vertex_index = [](int x, int z) { return static_cast<std::uint32_t>(z * (WATER_GRID_RESOLUTION + 1) + x); };
217 for (int z = 0; z < WATER_GRID_RESOLUTION; ++z) {
218 for (int x = 0; x < WATER_GRID_RESOLUTION; ++x) {
219 const std::uint32_t a = vertex_index(x, z);
220 const std::uint32_t b = vertex_index(x + 1, z);
221 const std::uint32_t c = vertex_index(x, z + 1);
222 const std::uint32_t d = vertex_index(x + 1, z + 1);
223 mesh.indices.insert(mesh.indices.end(), {a, c, b, b, c, d});
224 }
225 }
226
227 return mesh;
228 }
229
230 void uploadMesh(const MeshData &data, MeshResources &mesh) const {
231 const mxvk::VulkanContext context{
232 device,
236 };
237 mesh.indexCount = static_cast<uint32_t>(data.indices.size());
238
239 const VkDeviceSize vertex_size = sizeof(SceneVertex) * data.vertices.size();
240 uploadDeviceBuffer(context, data.vertices.data(), vertex_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, mesh.vertexBuffer);
241
242 const VkDeviceSize index_size = sizeof(std::uint32_t) * data.indices.size();
243 uploadDeviceBuffer(context, data.indices.data(), index_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, mesh.indexBuffer);
244 }
245
246 void drawMesh(VkCommandBuffer cmd, const MeshResources &mesh, const PipelineResources &pipeline, const PushConstants &push_constants) const {
247 if (mesh.vertexBuffer.buffer == VK_NULL_HANDLE || mesh.indexBuffer.buffer == VK_NULL_HANDLE || pipeline.pipeline == VK_NULL_HANDLE) {
248 return;
249 }
250
251 const VkDeviceSize offsets[] = {0};
252 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline);
253 vkCmdBindVertexBuffers(cmd, 0, 1, &mesh.vertexBuffer.buffer, offsets);
254 vkCmdBindIndexBuffer(cmd, mesh.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
255 vkCmdPushConstants(cmd, pipeline.layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &push_constants);
256 vkCmdDrawIndexed(cmd, mesh.indexCount, 1, 0, 0, 0);
257 }
258
259 void drawSky(VkCommandBuffer cmd, const PipelineResources &pipeline, const PushConstants &push_constants) const {
260 if (pipeline.pipeline == VK_NULL_HANDLE) {
261 return;
262 }
263
264 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline);
265 vkCmdPushConstants(cmd, pipeline.layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &push_constants);
266 vkCmdDraw(cmd, 3, 1, 0, 0);
267 }
268
269 void uploadDeviceBuffer(const mxvk::VulkanContext &context, const void *data, VkDeviceSize size, VkBufferUsageFlags usage, mxvk::BufferResource &buffer) const {
270 mxvk::BufferResource staging;
271 mxvk::create_buffer(context, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, staging);
272
273 try {
274 mxvk::map_buffer(device, staging);
275 std::memcpy(staging.mapped, data, static_cast<std::size_t>(size));
276 mxvk::unmap_buffer(device, staging);
277
278 mxvk::create_buffer(context, size, usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, buffer);
279 mxvk::copy_buffer(context, staging.buffer, buffer.buffer, size);
280 } catch (...) {
282 throw;
283 }
284
286 }
287
288 void destroyMesh(MeshResources &mesh) const {
289 mxvk::destroy_buffer(device, mesh.vertexBuffer);
290 mxvk::destroy_buffer(device, mesh.indexBuffer);
291 mesh.indexCount = 0;
292 }
293
294 bool ensurePipelines() {
295 if (sky_pipeline.pipeline == VK_NULL_HANDLE) {
296 createPipeline("sky.vert.spv", "sky.frag.spv", false, false, false, sky_pipeline);
297 }
298 if (water_pipeline.pipeline == VK_NULL_HANDLE) {
299 createPipeline("water.vert.spv", "water.frag.spv", true, false, true, water_pipeline);
300 }
301 return sky_pipeline.pipeline != VK_NULL_HANDLE && water_pipeline.pipeline != VK_NULL_HANDLE;
302 }
303
304 void createPipeline(const std::string &vertex_shader, const std::string &fragment_shader, bool useVertexInput, bool alphaBlend, bool depthTest, PipelineResources &resources) {
305 if (device == VK_NULL_HANDLE || swapchain_format == VK_FORMAT_UNDEFINED) {
306 return;
307 }
308
309 const std::vector<char> vert_bytes = loadSpv(shader_root + "/" + vertex_shader);
310 const std::vector<char> frag_bytes = loadSpv(shader_root + "/" + fragment_shader);
311
312 const VkShaderModule vert_module = createShaderModule(device, vert_bytes);
313 VkShaderModule frag_module = VK_NULL_HANDLE;
314 try {
315 frag_module = createShaderModule(device, frag_bytes);
316
317 std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
318 shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
319 shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
320 shader_stages[0].module = vert_module;
321 shader_stages[0].pName = "main";
322 shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
323 shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
324 shader_stages[1].module = frag_module;
325 shader_stages[1].pName = "main";
326
327 VkVertexInputBindingDescription binding_description{};
328 std::array<VkVertexInputAttributeDescription, 4> attribute_descriptions{};
329 if (useVertexInput) {
330 binding_description = vertexBindingDescription();
331 attribute_descriptions = vertexAttributeDescriptions();
332 }
333
334 VkPipelineVertexInputStateCreateInfo vertex_input{};
335 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
336 vertex_input.vertexBindingDescriptionCount = useVertexInput ? 1U : 0U;
337 vertex_input.pVertexBindingDescriptions = useVertexInput ? &binding_description : nullptr;
338 vertex_input.vertexAttributeDescriptionCount = useVertexInput ? static_cast<uint32_t>(attribute_descriptions.size()) : 0U;
339 vertex_input.pVertexAttributeDescriptions = useVertexInput ? attribute_descriptions.data() : nullptr;
340
341 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
342 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
343 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
344
345 VkPipelineViewportStateCreateInfo viewport_state{};
346 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
347 viewport_state.viewportCount = 1;
348 viewport_state.scissorCount = 1;
349
350 const VkDynamicState dynamic_states[] = {
351 VK_DYNAMIC_STATE_VIEWPORT,
352 VK_DYNAMIC_STATE_SCISSOR,
353 };
354 VkPipelineDynamicStateCreateInfo dynamic_state{};
355 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
356 dynamic_state.dynamicStateCount = 2;
357 dynamic_state.pDynamicStates = dynamic_states;
358
359 VkPipelineRasterizationStateCreateInfo rasterizer{};
360 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
361 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
362 rasterizer.lineWidth = 1.0f;
363 rasterizer.cullMode = VK_CULL_MODE_NONE;
364 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
365
366 VkPipelineMultisampleStateCreateInfo multisampling{};
367 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
368 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
369
370 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
371 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
372 depth_stencil.depthTestEnable = depthTest ? VK_TRUE : VK_FALSE;
373 depth_stencil.depthWriteEnable = (depthTest && !alphaBlend) ? VK_TRUE : VK_FALSE;
374 depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
375
376 VkPipelineColorBlendAttachmentState color_blend_attachment{};
377 color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
378 color_blend_attachment.blendEnable = alphaBlend ? VK_TRUE : VK_FALSE;
379 color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
380 color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
381 color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
382 color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
383 color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
384 color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
385
386 VkPipelineColorBlendStateCreateInfo color_blending{};
387 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
388 color_blending.attachmentCount = 1;
389 color_blending.pAttachments = &color_blend_attachment;
390
391 VkPushConstantRange push_constant_range{};
392 push_constant_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
393 push_constant_range.size = sizeof(PushConstants);
394
395 VkPipelineLayoutCreateInfo pipeline_layout_info{};
396 pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
397 pipeline_layout_info.pushConstantRangeCount = 1;
398 pipeline_layout_info.pPushConstantRanges = &push_constant_range;
399 check_vk(vkCreatePipelineLayout(device, &pipeline_layout_info, nullptr, &resources.layout), "water: failed to create pipeline layout");
400
401 VkPipelineRenderingCreateInfo pipeline_rendering_info{};
402 pipeline_rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
403 pipeline_rendering_info.colorAttachmentCount = 1;
404 pipeline_rendering_info.pColorAttachmentFormats = &swapchain_format;
405 if (depth_format != VK_FORMAT_UNDEFINED) {
406 pipeline_rendering_info.depthAttachmentFormat = depth_format;
407 }
408
409 VkGraphicsPipelineCreateInfo pipeline_info{};
410 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
411 pipeline_info.pNext = &pipeline_rendering_info;
412 pipeline_info.stageCount = static_cast<uint32_t>(shader_stages.size());
413 pipeline_info.pStages = shader_stages.data();
414 pipeline_info.pVertexInputState = &vertex_input;
415 pipeline_info.pInputAssemblyState = &input_assembly;
416 pipeline_info.pViewportState = &viewport_state;
417 pipeline_info.pRasterizationState = &rasterizer;
418 pipeline_info.pMultisampleState = &multisampling;
419 pipeline_info.pDepthStencilState = &depth_stencil;
420 pipeline_info.pColorBlendState = &color_blending;
421 pipeline_info.pDynamicState = &dynamic_state;
422 pipeline_info.layout = resources.layout;
423 pipeline_info.renderPass = VK_NULL_HANDLE;
424
425 check_vk(vkCreateGraphicsPipelines(device, pipeline_cache, 1, &pipeline_info, nullptr, &resources.pipeline), "water: failed to create graphics pipeline");
426 } catch (...) {
427 destroyPipeline(resources);
428 if (frag_module != VK_NULL_HANDLE) {
429 vkDestroyShaderModule(device, frag_module, nullptr);
430 }
431 vkDestroyShaderModule(device, vert_module, nullptr);
432 throw;
433 }
434
435 vkDestroyShaderModule(device, frag_module, nullptr);
436 vkDestroyShaderModule(device, vert_module, nullptr);
437 }
438
439 static VkVertexInputBindingDescription vertexBindingDescription() {
440 VkVertexInputBindingDescription binding{};
441 binding.binding = 0;
442 binding.stride = sizeof(SceneVertex);
443 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
444 return binding;
445 }
446
447 static std::array<VkVertexInputAttributeDescription, 4> vertexAttributeDescriptions() {
448 std::array<VkVertexInputAttributeDescription, 4> attributes{};
449 attributes[0].binding = 0;
450 attributes[0].location = 0;
451 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
452 attributes[0].offset = offsetof(SceneVertex, position);
453 attributes[1].binding = 0;
454 attributes[1].location = 1;
455 attributes[1].format = VK_FORMAT_R32G32_SFLOAT;
456 attributes[1].offset = offsetof(SceneVertex, texCoord);
457 attributes[2].binding = 0;
458 attributes[2].location = 2;
459 attributes[2].format = VK_FORMAT_R32G32B32_SFLOAT;
460 attributes[2].offset = offsetof(SceneVertex, normal);
461 attributes[3].binding = 0;
462 attributes[3].location = 3;
463 attributes[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
464 attributes[3].offset = offsetof(SceneVertex, color);
465 return attributes;
466 }
467
468 void destroyPipeline(PipelineResources &resources) const {
469 if (device == VK_NULL_HANDLE) {
470 resources.pipeline = VK_NULL_HANDLE;
471 resources.layout = VK_NULL_HANDLE;
472 return;
473 }
474
475 if (resources.pipeline != VK_NULL_HANDLE) {
476 vkDestroyPipeline(device, resources.pipeline, nullptr);
477 resources.pipeline = VK_NULL_HANDLE;
478 }
479 if (resources.layout != VK_NULL_HANDLE) {
480 vkDestroyPipelineLayout(device, resources.layout, nullptr);
481 resources.layout = VK_NULL_HANDLE;
482 }
483 }
484 };
485} // namespace example
486
487int main(int argc, char **argv) {
488 try {
489 const Arguments args = proc_args(argc, argv);
490 example::WaterWindow window(args.path, "MXVK - Bluesky", args.width, args.height, args.fullscreen, args.enable_vsync);
491 window.loop();
492 } catch (mxvk::Exception &e) {
493 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
494 return EXIT_FAILURE;
495 } catch (ArgException<std::string> &e) {
496 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
497 return EXIT_FAILURE;
498 }
499 return EXIT_SUCCESS;
500}
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 onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override
Optional hook for derived classes to record extra draw commands.
Definition bluesky.cpp:128
void onSwapchainAboutToRecreate() override
Called right before swapchain-dependent resources are recreated.
Definition bluesky.cpp:114
void event(SDL_Event &e) override
Handle one SDL event.
Definition bluesky.cpp:81
WaterWindow(const std::string &path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync)
Definition bluesky.cpp:70
~WaterWindow() override
Definition bluesky.cpp:72
void onPrepareFrameRendering(VkCommandBuffer cmd, uint32_t image_index) override
Record resource transitions that must happen before dynamic rendering begins.
Definition bluesky.cpp:119
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
VulkanContext context() const
Definition mxvk.cpp:80
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
VkPipelineCache pipeline_cache
Definition mxvk.hpp:611
void exit()
Request loop termination.
Definition mxvk.cpp:1378
VkCommandPool command_pool
Definition mxvk.hpp:627
VK_Window()=default
Construct an empty window object.
VkPhysicalDevice physical_device
Definition mxvk.hpp:605
static std::vector< char > loadSpv(const std::string &path)
Load a SPIR-V file from disk.
Definition mxvk.cpp:152
VkQueue graphics_queue
Definition mxvk.hpp:609
#define MXVK_VALIDATION
Definition mxvk.hpp:28
Reusable Vulkan buffer, image, upload, and one-shot command helpers.
constexpr float SCENE_REFERENCE_ASPECT
Definition bluesky.cpp:57
constexpr int WATER_GRID_RESOLUTION
Definition bluesky.cpp:55
void check_vk(VkResult result, const std::string &message)
Definition bluesky.cpp:59
int main()
Definition main.py:165
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
void copy_buffer(const VulkanContext &context, VkBuffer source, VkBuffer destination, VkDeviceSize size)
Copy one buffer into another with a one-shot Vulkan 1.3 copy command.
void unmap_buffer(VkDevice device, BufferResource &buffer)
Unmap a BufferResource if it is currently mapped.
void map_buffer(VkDevice device, BufferResource &buffer)
Persistently map a host-visible buffer allocation.
void create_buffer(const VulkanContext &context, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, BufferResource &buffer)
Create and bind a Vulkan buffer allocation.
void destroy_buffer(VkDevice device, BufferResource &buffer)
Unmap and destroy a BufferResource.
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
std::vector< std::uint32_t > indices
Definition bluesky.cpp:35
std::vector< SceneVertex > vertices
Definition bluesky.cpp:34
Owned Vulkan buffer allocation with optional persistent host mapping.
VkBuffer buffer
Vulkan buffer handle.
void * mapped
Host pointer returned by vkMapMemory, or nullptr when unmapped.