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