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