73 StarshipWindow(
const std::string filename,
const std::string &path,
const std::string &title,
int width,
int height,
bool fullscreen,
bool 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";
80 model.load(
this, filename,
"",
"", 1.0f);
81 model.setShaders(
this, modelVertPath, modelFragPath);
84 createStarResources();
85 createFlameResources();
89 if (
device != VK_NULL_HANDLE) {
92 cleanupFlameResources();
93 cleanupStarResources();
97 void event(SDL_Event &e)
override {
98 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
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);
110 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
111 mouseDragging =
false;
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;
121 mouseYawDegrees +=
static_cast<float>(deltaX) * mouseSensitivity;
122 mousePitchDegrees +=
static_cast<float>(deltaY) * mouseSensitivity;
123 mousePitchDegrees = std::clamp(mousePitchDegrees, -80.0f, 80.0f);
133 cleanupStarSwapchainResources();
134 cleanupFlameSwapchainResources();
135 createStarSwapchainResources();
136 createFlameSwapchainResources();
140 const float elapsedSeconds = std::chrono::duration<float>(std::chrono::steady_clock::now() - start).count();
142 const float aspect = (extent.height > 0U)
143 ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height)
146 drawStarfield(cmd, imageIndex, extent, elapsedSeconds);
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));
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;
160 model.updateUBO(imageIndex, ubo);
161 model.render(cmd, imageIndex,
false);
162 drawEngineFlame(cmd, extent, elapsedSeconds, ubo.
model, ubo.
view, ubo.
proj);
166 void initStarfield(
int numStarsParam) {
167 if (starfieldInitialized) {
171 numStars = numStarsParam;
172 stars.resize(
static_cast<std::size_t
>(numStars));
174 for (
int i = 0; i < numStars; ++i) {
175 respawnStar(stars[
static_cast<std::size_t
>(i)]);
178 starfieldInitialized =
true;
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);
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);
190 star.vx = randomFloat(-0.060f, 0.060f);
191 star.vy = randomFloat(-0.060f, 0.060f);
192 star.vz = randomFloat(-0.060f, 0.060f);
194 const float r = randomFloat(0.0f, 1.0f);
196 star.magnitude = randomFloat(-1.0f, 2.0f);
198 }
else if (r < 0.3f) {
199 star.magnitude = randomFloat(2.0f, 4.0f);
202 star.magnitude = randomFloat(4.0f, 6.5f);
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);
211 star.temperature = randomFloat(2500.0f, 4000.0f);
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);
219 void createStarResources() {
221 createStarVertexBuffer();
222 createStarSwapchainResources();
225 void cleanupStarResources() {
226 cleanupStarSwapchainResources();
228 if (starVertexBufferMapped !=
nullptr && starVertexBufferMemory != VK_NULL_HANDLE) {
229 vkUnmapMemory(
device, starVertexBufferMemory);
230 starVertexBufferMapped =
nullptr;
232 if (starVertexBuffer != VK_NULL_HANDLE) {
233 vkDestroyBuffer(
device, starVertexBuffer,
nullptr);
234 starVertexBuffer = VK_NULL_HANDLE;
236 if (starVertexBufferMemory != VK_NULL_HANDLE) {
237 vkFreeMemory(
device, starVertexBufferMemory,
nullptr);
238 starVertexBufferMemory = VK_NULL_HANDLE;
241 if (starSampler != VK_NULL_HANDLE) {
242 vkDestroySampler(
device, starSampler,
nullptr);
243 starSampler = VK_NULL_HANDLE;
245 if (starTextureView != VK_NULL_HANDLE) {
246 vkDestroyImageView(
device, starTextureView,
nullptr);
247 starTextureView = VK_NULL_HANDLE;
249 if (starTexture != VK_NULL_HANDLE) {
250 vkDestroyImage(
device, starTexture,
nullptr);
251 starTexture = VK_NULL_HANDLE;
253 if (starTextureMemory != VK_NULL_HANDLE) {
254 vkFreeMemory(
device, starTextureMemory,
nullptr);
255 starTextureMemory = VK_NULL_HANDLE;
259 void cleanupStarSwapchainResources() {
260 if (starPipeline != VK_NULL_HANDLE) {
261 vkDestroyPipeline(
device, starPipeline,
nullptr);
262 starPipeline = VK_NULL_HANDLE;
264 if (starPipelineLayout != VK_NULL_HANDLE) {
265 vkDestroyPipelineLayout(
device, starPipelineLayout,
nullptr);
266 starPipelineLayout = VK_NULL_HANDLE;
268 if (starDescriptorPool != VK_NULL_HANDLE) {
269 vkDestroyDescriptorPool(
device, starDescriptorPool,
nullptr);
270 starDescriptorPool = VK_NULL_HANDLE;
272 if (starDescriptorSetLayout != VK_NULL_HANDLE) {
273 vkDestroyDescriptorSetLayout(
device, starDescriptorSetLayout,
nullptr);
274 starDescriptorSetLayout = VK_NULL_HANDLE;
277 destroyStarUniformBuffers();
278 starDescriptorSets.clear();
281 void createStarSwapchainResources() {
282 if (!starfieldInitialized ||
device == VK_NULL_HANDLE) {
286 createStarDescriptorSetLayout();
287 createStarUniformBuffers();
288 createStarDescriptorPool();
289 createStarDescriptorSets();
290 createStarPipeline();
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");
300 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(starImg->w) *
static_cast<VkDeviceSize
>(starImg->h) * 4U;
302 VkBuffer stagingBuffer = VK_NULL_HANDLE;
303 VkDeviceMemory stagingBufferMemory = VK_NULL_HANDLE;
306 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
307 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
309 stagingBufferMemory);
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");
318 std::memcpy(data, starImg->pixels,
static_cast<std::size_t
>(imageSize));
319 vkUnmapMemory(
device, stagingBufferMemory);
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,
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);
335 vkDestroyBuffer(
device, stagingBuffer,
nullptr);
336 vkFreeMemory(
device, stagingBufferMemory,
nullptr);
338 starTextureView = createImageView(starTexture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
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");
358 SDL_DestroySurface(starImg);
361 void createStarVertexBuffer() {
362 const VkDeviceSize bufferSize =
sizeof(StarVertex) *
static_cast<VkDeviceSize
>(numStars);
365 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
366 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
368 starVertexBufferMemory);
370 if (vkMapMemory(
device, starVertexBufferMemory, 0, bufferSize, 0, &starVertexBufferMapped) != VK_SUCCESS || starVertexBufferMapped ==
nullptr) {
371 throw mxvk::Exception(
"Failed to map star vertex buffer");
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;
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;
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();
394 if (vkCreateDescriptorSetLayout(
device, &layoutInfo,
nullptr, &starDescriptorSetLayout) != VK_SUCCESS) {
395 throw mxvk::Exception(
"Failed to create star descriptor set layout");
399 void createStarUniformBuffers() {
401 const VkDeviceSize bufferSize =
sizeof(StarUniformBufferObject);
403 starUniformBuffers.resize(imageCount, VK_NULL_HANDLE);
404 starUniformBufferMemories.resize(imageCount, VK_NULL_HANDLE);
405 starUniformBufferMapped.resize(imageCount,
nullptr);
407 for (
size_t i = 0; i < imageCount; ++i) {
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]);
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");
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;
427 if (starUniformBuffers[i] != VK_NULL_HANDLE) {
428 vkDestroyBuffer(
device, starUniformBuffers[i],
nullptr);
429 starUniformBuffers[i] = VK_NULL_HANDLE;
431 if (starUniformBufferMemories[i] != VK_NULL_HANDLE) {
432 vkFreeMemory(
device, starUniformBufferMemories[i],
nullptr);
433 starUniformBufferMemories[i] = VK_NULL_HANDLE;
437 starUniformBuffers.clear();
438 starUniformBufferMemories.clear();
439 starUniformBufferMapped.clear();
442 void createStarDescriptorPool() {
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;
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;
457 if (vkCreateDescriptorPool(
device, &poolInfo,
nullptr, &starDescriptorPool) != VK_SUCCESS) {
458 throw mxvk::Exception(
"Failed to create star descriptor pool");
462 void createStarDescriptorSets() {
464 std::vector<VkDescriptorSetLayout> layouts(imageCount, starDescriptorSetLayout);
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();
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");
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;
483 VkDescriptorBufferInfo bufferInfo{};
484 bufferInfo.buffer = starUniformBuffers[i];
485 bufferInfo.offset = 0;
486 bufferInfo.range =
sizeof(StarUniformBufferObject);
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;
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;
503 vkUpdateDescriptorSets(
device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
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");
512 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
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";
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";
529 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
531 VkVertexInputBindingDescription bindingDescription{};
532 bindingDescription.binding = 0;
533 bindingDescription.stride =
sizeof(StarVertex);
534 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
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);
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);
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);
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();
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;
564 VkPipelineViewportStateCreateInfo viewportState{};
565 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
566 viewportState.viewportCount = 1;
567 viewportState.scissorCount = 1;
569 std::array<VkDynamicState, 2> dynamicStates = {
570 VK_DYNAMIC_STATE_VIEWPORT,
571 VK_DYNAMIC_STATE_SCISSOR,
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();
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;
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;
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;
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;
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;
618 VkPipelineLayoutCreateInfo layoutInfo{};
619 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
620 layoutInfo.setLayoutCount = 1;
621 layoutInfo.pSetLayouts = &starDescriptorSetLayout;
623 if (vkCreatePipelineLayout(
device, &layoutInfo,
nullptr, &starPipelineLayout) != VK_SUCCESS) {
624 throw mxvk::Exception(
"Failed to create star pipeline layout");
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;
637 renderingInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
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;
656 if (vkCreateGraphicsPipelines(
device, VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &starPipeline) != VK_SUCCESS) {
657 throw mxvk::Exception(
"Failed to create star pipeline");
660 if (fragShaderModule != VK_NULL_HANDLE) {
661 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
663 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
664 if (starPipeline != VK_NULL_HANDLE) {
665 vkDestroyPipeline(
device, starPipeline,
nullptr);
666 starPipeline = VK_NULL_HANDLE;
668 if (starPipelineLayout != VK_NULL_HANDLE) {
669 vkDestroyPipelineLayout(
device, starPipelineLayout,
nullptr);
670 starPipelineLayout = VK_NULL_HANDLE;
675 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
676 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
679 void updateStarfield(
float deltaTime) {
680 if (!starfieldInitialized || starVertexBufferMapped ==
nullptr) {
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);
688 for (
int i = 0; i < numStars; ++i) {
689 auto &star = stars[
static_cast<std::size_t
>(i)];
691 star.
x += star.vx * deltaTime;
692 star.
y += star.vy * deltaTime;
693 star.z += star.vz * deltaTime;
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) {
700 vertices[i].pos[0] = star.
x;
701 vertices[i].pos[1] = star.
y;
702 vertices[i].pos[2] = star.z;
704 float twinkleFactor = 1.0f;
705 if (atmosphericTwinkle > 0.0f) {
706 twinkleFactor = 0.7f + 0.3f * std::sin(time * star.twinkle) * atmosphericTwinkle;
709 float size = star.
size * twinkleFactor;
710 if (star.isConstellation) {
713 vertices[i].size = size;
715 const glm::vec3 starColor = getStarColor(star.temperature);
716 const float alpha = magnitudeToAlpha(star.magnitude) * twinkleFactor;
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;
725 void updateStarUniform(uint32_t imageIndex,
726 [[maybe_unused]]
const VkExtent2D &extent,
727 const glm::mat4 &view,
728 const glm::mat4 &proj,
730 if (imageIndex >= starUniformBufferMapped.size() || starUniformBufferMapped[imageIndex] ==
nullptr) {
734 StarUniformBufferObject ubo{};
735 ubo.model = glm::mat4(1.0f);
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));
743 void drawStarfield(VkCommandBuffer cmd, uint32_t imageIndex,
const VkExtent2D &extent,
float elapsedSeconds) {
744 if (!starfieldInitialized || starPipeline == VK_NULL_HANDLE || imageIndex >= starDescriptorSets.size()) {
748 const Uint32 currentTime = SDL_GetTicks();
749 const float deltaTime =
static_cast<float>(currentTime - lastStarUpdateTime) / 1000.0f;
750 lastStarUpdateTime = currentTime;
751 updateStarfield(deltaTime);
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(
756 static_cast<float>(extent.width) /
static_cast<float>(extent.height),
761 updateStarUniform(imageIndex, extent, view, proj, elapsedSeconds);
763 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, starPipeline);
765 VkViewport viewport{};
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);
775 scissor.offset = {0, 0};
776 scissor.extent = extent;
777 vkCmdSetScissor(cmd, 0, 1, &scissor);
779 VkBuffer vertexBuffers[] = {starVertexBuffer};
780 VkDeviceSize offsets[] = {0};
781 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
783 vkCmdBindDescriptorSets(
785 VK_PIPELINE_BIND_POINT_GRAPHICS,
789 &starDescriptorSets[imageIndex],
793 vkCmdDraw(cmd,
static_cast<uint32_t
>(numStars), 1, 0, 0);
796 void createFlameResources() {
798 createFlameSwapchainResources();
801 void cleanupFlameResources() {
802 cleanupFlameSwapchainResources();
804 if (flameVertexBuffer != VK_NULL_HANDLE) {
805 vkDestroyBuffer(
device, flameVertexBuffer,
nullptr);
806 flameVertexBuffer = VK_NULL_HANDLE;
808 if (flameVertexBufferMemory != VK_NULL_HANDLE) {
809 vkFreeMemory(
device, flameVertexBufferMemory,
nullptr);
810 flameVertexBufferMemory = VK_NULL_HANDLE;
814 void cleanupFlameSwapchainResources() {
815 if (flamePipeline != VK_NULL_HANDLE) {
816 vkDestroyPipeline(
device, flamePipeline,
nullptr);
817 flamePipeline = VK_NULL_HANDLE;
819 if (flamePipelineLayout != VK_NULL_HANDLE) {
820 vkDestroyPipelineLayout(
device, flamePipelineLayout,
nullptr);
821 flamePipelineLayout = VK_NULL_HANDLE;
825 void createFlameSwapchainResources() {
826 if (flameVertexCount == 0 ||
device == VK_NULL_HANDLE) {
830 createFlamePipeline();
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;
841 std::vector<FlameVertex> vertices{};
842 vertices.reserve(
static_cast<std::size_t
>(segments) * 6U);
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};
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});
862 addCone(outerRadius, outerBaseColor, outerTipColor);
863 addCone(innerRadius, innerBaseColor, innerTipColor);
865 flameVertexCount =
static_cast<uint32_t
>(vertices.size());
866 const VkDeviceSize bufferSize =
sizeof(FlameVertex) *
static_cast<VkDeviceSize
>(vertices.size());
869 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
870 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
872 flameVertexBufferMemory);
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");
878 std::memcpy(data, vertices.data(),
static_cast<std::size_t
>(bufferSize));
879 vkUnmapMemory(
device, flameVertexBufferMemory);
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");
887 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
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";
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";
904 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
906 VkVertexInputBindingDescription bindingDescription{};
907 bindingDescription.binding = 0;
908 bindingDescription.stride =
sizeof(FlameVertex);
909 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
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);
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();
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;
933 VkPipelineViewportStateCreateInfo viewportState{};
934 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
935 viewportState.viewportCount = 1;
936 viewportState.scissorCount = 1;
938 const std::array<VkDynamicState, 2> dynamicStates = {
939 VK_DYNAMIC_STATE_VIEWPORT,
940 VK_DYNAMIC_STATE_SCISSOR,
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();
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;
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;
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;
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;
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;
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);
992 VkPipelineLayoutCreateInfo layoutInfo{};
993 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
994 layoutInfo.pushConstantRangeCount = 1;
995 layoutInfo.pPushConstantRanges = &pushRange;
997 if (vkCreatePipelineLayout(
device, &layoutInfo,
nullptr, &flamePipelineLayout) != VK_SUCCESS) {
998 throw mxvk::Exception(
"Failed to create flame pipeline layout");
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;
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;
1029 if (vkCreateGraphicsPipelines(
device, VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &flamePipeline) != VK_SUCCESS) {
1030 throw mxvk::Exception(
"Failed to create flame pipeline");
1033 if (fragShaderModule != VK_NULL_HANDLE) {
1034 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
1036 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
1037 cleanupFlameSwapchainResources();
1041 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
1042 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
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) {
1050 VkViewport viewport{};
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);
1060 scissor.offset = {0, 0};
1061 scissor.extent = extent;
1062 vkCmdSetScissor(cmd, 0, 1, &scissor);
1064 FlamePushConstants pc{};
1065 pc.mvp = proj * view * modelMatrix;
1066 pc.params = glm::vec4(elapsedSeconds, 0.0f, 0.0f, 0.0f);
1068 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, flamePipeline);
1071 flamePipelineLayout,
1072 VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
1077 VkBuffer vertexBuffers[] = {flameVertexBuffer};
1078 VkDeviceSize offsets[] = {0};
1079 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
1080 vkCmdDraw(cmd, flameVertexCount, 1, 0, 0);
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);
1090 glm::vec3 getStarColor(
float temperature)
const {
1095 if (temperature < 3700.0f) {
1097 g = temperature / 3700.0f * 0.6f;
1099 }
else if (temperature < 5200.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) {
1106 b = (temperature - 5200.0f) / 800.0f * 0.7f;
1107 }
else if (temperature < 7500.0f) {
1110 b = 0.7f + (temperature - 6000.0f) / 1500.0f * 0.3f;
1112 r = 0.7f - (temperature - 7500.0f) / 10000.0f * 0.4f;
1113 g = 0.8f + (temperature - 7500.0f) / 10000.0f * 0.2f;
1117 return glm::vec3(r, g, b);
1120 float magnitudeToSize(
float magnitude)
const {
1121 return glm::clamp(6.5f - magnitude * 0.85f, 0.75f, 7.0f);
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);
1129 void createBuffer(VkDeviceSize size,
1130 VkBufferUsageFlags usage,
1131 VkMemoryPropertyFlags properties,
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;
1140 if (vkCreateBuffer(
device, &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
1141 throw mxvk::Exception(
"Failed to create buffer");
1144 VkMemoryRequirements memRequirements{};
1145 vkGetBufferMemoryRequirements(
device, buffer, &memRequirements);
1147 VkMemoryAllocateInfo allocInfo{};
1148 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1149 allocInfo.allocationSize = memRequirements.size;
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");
1156 if (vkBindBufferMemory(
device, buffer, bufferMemory, 0) != VK_SUCCESS) {
1157 throw mxvk::Exception(
"Failed to bind buffer memory");
1160 if (bufferMemory != VK_NULL_HANDLE) {
1161 vkFreeMemory(
device, bufferMemory,
nullptr);
1162 bufferMemory = VK_NULL_HANDLE;
1164 if (buffer != VK_NULL_HANDLE) {
1165 vkDestroyBuffer(
device, buffer,
nullptr);
1166 buffer = VK_NULL_HANDLE;
1172 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
1173 VkPhysicalDeviceMemoryProperties memProperties{};
1176 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
1177 if ((typeFilter & (1U << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1182 throw mxvk::Exception(
"Failed to find suitable memory type");
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;
1190 allocInfo.commandBufferCount = 1;
1192 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
1193 if (vkAllocateCommandBuffers(
device, &allocInfo, &commandBuffer) != VK_SUCCESS) {
1194 throw mxvk::Exception(
"Failed to allocate command buffer");
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) {
1202 throw mxvk::Exception(
"Failed to begin command buffer");
1205 return commandBuffer;
1208 void endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
1209 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
1211 throw mxvk::Exception(
"Failed to end command buffer");
1214 VkSubmitInfo submitInfo{};
1215 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1216 submitInfo.commandBufferCount = 1;
1217 submitInfo.pCommandBuffers = &commandBuffer;
1219 if (vkQueueSubmit(
graphics_queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
1221 throw mxvk::Exception(
"Failed to submit command buffer");
1225 throw mxvk::Exception(
"Failed to wait for graphics queue idle");
1231 void createImage(uint32_t width,
1234 VkImageTiling tiling,
1235 VkImageUsageFlags usage,
1236 VkMemoryPropertyFlags properties,
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;
1254 if (vkCreateImage(
device, &imageInfo,
nullptr, &image) != VK_SUCCESS) {
1255 throw mxvk::Exception(
"Failed to create image");
1258 VkMemoryRequirements memRequirements{};
1259 vkGetImageMemoryRequirements(
device, image, &memRequirements);
1261 VkMemoryAllocateInfo allocInfo{};
1262 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1263 allocInfo.allocationSize = memRequirements.size;
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");
1270 if (vkBindImageMemory(
device, image, memory, 0) != VK_SUCCESS) {
1271 throw mxvk::Exception(
"Failed to bind image memory");
1274 if (memory != VK_NULL_HANDLE) {
1275 vkFreeMemory(
device, memory,
nullptr);
1276 memory = VK_NULL_HANDLE;
1278 if (image != VK_NULL_HANDLE) {
1279 vkDestroyImage(
device, image,
nullptr);
1280 image = VK_NULL_HANDLE;
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;
1298 VkImageView imageView = VK_NULL_HANDLE;
1299 if (vkCreateImageView(
device, &viewInfo,
nullptr, &imageView) != VK_SUCCESS) {
1300 throw mxvk::Exception(
"Failed to create image view");
1305 void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
1306 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
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;
1321 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1322 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
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;
1335 throw mxvk::Exception(
"Unsupported image layout transition");
1338 vkCmdPipelineBarrier(
1350 endSingleTimeCommands(commandBuffer);
1353 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
1354 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
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};
1367 vkCmdCopyBufferToImage(
1371 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1375 endSingleTimeCommands(commandBuffer);
1378 bool starfieldInitialized =
false;
1380 std::vector<Star> stars{};
1381 float atmosphericTwinkle = 1.0f;
1382 float lightPollution = 0.0f;
1384 VkImage starTexture = VK_NULL_HANDLE;
1385 VkDeviceMemory starTextureMemory = VK_NULL_HANDLE;
1386 VkImageView starTextureView = VK_NULL_HANDLE;
1387 VkSampler starSampler = VK_NULL_HANDLE;
1389 VkBuffer starVertexBuffer = VK_NULL_HANDLE;
1390 VkDeviceMemory starVertexBufferMemory = VK_NULL_HANDLE;
1391 void *starVertexBufferMapped =
nullptr;
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;
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;
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;
1416 float mouseYawDegrees = 0.0f;
1417 float mousePitchDegrees = 0.0f;
1418 float mouseSensitivity = 0.35f;
1419 float autoSpinSpeed = 0.65f;