MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
starship.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <array>
3#include <chrono>
4#include <cmath>
5#include <cstddef>
6#include <cstdlib>
7#include <cstring>
8#include <format>
9#include <iostream>
10#include <random>
11#include <string>
12#include <vector>
13
14#include <glm/ext/matrix_clip_space.hpp>
15#include <glm/ext/matrix_transform.hpp>
16#include <glm/glm.hpp>
17
18#include "mxvk/argz.hpp"
19#include "mxvk/mxvk.hpp"
22#include "mxvk/mxvk_png.hpp"
23
24namespace {
25
27 alignas(16) glm::mat4 model{1.0f};
28 alignas(16) glm::mat4 view{1.0f};
29 alignas(16) glm::mat4 proj{1.0f};
30 alignas(16) glm::vec4 params{0.0f};
31 alignas(16) glm::vec4 color{1.0f};
32 };
33
34 struct StarVertex {
35 float pos[3];
36 float size;
37 float color[4];
38 };
39
40 struct Star {
41 float x = 0.0f;
42 float y = 0.0f;
43 float z = 0.0f;
44 float vx = 0.0f;
45 float vy = 0.0f;
46 float vz = 0.0f;
47 float magnitude = 0.0f;
48 float temperature = 0.0f;
49 float twinkle = 0.0f;
50 float size = 0.0f;
51 int starType = 0;
52 bool isConstellation = false;
53 };
54
55 struct FlameVertex {
56 glm::vec3 pos{};
57 glm::vec4 color{};
58 };
59
61 glm::mat4 mvp{1.0f};
62 glm::vec4 params{0.0f};
63 };
64
65 constexpr float PI = 3.14159265358979323846f;
66
67} // namespace
68
69namespace example {
70
72 public:
73 StarshipWindow(const std::string filename, 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), assetRoot((path.empty() || path == ".") ? std::string(STARSHIP_EXAMPLE_ASSET_DIR) : path), dataRoot(assetRoot + "/data") {
74 const std::string modelVertPath = dataRoot + "/model.vert.spv";
75 const std::string modelFragPath = dataRoot + "/model.frag.spv";
76
77 model.load(this, filename, "", "", 1.0f);
78 model.setShaders(this, modelVertPath, modelFragPath);
79
80 initStarfield(12000);
81 createStarResources();
82 createFlameResources();
83 }
84
85 ~StarshipWindow() override {
86 if (device != VK_NULL_HANDLE) {
87 vkDeviceWaitIdle(device);
88 }
89 cleanupFlameResources();
90 cleanupStarResources();
91 model.cleanup(this);
92 }
93
94 void event(SDL_Event &e) override {
95 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
96 exit();
97 return;
98 }
99
100 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
101 mouseDragging = true;
102 lastMouseX = static_cast<int>(e.button.x);
103 lastMouseY = static_cast<int>(e.button.y);
104 return;
105 }
106
107 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
108 mouseDragging = false;
109 return;
110 }
111
112 if (e.type == SDL_EVENT_MOUSE_MOTION && mouseDragging) {
113 const int x = static_cast<int>(e.motion.x);
114 const int y = static_cast<int>(e.motion.y);
115 const int deltaX = x - lastMouseX;
116 const int deltaY = y - lastMouseY;
117
118 mouseYawDegrees += static_cast<float>(deltaX) * mouseSensitivity;
119 mousePitchDegrees += static_cast<float>(deltaY) * mouseSensitivity;
120 mousePitchDegrees = std::clamp(mousePitchDegrees, -80.0f, 80.0f);
121
122 lastMouseX = x;
123 lastMouseY = y;
124 return;
125 }
126 }
127
128 void onSwapchainRecreated() override {
129 model.resize(this);
130 cleanupStarSwapchainResources();
131 cleanupFlameSwapchainResources();
132 createStarSwapchainResources();
133 createFlameSwapchainResources();
134 }
135
136 void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override {
137 const float elapsedSeconds = std::chrono::duration<float>(std::chrono::steady_clock::now() - start).count();
138 const VkExtent2D extent = getSwapchainExtent();
139 const float aspect = (extent.height > 0U) ? static_cast<float>(extent.width) / static_cast<float>(extent.height) : 1.0f;
140
141 drawStarfield(cmd, imageIndex, extent, elapsedSeconds);
142
144 ubo.model = glm::mat4(1.0f);
145 ubo.model = glm::rotate(ubo.model, glm::radians(mousePitchDegrees), glm::vec3(1.0f, 0.0f, 0.0f));
146 ubo.model = glm::rotate(ubo.model, elapsedSeconds * autoSpinSpeed + glm::radians(mouseYawDegrees), glm::vec3(0.0f, 1.0f, 0.0f));
147 ubo.model = glm::scale(ubo.model, glm::vec3(model.modelRenderScale()));
148 ubo.model = glm::translate(ubo.model, model.modelCenterOffset());
149 ubo.view = glm::lookAt(glm::vec3(0.0f, 0.0f, 4.2f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
150 ubo.proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
151 ubo.proj[1][1] *= -1.0f;
152
153 model.updateUBO(imageIndex, ubo);
154 model.render(cmd, imageIndex, false);
155 drawEngineFlame(cmd, extent, elapsedSeconds, ubo.model, ubo.view, ubo.proj);
156 }
157
158 private:
159 void initStarfield(int numStarsParam) {
160 if (starfieldInitialized) {
161 return;
162 }
163
164 numStars = numStarsParam;
165 stars.resize(static_cast<std::size_t>(numStars));
166
167 for (int i = 0; i < numStars; ++i) {
168 respawnStar(stars[static_cast<std::size_t>(i)]);
169 }
170
171 starfieldInitialized = true;
172 }
173
174 void respawnStar(Star &star) {
175 const float theta = randomFloat(0.0f, 2.0f * PI);
176 const float phi = std::acos(randomFloat(-1.0f, 1.0f));
177 const float radius = randomFloat(50.0f, 200.0f);
178
179 star.x = radius * std::sin(phi) * std::cos(theta);
180 star.y = radius * std::sin(phi) * std::sin(theta);
181 star.z = radius * std::cos(phi);
182
183 star.vx = randomFloat(-0.060f, 0.060f);
184 star.vy = randomFloat(-0.060f, 0.060f);
185 star.vz = randomFloat(-0.060f, 0.060f);
186
187 const float r = randomFloat(0.0f, 1.0f);
188 if (r < 0.05f) {
189 star.magnitude = randomFloat(-1.0f, 2.0f);
190 star.starType = 1;
191 } else if (r < 0.3f) {
192 star.magnitude = randomFloat(2.0f, 4.0f);
193 star.starType = 0;
194 } else {
195 star.magnitude = randomFloat(4.0f, 6.5f);
196 star.starType = 2;
197 }
198
199 if (star.starType == 1) {
200 star.temperature = randomFloat(3000.0f, 5000.0f);
201 } else if (star.starType == 0) {
202 star.temperature = randomFloat(4000.0f, 8000.0f);
203 } else {
204 star.temperature = randomFloat(2500.0f, 4000.0f);
205 }
206
207 star.twinkle = randomFloat(0.5f, 3.0f);
208 star.size = magnitudeToSize(star.magnitude);
209 star.isConstellation = (star.magnitude < 3.0f) && (randomFloat(0.0f, 1.0f) < 0.3f);
210 }
211
212 void createStarResources() {
213 createStarTexture();
214 createStarVertexBuffer();
215 createStarSwapchainResources();
216 }
217
218 void cleanupStarResources() {
219 cleanupStarSwapchainResources();
220
221 if (starVertexBufferMapped != nullptr && starVertexBufferMemory != VK_NULL_HANDLE) {
222 vkUnmapMemory(device, starVertexBufferMemory);
223 starVertexBufferMapped = nullptr;
224 }
225 if (starVertexBuffer != VK_NULL_HANDLE) {
226 vkDestroyBuffer(device, starVertexBuffer, nullptr);
227 starVertexBuffer = VK_NULL_HANDLE;
228 }
229 if (starVertexBufferMemory != VK_NULL_HANDLE) {
230 vkFreeMemory(device, starVertexBufferMemory, nullptr);
231 starVertexBufferMemory = VK_NULL_HANDLE;
232 }
233
234 if (starSampler != VK_NULL_HANDLE) {
235 vkDestroySampler(device, starSampler, nullptr);
236 starSampler = VK_NULL_HANDLE;
237 }
238 if (starTextureView != VK_NULL_HANDLE) {
239 vkDestroyImageView(device, starTextureView, nullptr);
240 starTextureView = VK_NULL_HANDLE;
241 }
242 if (starTexture != VK_NULL_HANDLE) {
243 vkDestroyImage(device, starTexture, nullptr);
244 starTexture = VK_NULL_HANDLE;
245 }
246 if (starTextureMemory != VK_NULL_HANDLE) {
247 vkFreeMemory(device, starTextureMemory, nullptr);
248 starTextureMemory = VK_NULL_HANDLE;
249 }
250 }
251
252 void cleanupStarSwapchainResources() {
253 if (starPipeline != VK_NULL_HANDLE) {
254 vkDestroyPipeline(device, starPipeline, nullptr);
255 starPipeline = VK_NULL_HANDLE;
256 }
257 if (starPipelineLayout != VK_NULL_HANDLE) {
258 vkDestroyPipelineLayout(device, starPipelineLayout, nullptr);
259 starPipelineLayout = VK_NULL_HANDLE;
260 }
261 if (starDescriptorPool != VK_NULL_HANDLE) {
262 vkDestroyDescriptorPool(device, starDescriptorPool, nullptr);
263 starDescriptorPool = VK_NULL_HANDLE;
264 }
265 if (starDescriptorSetLayout != VK_NULL_HANDLE) {
266 vkDestroyDescriptorSetLayout(device, starDescriptorSetLayout, nullptr);
267 starDescriptorSetLayout = VK_NULL_HANDLE;
268 }
269
270 destroyStarUniformBuffers();
271 starDescriptorSets.clear();
272 }
273
274 void createStarSwapchainResources() {
275 if (!starfieldInitialized || device == VK_NULL_HANDLE) {
276 return;
277 }
278
279 createStarDescriptorSetLayout();
280 createStarUniformBuffers();
281 createStarDescriptorPool();
282 createStarDescriptorSets();
283 createStarPipeline();
284 }
285
286 void createStarTexture() {
287 const std::string starTexturePath = dataRoot + "/star.png";
288 SDL_Surface *starImg = mxvk::LoadPNG(starTexturePath.c_str());
289 if (starImg == nullptr) {
290 throw mxvk::Exception("Failed to load star.png texture");
291 }
292
293 const VkDeviceSize imageSize = static_cast<VkDeviceSize>(starImg->w) * static_cast<VkDeviceSize>(starImg->h) * 4U;
294
295 VkBuffer stagingBuffer = VK_NULL_HANDLE;
296 VkDeviceMemory stagingBufferMemory = VK_NULL_HANDLE;
297 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
298
299 void *data = nullptr;
300 if (vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data) != VK_SUCCESS || data == nullptr) {
301 vkDestroyBuffer(device, stagingBuffer, nullptr);
302 vkFreeMemory(device, stagingBufferMemory, nullptr);
303 SDL_DestroySurface(starImg);
304 throw mxvk::Exception("Failed to map star texture staging buffer");
305 }
306 std::memcpy(data, starImg->pixels, static_cast<std::size_t>(imageSize));
307 vkUnmapMemory(device, stagingBufferMemory);
308
309 createImage(static_cast<uint32_t>(starImg->w), static_cast<uint32_t>(starImg->h), VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, starTexture, starTextureMemory);
310
311 transitionImageLayout(starTexture, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
312 copyBufferToImage(stagingBuffer, starTexture, static_cast<uint32_t>(starImg->w), static_cast<uint32_t>(starImg->h));
313 transitionImageLayout(starTexture, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
314
315 vkDestroyBuffer(device, stagingBuffer, nullptr);
316 vkFreeMemory(device, stagingBufferMemory, nullptr);
317
318 starTextureView = createImageView(starTexture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
319
320 VkSamplerCreateInfo samplerInfo{};
321 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
322 samplerInfo.magFilter = VK_FILTER_LINEAR;
323 samplerInfo.minFilter = VK_FILTER_LINEAR;
324 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
325 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
326 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
327 samplerInfo.anisotropyEnable = VK_FALSE;
328 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
329 samplerInfo.unnormalizedCoordinates = VK_FALSE;
330 samplerInfo.compareEnable = VK_FALSE;
331 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
332 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
333 if (vkCreateSampler(device, &samplerInfo, nullptr, &starSampler) != VK_SUCCESS) {
334 SDL_DestroySurface(starImg);
335 throw mxvk::Exception("Failed to create star texture sampler");
336 }
337
338 SDL_DestroySurface(starImg);
339 }
340
341 void createStarVertexBuffer() {
342 const VkDeviceSize bufferSize = sizeof(StarVertex) * static_cast<VkDeviceSize>(numStars);
343 createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, starVertexBuffer, starVertexBufferMemory);
344
345 if (vkMapMemory(device, starVertexBufferMemory, 0, bufferSize, 0, &starVertexBufferMapped) != VK_SUCCESS || starVertexBufferMapped == nullptr) {
346 throw mxvk::Exception("Failed to map star vertex buffer");
347 }
348 }
349
350 void createStarDescriptorSetLayout() {
351 VkDescriptorSetLayoutBinding samplerBinding{};
352 samplerBinding.binding = 0;
353 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
354 samplerBinding.descriptorCount = 1;
355 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
356
357 VkDescriptorSetLayoutBinding uboBinding{};
358 uboBinding.binding = 1;
359 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
360 uboBinding.descriptorCount = 1;
361 uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
362
363 std::array<VkDescriptorSetLayoutBinding, 2> bindings = {samplerBinding, uboBinding};
364 VkDescriptorSetLayoutCreateInfo layoutInfo{};
365 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
366 layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
367 layoutInfo.pBindings = bindings.data();
368
369 if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &starDescriptorSetLayout) != VK_SUCCESS) {
370 throw mxvk::Exception("Failed to create star descriptor set layout");
371 }
372 }
373
374 void createStarUniformBuffers() {
375 const size_t imageCount = getSwapchainImageCount();
376 const VkDeviceSize bufferSize = sizeof(StarUniformBufferObject);
377
378 starUniformBuffers.resize(imageCount, VK_NULL_HANDLE);
379 starUniformBufferMemories.resize(imageCount, VK_NULL_HANDLE);
380 starUniformBufferMapped.resize(imageCount, nullptr);
381
382 for (size_t i = 0; i < imageCount; ++i) {
383 createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, starUniformBuffers[i], starUniformBufferMemories[i]);
384
385 if (vkMapMemory(device, starUniformBufferMemories[i], 0, bufferSize, 0, &starUniformBufferMapped[i]) != VK_SUCCESS || starUniformBufferMapped[i] == nullptr) {
386 throw mxvk::Exception("Failed to map star uniform buffer");
387 }
388 }
389 }
390
391 void destroyStarUniformBuffers() {
392 for (size_t i = 0; i < starUniformBuffers.size(); ++i) {
393 if (starUniformBufferMapped[i] != nullptr && starUniformBufferMemories[i] != VK_NULL_HANDLE) {
394 vkUnmapMemory(device, starUniformBufferMemories[i]);
395 starUniformBufferMapped[i] = nullptr;
396 }
397 if (starUniformBuffers[i] != VK_NULL_HANDLE) {
398 vkDestroyBuffer(device, starUniformBuffers[i], nullptr);
399 starUniformBuffers[i] = VK_NULL_HANDLE;
400 }
401 if (starUniformBufferMemories[i] != VK_NULL_HANDLE) {
402 vkFreeMemory(device, starUniformBufferMemories[i], nullptr);
403 starUniformBufferMemories[i] = VK_NULL_HANDLE;
404 }
405 }
406
407 starUniformBuffers.clear();
408 starUniformBufferMemories.clear();
409 starUniformBufferMapped.clear();
410 }
411
412 void createStarDescriptorPool() {
413 const uint32_t imageCount = static_cast<uint32_t>(getSwapchainImageCount());
414
415 std::array<VkDescriptorPoolSize, 2> poolSizes{};
416 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
417 poolSizes[0].descriptorCount = imageCount;
418 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
419 poolSizes[1].descriptorCount = imageCount;
420
421 VkDescriptorPoolCreateInfo poolInfo{};
422 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
423 poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
424 poolInfo.pPoolSizes = poolSizes.data();
425 poolInfo.maxSets = imageCount;
426
427 if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &starDescriptorPool) != VK_SUCCESS) {
428 throw mxvk::Exception("Failed to create star descriptor pool");
429 }
430 }
431
432 void createStarDescriptorSets() {
433 const size_t imageCount = getSwapchainImageCount();
434 std::vector<VkDescriptorSetLayout> layouts(imageCount, starDescriptorSetLayout);
435
436 VkDescriptorSetAllocateInfo allocInfo{};
437 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
438 allocInfo.descriptorPool = starDescriptorPool;
439 allocInfo.descriptorSetCount = static_cast<uint32_t>(imageCount);
440 allocInfo.pSetLayouts = layouts.data();
441
442 starDescriptorSets.resize(imageCount, VK_NULL_HANDLE);
443 if (vkAllocateDescriptorSets(device, &allocInfo, starDescriptorSets.data()) != VK_SUCCESS) {
444 throw mxvk::Exception("Failed to allocate star descriptor sets");
445 }
446
447 for (size_t i = 0; i < imageCount; ++i) {
448 VkDescriptorImageInfo imageInfo{};
449 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
450 imageInfo.imageView = starTextureView;
451 imageInfo.sampler = starSampler;
452
453 VkDescriptorBufferInfo bufferInfo{};
454 bufferInfo.buffer = starUniformBuffers[i];
455 bufferInfo.offset = 0;
456 bufferInfo.range = sizeof(StarUniformBufferObject);
457
458 std::array<VkWriteDescriptorSet, 2> writes{};
459 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
460 writes[0].dstSet = starDescriptorSets[i];
461 writes[0].dstBinding = 0;
462 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
463 writes[0].descriptorCount = 1;
464 writes[0].pImageInfo = &imageInfo;
465
466 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
467 writes[1].dstSet = starDescriptorSets[i];
468 writes[1].dstBinding = 1;
469 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
470 writes[1].descriptorCount = 1;
471 writes[1].pBufferInfo = &bufferInfo;
472
473 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
474 }
475 }
476
477 void createStarPipeline() {
478 const std::vector<char> vertShaderCode = loadSpv(dataRoot + "/star.vert.spv");
479 const std::vector<char> fragShaderCode = loadSpv(dataRoot + "/star.frag.spv");
480
481 VkShaderModule vertShaderModule = createShaderModule(device, vertShaderCode);
482 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
483
484 try {
485 fragShaderModule = createShaderModule(device, fragShaderCode);
486
487 VkPipelineShaderStageCreateInfo vertStage{};
488 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
489 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
490 vertStage.module = vertShaderModule;
491 vertStage.pName = "main";
492
493 VkPipelineShaderStageCreateInfo fragStage{};
494 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
495 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
496 fragStage.module = fragShaderModule;
497 fragStage.pName = "main";
498
499 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
500
501 VkVertexInputBindingDescription bindingDescription{};
502 bindingDescription.binding = 0;
503 bindingDescription.stride = sizeof(StarVertex);
504 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
505
506 std::array<VkVertexInputAttributeDescription, 3> attributes{};
507 attributes[0].binding = 0;
508 attributes[0].location = 0;
509 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
510 attributes[0].offset = offsetof(StarVertex, pos);
511
512 attributes[1].binding = 0;
513 attributes[1].location = 1;
514 attributes[1].format = VK_FORMAT_R32_SFLOAT;
515 attributes[1].offset = offsetof(StarVertex, size);
516
517 attributes[2].binding = 0;
518 attributes[2].location = 2;
519 attributes[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
520 attributes[2].offset = offsetof(StarVertex, color);
521
522 VkPipelineVertexInputStateCreateInfo vertexInput{};
523 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
524 vertexInput.vertexBindingDescriptionCount = 1;
525 vertexInput.pVertexBindingDescriptions = &bindingDescription;
526 vertexInput.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributes.size());
527 vertexInput.pVertexAttributeDescriptions = attributes.data();
528
529 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
530 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
531 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
532 inputAssembly.primitiveRestartEnable = VK_FALSE;
533
534 VkPipelineViewportStateCreateInfo viewportState{};
535 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
536 viewportState.viewportCount = 1;
537 viewportState.scissorCount = 1;
538
539 std::array<VkDynamicState, 2> dynamicStates = {
540 VK_DYNAMIC_STATE_VIEWPORT,
541 VK_DYNAMIC_STATE_SCISSOR,
542 };
543 VkPipelineDynamicStateCreateInfo dynamicInfo{};
544 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
545 dynamicInfo.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
546 dynamicInfo.pDynamicStates = dynamicStates.data();
547
548 VkPipelineRasterizationStateCreateInfo rasterizer{};
549 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
550 rasterizer.depthClampEnable = VK_FALSE;
551 rasterizer.rasterizerDiscardEnable = VK_FALSE;
552 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
553 rasterizer.lineWidth = 1.0f;
554 rasterizer.cullMode = VK_CULL_MODE_NONE;
555 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
556 rasterizer.depthBiasEnable = VK_FALSE;
557
558 VkPipelineMultisampleStateCreateInfo multisampling{};
559 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
560 multisampling.sampleShadingEnable = VK_FALSE;
561 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
562
563 VkPipelineDepthStencilStateCreateInfo depthStencil{};
564 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
565 depthStencil.depthTestEnable = VK_FALSE;
566 depthStencil.depthWriteEnable = VK_FALSE;
567 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
568 depthStencil.depthBoundsTestEnable = VK_FALSE;
569 depthStencil.stencilTestEnable = VK_FALSE;
570
571 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
572 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
573 colorBlendAttachment.blendEnable = VK_TRUE;
574 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
575 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
576 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
577 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
578 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
579 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
580
581 VkPipelineColorBlendStateCreateInfo colorBlending{};
582 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
583 colorBlending.logicOpEnable = VK_FALSE;
584 colorBlending.attachmentCount = 1;
585 colorBlending.pAttachments = &colorBlendAttachment;
586
587 VkPipelineLayoutCreateInfo layoutInfo{};
588 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
589 layoutInfo.setLayoutCount = 1;
590 layoutInfo.pSetLayouts = &starDescriptorSetLayout;
591
592 if (vkCreatePipelineLayout(device, &layoutInfo, nullptr, &starPipelineLayout) != VK_SUCCESS) {
593 throw mxvk::Exception("Failed to create star pipeline layout");
594 }
595
596 const VkFormat colorFormat = getSwapchainFormat();
597 const VkFormat depthFormat = getDepthFormat();
598
599 VkPipelineRenderingCreateInfo renderingInfo{};
600 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
601 renderingInfo.colorAttachmentCount = 1;
602 renderingInfo.pColorAttachmentFormats = &colorFormat;
603 if (depthFormat != VK_FORMAT_UNDEFINED) {
604 renderingInfo.depthAttachmentFormat = depthFormat;
605 }
606 renderingInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
607
608 VkGraphicsPipelineCreateInfo pipelineInfo{};
609 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
610 pipelineInfo.pNext = &renderingInfo;
611 pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
612 pipelineInfo.pStages = shaderStages.data();
613 pipelineInfo.pVertexInputState = &vertexInput;
614 pipelineInfo.pInputAssemblyState = &inputAssembly;
615 pipelineInfo.pViewportState = &viewportState;
616 pipelineInfo.pRasterizationState = &rasterizer;
617 pipelineInfo.pMultisampleState = &multisampling;
618 pipelineInfo.pDepthStencilState = &depthStencil;
619 pipelineInfo.pColorBlendState = &colorBlending;
620 pipelineInfo.pDynamicState = &dynamicInfo;
621 pipelineInfo.layout = starPipelineLayout;
622 pipelineInfo.renderPass = VK_NULL_HANDLE;
623 pipelineInfo.subpass = 0;
624
625 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &starPipeline) != VK_SUCCESS) {
626 throw mxvk::Exception("Failed to create star pipeline");
627 }
628 } catch (...) {
629 if (fragShaderModule != VK_NULL_HANDLE) {
630 vkDestroyShaderModule(device, fragShaderModule, nullptr);
631 }
632 vkDestroyShaderModule(device, vertShaderModule, nullptr);
633 if (starPipeline != VK_NULL_HANDLE) {
634 vkDestroyPipeline(device, starPipeline, nullptr);
635 starPipeline = VK_NULL_HANDLE;
636 }
637 if (starPipelineLayout != VK_NULL_HANDLE) {
638 vkDestroyPipelineLayout(device, starPipelineLayout, nullptr);
639 starPipelineLayout = VK_NULL_HANDLE;
640 }
641 throw;
642 }
643
644 vkDestroyShaderModule(device, fragShaderModule, nullptr);
645 vkDestroyShaderModule(device, vertShaderModule, nullptr);
646 }
647
648 void updateStarfield(float deltaTime) {
649 if (!starfieldInitialized || starVertexBufferMapped == nullptr) {
650 return;
651 }
652
653 deltaTime = std::clamp(deltaTime, 0.0f, 0.1f) * 4.0f;
654 const float time = SDL_GetTicks() * 0.001f;
655 auto *vertices = static_cast<StarVertex *>(starVertexBufferMapped);
656
657 for (int i = 0; i < numStars; ++i) {
658 auto &star = stars[static_cast<std::size_t>(i)];
659
660 star.x += star.vx * deltaTime;
661 star.y += star.vy * deltaTime;
662 star.z += star.vz * deltaTime;
663
664 const float radiusSquared = star.x * star.x + star.y * star.y + star.z * star.z;
665 if (radiusSquared < 20.0f * 20.0f || radiusSquared > 260.0f * 260.0f) {
666 respawnStar(star);
667 }
668
669 vertices[i].pos[0] = star.x;
670 vertices[i].pos[1] = star.y;
671 vertices[i].pos[2] = star.z;
672
673 float twinkleFactor = 1.0f;
674 if (atmosphericTwinkle > 0.0f) {
675 twinkleFactor = 0.7f + 0.3f * std::sin(time * star.twinkle) * atmosphericTwinkle;
676 }
677
678 float size = star.size * twinkleFactor;
679 if (star.isConstellation) {
680 size *= 1.2f;
681 }
682 vertices[i].size = size;
683
684 const glm::vec3 starColor = getStarColor(star.temperature);
685 const float alpha = magnitudeToAlpha(star.magnitude) * twinkleFactor;
686
687 vertices[i].color[0] = starColor.r;
688 vertices[i].color[1] = starColor.g;
689 vertices[i].color[2] = starColor.b;
690 vertices[i].color[3] = alpha;
691 }
692 }
693
694 void updateStarUniform(uint32_t imageIndex, [[maybe_unused]] const VkExtent2D &extent, const glm::mat4 &view, const glm::mat4 &proj, float timeSeconds) {
695 if (imageIndex >= starUniformBufferMapped.size() || starUniformBufferMapped[imageIndex] == nullptr) {
696 return;
697 }
698
699 StarUniformBufferObject ubo{};
700 ubo.model = glm::mat4(1.0f);
701 ubo.view = view;
702 ubo.proj = proj;
703 ubo.params = glm::vec4(timeSeconds, 0.0f, 0.0f, 0.0f);
704 ubo.color = glm::vec4(1.0f);
705 std::memcpy(starUniformBufferMapped[imageIndex], &ubo, sizeof(ubo));
706 }
707
708 void drawStarfield(VkCommandBuffer cmd, uint32_t imageIndex, const VkExtent2D &extent, float elapsedSeconds) {
709 if (!starfieldInitialized || starPipeline == VK_NULL_HANDLE || imageIndex >= starDescriptorSets.size()) {
710 return;
711 }
712
713 const Uint32 currentTime = SDL_GetTicks();
714 const float deltaTime = static_cast<float>(currentTime - lastStarUpdateTime) / 1000.0f;
715 lastStarUpdateTime = currentTime;
716 updateStarfield(deltaTime);
717
718 const glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 4.2f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
719 glm::mat4 proj = glm::perspective(glm::radians(60.0f), static_cast<float>(extent.width) / static_cast<float>(extent.height), 0.1f, 1000.0f);
720 proj[1][1] *= -1.0f;
721
722 updateStarUniform(imageIndex, extent, view, proj, elapsedSeconds);
723
724 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, starPipeline);
725
726 VkViewport viewport{};
727 viewport.x = 0.0f;
728 viewport.y = 0.0f;
729 viewport.width = static_cast<float>(extent.width);
730 viewport.height = static_cast<float>(extent.height);
731 viewport.minDepth = 0.0f;
732 viewport.maxDepth = 1.0f;
733 vkCmdSetViewport(cmd, 0, 1, &viewport);
734
735 VkRect2D scissor{};
736 scissor.offset = {0, 0};
737 scissor.extent = extent;
738 vkCmdSetScissor(cmd, 0, 1, &scissor);
739
740 VkBuffer vertexBuffers[] = {starVertexBuffer};
741 VkDeviceSize offsets[] = {0};
742 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
743
744 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, starPipelineLayout, 0, 1, &starDescriptorSets[imageIndex], 0, nullptr);
745
746 vkCmdDraw(cmd, static_cast<uint32_t>(numStars), 1, 0, 0);
747 }
748
749 void createFlameResources() {
750 createFlameMesh();
751 createFlameSwapchainResources();
752 }
753
754 void cleanupFlameResources() {
755 cleanupFlameSwapchainResources();
756
757 if (flameVertexBuffer != VK_NULL_HANDLE) {
758 vkDestroyBuffer(device, flameVertexBuffer, nullptr);
759 flameVertexBuffer = VK_NULL_HANDLE;
760 }
761 if (flameVertexBufferMemory != VK_NULL_HANDLE) {
762 vkFreeMemory(device, flameVertexBufferMemory, nullptr);
763 flameVertexBufferMemory = VK_NULL_HANDLE;
764 }
765 }
766
767 void cleanupFlameSwapchainResources() {
768 if (flamePipeline != VK_NULL_HANDLE) {
769 vkDestroyPipeline(device, flamePipeline, nullptr);
770 flamePipeline = VK_NULL_HANDLE;
771 }
772 if (flamePipelineLayout != VK_NULL_HANDLE) {
773 vkDestroyPipelineLayout(device, flamePipelineLayout, nullptr);
774 flamePipelineLayout = VK_NULL_HANDLE;
775 }
776 }
777
778 void createFlameSwapchainResources() {
779 if (flameVertexCount == 0 || device == VK_NULL_HANDLE) {
780 return;
781 }
782
783 createFlamePipeline();
784 }
785
786 void createFlameMesh() {
787 constexpr int segments = 40;
788 constexpr float baseZ = 0.555f;
789 constexpr float tipZ = 1.02f;
790 constexpr float baseY = 0.040f;
791 constexpr float outerRadius = 0.052f;
792 constexpr float innerRadius = 0.026f;
793
794 std::vector<FlameVertex> vertices{};
795 vertices.reserve(static_cast<std::size_t>(segments) * 6U);
796
797 const glm::vec4 outerBaseColor{1.0f, 0.42f, 0.08f, 0.50f};
798 const glm::vec4 outerTipColor{0.7f, 0.08f, 0.0f, 0.0f};
799 const glm::vec4 innerBaseColor{1.0f, 0.92f, 0.45f, 0.72f};
800 const glm::vec4 innerTipColor{1.0f, 0.32f, 0.04f, 0.0f};
801
802 auto addCone = [&](float radius, const glm::vec4 &baseColor, const glm::vec4 &tipColor) {
803 const glm::vec3 tip{0.0f, baseY, tipZ};
804 for (int i = 0; i < segments; ++i) {
805 const float a0 = (static_cast<float>(i) / static_cast<float>(segments)) * 2.0f * PI;
806 const float a1 = (static_cast<float>(i + 1) / static_cast<float>(segments)) * 2.0f * PI;
807 const glm::vec3 p0{std::cos(a0) * radius, baseY + std::sin(a0) * radius, baseZ};
808 const glm::vec3 p1{std::cos(a1) * radius, baseY + std::sin(a1) * radius, baseZ};
809 vertices.push_back({p0, baseColor});
810 vertices.push_back({p1, baseColor});
811 vertices.push_back({tip, tipColor});
812 }
813 };
814
815 addCone(outerRadius, outerBaseColor, outerTipColor);
816 addCone(innerRadius, innerBaseColor, innerTipColor);
817
818 flameVertexCount = static_cast<uint32_t>(vertices.size());
819 const VkDeviceSize bufferSize = sizeof(FlameVertex) * static_cast<VkDeviceSize>(vertices.size());
820 createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, flameVertexBuffer, flameVertexBufferMemory);
821
822 void *data = nullptr;
823 if (vkMapMemory(device, flameVertexBufferMemory, 0, bufferSize, 0, &data) != VK_SUCCESS || data == nullptr) {
824 throw mxvk::Exception("Failed to map flame vertex buffer");
825 }
826 std::memcpy(data, vertices.data(), static_cast<std::size_t>(bufferSize));
827 vkUnmapMemory(device, flameVertexBufferMemory);
828 }
829
830 void createFlamePipeline() {
831 const std::vector<char> vertShaderCode = loadSpv(dataRoot + "/flame.vert.spv");
832 const std::vector<char> fragShaderCode = loadSpv(dataRoot + "/flame.frag.spv");
833
834 VkShaderModule vertShaderModule = createShaderModule(device, vertShaderCode);
835 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
836
837 try {
838 fragShaderModule = createShaderModule(device, fragShaderCode);
839
840 VkPipelineShaderStageCreateInfo vertStage{};
841 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
842 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
843 vertStage.module = vertShaderModule;
844 vertStage.pName = "main";
845
846 VkPipelineShaderStageCreateInfo fragStage{};
847 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
848 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
849 fragStage.module = fragShaderModule;
850 fragStage.pName = "main";
851
852 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
853
854 VkVertexInputBindingDescription bindingDescription{};
855 bindingDescription.binding = 0;
856 bindingDescription.stride = sizeof(FlameVertex);
857 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
858
859 std::array<VkVertexInputAttributeDescription, 2> attributes{};
860 attributes[0].binding = 0;
861 attributes[0].location = 0;
862 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
863 attributes[0].offset = offsetof(FlameVertex, pos);
864 attributes[1].binding = 0;
865 attributes[1].location = 1;
866 attributes[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
867 attributes[1].offset = offsetof(FlameVertex, color);
868
869 VkPipelineVertexInputStateCreateInfo vertexInput{};
870 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
871 vertexInput.vertexBindingDescriptionCount = 1;
872 vertexInput.pVertexBindingDescriptions = &bindingDescription;
873 vertexInput.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributes.size());
874 vertexInput.pVertexAttributeDescriptions = attributes.data();
875
876 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
877 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
878 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
879 inputAssembly.primitiveRestartEnable = VK_FALSE;
880
881 VkPipelineViewportStateCreateInfo viewportState{};
882 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
883 viewportState.viewportCount = 1;
884 viewportState.scissorCount = 1;
885
886 const std::array<VkDynamicState, 2> dynamicStates = {
887 VK_DYNAMIC_STATE_VIEWPORT,
888 VK_DYNAMIC_STATE_SCISSOR,
889 };
890 VkPipelineDynamicStateCreateInfo dynamicInfo{};
891 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
892 dynamicInfo.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
893 dynamicInfo.pDynamicStates = dynamicStates.data();
894
895 VkPipelineRasterizationStateCreateInfo rasterizer{};
896 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
897 rasterizer.depthClampEnable = VK_FALSE;
898 rasterizer.rasterizerDiscardEnable = VK_FALSE;
899 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
900 rasterizer.lineWidth = 1.0f;
901 rasterizer.cullMode = VK_CULL_MODE_NONE;
902 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
903 rasterizer.depthBiasEnable = VK_FALSE;
904
905 VkPipelineMultisampleStateCreateInfo multisampling{};
906 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
907 multisampling.sampleShadingEnable = VK_FALSE;
908 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
909
910 VkPipelineDepthStencilStateCreateInfo depthStencil{};
911 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
912 depthStencil.depthTestEnable = VK_TRUE;
913 depthStencil.depthWriteEnable = VK_FALSE;
914 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
915 depthStencil.depthBoundsTestEnable = VK_FALSE;
916 depthStencil.stencilTestEnable = VK_FALSE;
917
918 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
919 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
920 colorBlendAttachment.blendEnable = VK_TRUE;
921 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
922 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
923 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
924 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
925 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
926 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
927
928 VkPipelineColorBlendStateCreateInfo colorBlending{};
929 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
930 colorBlending.logicOpEnable = VK_FALSE;
931 colorBlending.attachmentCount = 1;
932 colorBlending.pAttachments = &colorBlendAttachment;
933
934 VkPushConstantRange pushRange{};
935 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
936 pushRange.offset = 0;
937 pushRange.size = sizeof(FlamePushConstants);
938
939 VkPipelineLayoutCreateInfo layoutInfo{};
940 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
941 layoutInfo.pushConstantRangeCount = 1;
942 layoutInfo.pPushConstantRanges = &pushRange;
943
944 if (vkCreatePipelineLayout(device, &layoutInfo, nullptr, &flamePipelineLayout) != VK_SUCCESS) {
945 throw mxvk::Exception("Failed to create flame pipeline layout");
946 }
947
948 const VkFormat colorFormat = getSwapchainFormat();
949 const VkFormat depthFormat = getDepthFormat();
950
951 VkPipelineRenderingCreateInfo renderingInfo{};
952 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
953 renderingInfo.colorAttachmentCount = 1;
954 renderingInfo.pColorAttachmentFormats = &colorFormat;
955 if (depthFormat != VK_FORMAT_UNDEFINED) {
956 renderingInfo.depthAttachmentFormat = depthFormat;
957 }
958
959 VkGraphicsPipelineCreateInfo pipelineInfo{};
960 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
961 pipelineInfo.pNext = &renderingInfo;
962 pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
963 pipelineInfo.pStages = shaderStages.data();
964 pipelineInfo.pVertexInputState = &vertexInput;
965 pipelineInfo.pInputAssemblyState = &inputAssembly;
966 pipelineInfo.pViewportState = &viewportState;
967 pipelineInfo.pRasterizationState = &rasterizer;
968 pipelineInfo.pMultisampleState = &multisampling;
969 pipelineInfo.pDepthStencilState = &depthStencil;
970 pipelineInfo.pColorBlendState = &colorBlending;
971 pipelineInfo.pDynamicState = &dynamicInfo;
972 pipelineInfo.layout = flamePipelineLayout;
973 pipelineInfo.renderPass = VK_NULL_HANDLE;
974 pipelineInfo.subpass = 0;
975
976 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &flamePipeline) != VK_SUCCESS) {
977 throw mxvk::Exception("Failed to create flame pipeline");
978 }
979 } catch (...) {
980 if (fragShaderModule != VK_NULL_HANDLE) {
981 vkDestroyShaderModule(device, fragShaderModule, nullptr);
982 }
983 vkDestroyShaderModule(device, vertShaderModule, nullptr);
984 cleanupFlameSwapchainResources();
985 throw;
986 }
987
988 vkDestroyShaderModule(device, fragShaderModule, nullptr);
989 vkDestroyShaderModule(device, vertShaderModule, nullptr);
990 }
991
992 void drawEngineFlame(VkCommandBuffer cmd, const VkExtent2D &extent, float elapsedSeconds, const glm::mat4 &modelMatrix, const glm::mat4 &view, const glm::mat4 &proj) {
993 if (flamePipeline == VK_NULL_HANDLE || flameVertexBuffer == VK_NULL_HANDLE || flameVertexCount == 0) {
994 return;
995 }
996
997 VkViewport viewport{};
998 viewport.x = 0.0f;
999 viewport.y = 0.0f;
1000 viewport.width = static_cast<float>(extent.width);
1001 viewport.height = static_cast<float>(extent.height);
1002 viewport.minDepth = 0.0f;
1003 viewport.maxDepth = 1.0f;
1004 vkCmdSetViewport(cmd, 0, 1, &viewport);
1005
1006 VkRect2D scissor{};
1007 scissor.offset = {0, 0};
1008 scissor.extent = extent;
1009 vkCmdSetScissor(cmd, 0, 1, &scissor);
1010
1011 FlamePushConstants pc{};
1012 pc.mvp = proj * view * modelMatrix;
1013 pc.params = glm::vec4(elapsedSeconds, 0.0f, 0.0f, 0.0f);
1014
1015 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, flamePipeline);
1016 vkCmdPushConstants(cmd, flamePipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pc), &pc);
1017
1018 VkBuffer vertexBuffers[] = {flameVertexBuffer};
1019 VkDeviceSize offsets[] = {0};
1020 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
1021 vkCmdDraw(cmd, flameVertexCount, 1, 0, 0);
1022 }
1023
1024 float randomFloat(float minv, float maxv) {
1025 static std::random_device rd;
1026 static std::default_random_engine eng(rd());
1027 std::uniform_real_distribution<float> dist(minv, maxv);
1028 return dist(eng);
1029 }
1030
1031 glm::vec3 getStarColor(float temperature) const {
1032 float r = 1.0f;
1033 float g = 1.0f;
1034 float b = 1.0f;
1035
1036 if (temperature < 3700.0f) {
1037 r = 1.0f;
1038 g = temperature / 3700.0f * 0.6f;
1039 b = 0.0f;
1040 } else if (temperature < 5200.0f) {
1041 r = 1.0f;
1042 g = 0.6f + (temperature - 3700.0f) / 1500.0f * 0.4f;
1043 b = (temperature - 3700.0f) / 1500.0f * 0.3f;
1044 } else if (temperature < 6000.0f) {
1045 r = 1.0f;
1046 g = 1.0f;
1047 b = (temperature - 5200.0f) / 800.0f * 0.7f;
1048 } else if (temperature < 7500.0f) {
1049 r = 1.0f;
1050 g = 1.0f;
1051 b = 0.7f + (temperature - 6000.0f) / 1500.0f * 0.3f;
1052 } else {
1053 r = 0.7f - (temperature - 7500.0f) / 10000.0f * 0.4f;
1054 g = 0.8f + (temperature - 7500.0f) / 10000.0f * 0.2f;
1055 b = 1.0f;
1056 }
1057
1058 return glm::vec3(r, g, b);
1059 }
1060
1061 float magnitudeToSize(float magnitude) const { return glm::clamp(6.5f - magnitude * 0.85f, 0.75f, 7.0f); }
1062
1063 float magnitudeToAlpha(float magnitude) const {
1064 const float alpha = (6.5f - magnitude) / 6.5f;
1065 return glm::clamp(alpha - lightPollution, 0.0f, 1.0f);
1066 }
1067
1068 void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory) const {
1069 VkBufferCreateInfo bufferInfo{};
1070 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1071 bufferInfo.size = size;
1072 bufferInfo.usage = usage;
1073 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1074
1075 if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
1076 throw mxvk::Exception("Failed to create buffer");
1077 }
1078
1079 VkMemoryRequirements memRequirements{};
1080 vkGetBufferMemoryRequirements(device, buffer, &memRequirements);
1081
1082 VkMemoryAllocateInfo allocInfo{};
1083 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1084 allocInfo.allocationSize = memRequirements.size;
1085
1086 try {
1087 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1088 if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) {
1089 throw mxvk::Exception("Failed to allocate buffer memory");
1090 }
1091 if (vkBindBufferMemory(device, buffer, bufferMemory, 0) != VK_SUCCESS) {
1092 throw mxvk::Exception("Failed to bind buffer memory");
1093 }
1094 } catch (...) {
1095 if (bufferMemory != VK_NULL_HANDLE) {
1096 vkFreeMemory(device, bufferMemory, nullptr);
1097 bufferMemory = VK_NULL_HANDLE;
1098 }
1099 if (buffer != VK_NULL_HANDLE) {
1100 vkDestroyBuffer(device, buffer, nullptr);
1101 buffer = VK_NULL_HANDLE;
1102 }
1103 throw;
1104 }
1105 }
1106
1107 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const {
1108 VkPhysicalDeviceMemoryProperties memProperties{};
1109 vkGetPhysicalDeviceMemoryProperties(physical_device, &memProperties);
1110
1111 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
1112 if ((typeFilter & (1U << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1113 return i;
1114 }
1115 }
1116
1117 throw mxvk::Exception("Failed to find suitable memory type");
1118 }
1119
1120 [[nodiscard]] VkCommandBuffer beginSingleTimeCommands() const {
1121 VkCommandBufferAllocateInfo allocInfo{};
1122 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1123 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1124 allocInfo.commandPool = command_pool;
1125 allocInfo.commandBufferCount = 1;
1126
1127 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
1128 if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) {
1129 throw mxvk::Exception("Failed to allocate command buffer");
1130 }
1131
1132 VkCommandBufferBeginInfo beginInfo{};
1133 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1134 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1135 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
1136 vkFreeCommandBuffers(device, command_pool, 1, &commandBuffer);
1137 throw mxvk::Exception("Failed to begin command buffer");
1138 }
1139
1140 return commandBuffer;
1141 }
1142
1143 void endSingleTimeCommands(VkCommandBuffer commandBuffer) const {
1144 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
1145 vkFreeCommandBuffers(device, command_pool, 1, &commandBuffer);
1146 throw mxvk::Exception("Failed to end command buffer");
1147 }
1148
1149 VkSubmitInfo submitInfo{};
1150 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1151 submitInfo.commandBufferCount = 1;
1152 submitInfo.pCommandBuffers = &commandBuffer;
1153
1154 if (vkQueueSubmit(graphics_queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
1155 vkFreeCommandBuffers(device, command_pool, 1, &commandBuffer);
1156 throw mxvk::Exception("Failed to submit command buffer");
1157 }
1158 if (vkQueueWaitIdle(graphics_queue) != VK_SUCCESS) {
1159 vkFreeCommandBuffers(device, command_pool, 1, &commandBuffer);
1160 throw mxvk::Exception("Failed to wait for graphics queue idle");
1161 }
1162
1163 vkFreeCommandBuffers(device, command_pool, 1, &commandBuffer);
1164 }
1165
1166 void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &memory) const {
1167 VkImageCreateInfo imageInfo{};
1168 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1169 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1170 imageInfo.extent.width = width;
1171 imageInfo.extent.height = height;
1172 imageInfo.extent.depth = 1;
1173 imageInfo.mipLevels = 1;
1174 imageInfo.arrayLayers = 1;
1175 imageInfo.format = format;
1176 imageInfo.tiling = tiling;
1177 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1178 imageInfo.usage = usage;
1179 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1180 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1181
1182 if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) {
1183 throw mxvk::Exception("Failed to create image");
1184 }
1185
1186 VkMemoryRequirements memRequirements{};
1187 vkGetImageMemoryRequirements(device, image, &memRequirements);
1188
1189 VkMemoryAllocateInfo allocInfo{};
1190 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1191 allocInfo.allocationSize = memRequirements.size;
1192
1193 try {
1194 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1195 if (vkAllocateMemory(device, &allocInfo, nullptr, &memory) != VK_SUCCESS) {
1196 throw mxvk::Exception("Failed to allocate image memory");
1197 }
1198 if (vkBindImageMemory(device, image, memory, 0) != VK_SUCCESS) {
1199 throw mxvk::Exception("Failed to bind image memory");
1200 }
1201 } catch (...) {
1202 if (memory != VK_NULL_HANDLE) {
1203 vkFreeMemory(device, memory, nullptr);
1204 memory = VK_NULL_HANDLE;
1205 }
1206 if (image != VK_NULL_HANDLE) {
1207 vkDestroyImage(device, image, nullptr);
1208 image = VK_NULL_HANDLE;
1209 }
1210 throw;
1211 }
1212 }
1213
1214 [[nodiscard]] VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect) const {
1215 VkImageViewCreateInfo viewInfo{};
1216 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1217 viewInfo.image = image;
1218 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1219 viewInfo.format = format;
1220 viewInfo.subresourceRange.aspectMask = aspect;
1221 viewInfo.subresourceRange.baseMipLevel = 0;
1222 viewInfo.subresourceRange.levelCount = 1;
1223 viewInfo.subresourceRange.baseArrayLayer = 0;
1224 viewInfo.subresourceRange.layerCount = 1;
1225
1226 VkImageView imageView = VK_NULL_HANDLE;
1227 if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) {
1228 throw mxvk::Exception("Failed to create image view");
1229 }
1230 return imageView;
1231 }
1232
1233 void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) const {
1234 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1235
1236 VkImageMemoryBarrier barrier{};
1237 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1238 barrier.oldLayout = oldLayout;
1239 barrier.newLayout = newLayout;
1240 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1241 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1242 barrier.image = image;
1243 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1244 barrier.subresourceRange.baseMipLevel = 0;
1245 barrier.subresourceRange.levelCount = 1;
1246 barrier.subresourceRange.baseArrayLayer = 0;
1247 barrier.subresourceRange.layerCount = 1;
1248
1249 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1250 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1251
1252 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1253 barrier.srcAccessMask = 0;
1254 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1255 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1256 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1257 } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1258 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1259 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1260 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1261 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1262 } else {
1263 throw mxvk::Exception("Unsupported image layout transition");
1264 }
1265
1266 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
1267
1268 endSingleTimeCommands(commandBuffer);
1269 }
1270
1271 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) const {
1272 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1273
1274 VkBufferImageCopy region{};
1275 region.bufferOffset = 0;
1276 region.bufferRowLength = 0;
1277 region.bufferImageHeight = 0;
1278 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1279 region.imageSubresource.mipLevel = 0;
1280 region.imageSubresource.baseArrayLayer = 0;
1281 region.imageSubresource.layerCount = 1;
1282 region.imageOffset = {0, 0, 0};
1283 region.imageExtent = {width, height, 1};
1284
1285 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
1286
1287 endSingleTimeCommands(commandBuffer);
1288 }
1289
1290 bool starfieldInitialized = false;
1291 int numStars = 0;
1292 std::vector<Star> stars{};
1293 float atmosphericTwinkle = 1.0f;
1294 float lightPollution = 0.0f;
1295
1296 VkImage starTexture = VK_NULL_HANDLE;
1297 VkDeviceMemory starTextureMemory = VK_NULL_HANDLE;
1298 VkImageView starTextureView = VK_NULL_HANDLE;
1299 VkSampler starSampler = VK_NULL_HANDLE;
1300
1301 VkBuffer starVertexBuffer = VK_NULL_HANDLE;
1302 VkDeviceMemory starVertexBufferMemory = VK_NULL_HANDLE;
1303 void *starVertexBufferMapped = nullptr;
1304
1305 VkDescriptorSetLayout starDescriptorSetLayout = VK_NULL_HANDLE;
1306 VkDescriptorPool starDescriptorPool = VK_NULL_HANDLE;
1307 std::vector<VkDescriptorSet> starDescriptorSets{};
1308 std::vector<VkBuffer> starUniformBuffers{};
1309 std::vector<VkDeviceMemory> starUniformBufferMemories{};
1310 std::vector<void *> starUniformBufferMapped{};
1311 VkPipeline starPipeline = VK_NULL_HANDLE;
1312 VkPipelineLayout starPipelineLayout = VK_NULL_HANDLE;
1313 Uint32 lastStarUpdateTime = 0;
1314
1315 VkBuffer flameVertexBuffer = VK_NULL_HANDLE;
1316 VkDeviceMemory flameVertexBufferMemory = VK_NULL_HANDLE;
1317 VkPipeline flamePipeline = VK_NULL_HANDLE;
1318 VkPipelineLayout flamePipelineLayout = VK_NULL_HANDLE;
1319 uint32_t flameVertexCount = 0;
1320
1321 std::string assetRoot;
1322 std::string dataRoot;
1323 mxvk::VKAbstractModel model{};
1324 std::chrono::steady_clock::time_point start{std::chrono::steady_clock::now()};
1325 bool mouseDragging = false;
1326 int lastMouseX = 0;
1327 int lastMouseY = 0;
1328 float mouseYawDegrees = 0.0f;
1329 float mousePitchDegrees = 0.0f;
1330 float mouseSensitivity = 0.35f;
1331 float autoSpinSpeed = 0.65f;
1332 };
1333
1334} // namespace example
1335
1336int main(int argc, char **argv) {
1337 try {
1338 const Arguments args = proc_args(argc, argv);
1339 std::string filename = args.filename;
1340 if (filename.empty()) {
1341 filename = args.path + "/data/starship.obj";
1342 }
1343 example::StarshipWindow window(filename, args.path, "MXVK Starship Example", args.width, args.height, args.fullscreen, args.enable_vsync);
1344 window.loop();
1345 } catch (mxvk::Exception &e) {
1346 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
1347 return EXIT_FAILURE;
1348 } catch (ArgException<std::string> &e) {
1349 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
1350 return EXIT_FAILURE;
1351 }
1352
1353 return EXIT_SUCCESS;
1354}
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 event(SDL_Event &e) override
Handle one SDL event.
Definition starship.cpp:94
StarshipWindow(const std::string filename, const std::string &path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync)
Definition starship.cpp:73
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
Definition starship.cpp:128
~StarshipWindow() override
Definition starship.cpp:85
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override
Optional hook for derived classes to record extra draw commands.
Definition starship.cpp:136
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
static VkShaderModule createShaderModule(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
Definition mxvk.cpp:154
size_t getSwapchainImageCount() const noexcept
Get the number of swapchain images currently allocated.
Definition mxvk.hpp:239
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
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
VkQueue graphics_queue
Definition mxvk.hpp:609
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
Definition mxvk.hpp:213
#define MXVK_VALIDATION
Definition mxvk.hpp:28
High-level model wrapper integrated with MXVK dynamic rendering.
PNG image loading and saving utilities via SDL3.
int main()
Definition main.py:165
Definition model.py:1
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition mxvk_png.cpp:89
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 filename
Optional input filename (--filename).
Definition argz.hpp:726
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
float x
Definition space.cpp:83
float y
Definition space.cpp:83
float size
Definition space.cpp:84
Default transform UBO payload for model shaders.