73 StarshipWindow(
const std::string filename,
const std::string &path,
const std::string &title,
int width,
int height,
bool fullscreen,
bool enable_vsync) :
mxvk::
VK_Window(title, width, height, fullscreen,
MXVK_VALIDATION, enable_vsync), assetRoot((path.empty() || path ==
".") ? std::string(STARSHIP_EXAMPLE_ASSET_DIR) : path), dataRoot(assetRoot +
"/data") {
74 const std::string modelVertPath = dataRoot +
"/model.vert.spv";
75 const std::string modelFragPath = dataRoot +
"/model.frag.spv";
77 model.load(
this, filename,
"",
"", 1.0f);
78 model.setShaders(
this, modelVertPath, modelFragPath);
81 createStarResources();
82 createFlameResources();
86 if (
device != VK_NULL_HANDLE) {
89 cleanupFlameResources();
90 cleanupStarResources();
94 void event(SDL_Event &e)
override {
95 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
100 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
101 mouseDragging =
true;
102 lastMouseX =
static_cast<int>(e.button.x);
103 lastMouseY =
static_cast<int>(e.button.y);
107 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
108 mouseDragging =
false;
112 if (e.type == SDL_EVENT_MOUSE_MOTION && mouseDragging) {
113 const int x =
static_cast<int>(e.motion.x);
114 const int y =
static_cast<int>(e.motion.y);
115 const int deltaX = x - lastMouseX;
116 const int deltaY = y - lastMouseY;
118 mouseYawDegrees +=
static_cast<float>(deltaX) * mouseSensitivity;
119 mousePitchDegrees +=
static_cast<float>(deltaY) * mouseSensitivity;
120 mousePitchDegrees = std::clamp(mousePitchDegrees, -80.0f, 80.0f);
130 cleanupStarSwapchainResources();
131 cleanupFlameSwapchainResources();
132 createStarSwapchainResources();
133 createFlameSwapchainResources();
137 const float elapsedSeconds = std::chrono::duration<float>(std::chrono::steady_clock::now() - start).count();
139 const float aspect = (extent.height > 0U) ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height) : 1.0f;
141 drawStarfield(cmd, imageIndex, extent, elapsedSeconds);
144 ubo.
model = glm::mat4(1.0f);
145 ubo.
model = glm::rotate(ubo.
model, glm::radians(mousePitchDegrees), glm::vec3(1.0f, 0.0f, 0.0f));
146 ubo.
model = glm::rotate(ubo.
model, elapsedSeconds * autoSpinSpeed + glm::radians(mouseYawDegrees), glm::vec3(0.0f, 1.0f, 0.0f));
147 ubo.
model = glm::scale(ubo.
model, glm::vec3(
model.modelRenderScale()));
149 ubo.
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 4.2f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
150 ubo.
proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
151 ubo.
proj[1][1] *= -1.0f;
153 model.updateUBO(imageIndex, ubo);
154 model.render(cmd, imageIndex,
false);
155 drawEngineFlame(cmd, extent, elapsedSeconds, ubo.
model, ubo.
view, ubo.
proj);
159 void initStarfield(
int numStarsParam) {
160 if (starfieldInitialized) {
164 numStars = numStarsParam;
165 stars.resize(
static_cast<std::size_t
>(numStars));
167 for (
int i = 0; i < numStars; ++i) {
168 respawnStar(stars[
static_cast<std::size_t
>(i)]);
171 starfieldInitialized =
true;
174 void respawnStar(Star &star) {
175 const float theta = randomFloat(0.0f, 2.0f * PI);
176 const float phi = std::acos(randomFloat(-1.0f, 1.0f));
177 const float radius = randomFloat(50.0f, 200.0f);
179 star.
x = radius * std::sin(phi) * std::cos(theta);
180 star.
y = radius * std::sin(phi) * std::sin(theta);
181 star.z = radius * std::cos(phi);
183 star.vx = randomFloat(-0.060f, 0.060f);
184 star.vy = randomFloat(-0.060f, 0.060f);
185 star.vz = randomFloat(-0.060f, 0.060f);
187 const float r = randomFloat(0.0f, 1.0f);
189 star.magnitude = randomFloat(-1.0f, 2.0f);
191 }
else if (r < 0.3f) {
192 star.magnitude = randomFloat(2.0f, 4.0f);
195 star.magnitude = randomFloat(4.0f, 6.5f);
199 if (star.starType == 1) {
200 star.temperature = randomFloat(3000.0f, 5000.0f);
201 }
else if (star.starType == 0) {
202 star.temperature = randomFloat(4000.0f, 8000.0f);
204 star.temperature = randomFloat(2500.0f, 4000.0f);
207 star.twinkle = randomFloat(0.5f, 3.0f);
208 star.
size = magnitudeToSize(star.magnitude);
209 star.isConstellation = (star.magnitude < 3.0f) && (randomFloat(0.0f, 1.0f) < 0.3f);
212 void createStarResources() {
214 createStarVertexBuffer();
215 createStarSwapchainResources();
218 void cleanupStarResources() {
219 cleanupStarSwapchainResources();
221 if (starVertexBufferMapped !=
nullptr && starVertexBufferMemory != VK_NULL_HANDLE) {
222 vkUnmapMemory(
device, starVertexBufferMemory);
223 starVertexBufferMapped =
nullptr;
225 if (starVertexBuffer != VK_NULL_HANDLE) {
226 vkDestroyBuffer(
device, starVertexBuffer,
nullptr);
227 starVertexBuffer = VK_NULL_HANDLE;
229 if (starVertexBufferMemory != VK_NULL_HANDLE) {
230 vkFreeMemory(
device, starVertexBufferMemory,
nullptr);
231 starVertexBufferMemory = VK_NULL_HANDLE;
234 if (starSampler != VK_NULL_HANDLE) {
235 vkDestroySampler(
device, starSampler,
nullptr);
236 starSampler = VK_NULL_HANDLE;
238 if (starTextureView != VK_NULL_HANDLE) {
239 vkDestroyImageView(
device, starTextureView,
nullptr);
240 starTextureView = VK_NULL_HANDLE;
242 if (starTexture != VK_NULL_HANDLE) {
243 vkDestroyImage(
device, starTexture,
nullptr);
244 starTexture = VK_NULL_HANDLE;
246 if (starTextureMemory != VK_NULL_HANDLE) {
247 vkFreeMemory(
device, starTextureMemory,
nullptr);
248 starTextureMemory = VK_NULL_HANDLE;
252 void cleanupStarSwapchainResources() {
253 if (starPipeline != VK_NULL_HANDLE) {
254 vkDestroyPipeline(
device, starPipeline,
nullptr);
255 starPipeline = VK_NULL_HANDLE;
257 if (starPipelineLayout != VK_NULL_HANDLE) {
258 vkDestroyPipelineLayout(
device, starPipelineLayout,
nullptr);
259 starPipelineLayout = VK_NULL_HANDLE;
261 if (starDescriptorPool != VK_NULL_HANDLE) {
262 vkDestroyDescriptorPool(
device, starDescriptorPool,
nullptr);
263 starDescriptorPool = VK_NULL_HANDLE;
265 if (starDescriptorSetLayout != VK_NULL_HANDLE) {
266 vkDestroyDescriptorSetLayout(
device, starDescriptorSetLayout,
nullptr);
267 starDescriptorSetLayout = VK_NULL_HANDLE;
270 destroyStarUniformBuffers();
271 starDescriptorSets.clear();
274 void createStarSwapchainResources() {
275 if (!starfieldInitialized ||
device == VK_NULL_HANDLE) {
279 createStarDescriptorSetLayout();
280 createStarUniformBuffers();
281 createStarDescriptorPool();
282 createStarDescriptorSets();
283 createStarPipeline();
286 void createStarTexture() {
287 const std::string starTexturePath = dataRoot +
"/star.png";
288 SDL_Surface *starImg =
mxvk::LoadPNG(starTexturePath.c_str());
289 if (starImg ==
nullptr) {
290 throw mxvk::Exception(
"Failed to load star.png texture");
293 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(starImg->w) *
static_cast<VkDeviceSize
>(starImg->h) * 4U;
295 VkBuffer stagingBuffer = VK_NULL_HANDLE;
296 VkDeviceMemory stagingBufferMemory = VK_NULL_HANDLE;
297 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
299 void *data =
nullptr;
300 if (vkMapMemory(
device, stagingBufferMemory, 0, imageSize, 0, &data) != VK_SUCCESS || data ==
nullptr) {
301 vkDestroyBuffer(
device, stagingBuffer,
nullptr);
302 vkFreeMemory(
device, stagingBufferMemory,
nullptr);
303 SDL_DestroySurface(starImg);
304 throw mxvk::Exception(
"Failed to map star texture staging buffer");
306 std::memcpy(data, starImg->pixels,
static_cast<std::size_t
>(imageSize));
307 vkUnmapMemory(
device, stagingBufferMemory);
309 createImage(
static_cast<uint32_t
>(starImg->w),
static_cast<uint32_t
>(starImg->h), VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, starTexture, starTextureMemory);
311 transitionImageLayout(starTexture, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
312 copyBufferToImage(stagingBuffer, starTexture,
static_cast<uint32_t
>(starImg->w),
static_cast<uint32_t
>(starImg->h));
313 transitionImageLayout(starTexture, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
315 vkDestroyBuffer(
device, stagingBuffer,
nullptr);
316 vkFreeMemory(
device, stagingBufferMemory,
nullptr);
318 starTextureView = createImageView(starTexture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
320 VkSamplerCreateInfo samplerInfo{};
321 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
322 samplerInfo.magFilter = VK_FILTER_LINEAR;
323 samplerInfo.minFilter = VK_FILTER_LINEAR;
324 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
325 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
326 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
327 samplerInfo.anisotropyEnable = VK_FALSE;
328 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
329 samplerInfo.unnormalizedCoordinates = VK_FALSE;
330 samplerInfo.compareEnable = VK_FALSE;
331 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
332 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
333 if (vkCreateSampler(
device, &samplerInfo,
nullptr, &starSampler) != VK_SUCCESS) {
334 SDL_DestroySurface(starImg);
335 throw mxvk::Exception(
"Failed to create star texture sampler");
338 SDL_DestroySurface(starImg);
341 void createStarVertexBuffer() {
342 const VkDeviceSize bufferSize =
sizeof(StarVertex) *
static_cast<VkDeviceSize
>(numStars);
343 createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, starVertexBuffer, starVertexBufferMemory);
345 if (vkMapMemory(
device, starVertexBufferMemory, 0, bufferSize, 0, &starVertexBufferMapped) != VK_SUCCESS || starVertexBufferMapped ==
nullptr) {
346 throw mxvk::Exception(
"Failed to map star vertex buffer");
350 void createStarDescriptorSetLayout() {
351 VkDescriptorSetLayoutBinding samplerBinding{};
352 samplerBinding.binding = 0;
353 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
354 samplerBinding.descriptorCount = 1;
355 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
357 VkDescriptorSetLayoutBinding uboBinding{};
358 uboBinding.binding = 1;
359 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
360 uboBinding.descriptorCount = 1;
361 uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
363 std::array<VkDescriptorSetLayoutBinding, 2> bindings = {samplerBinding, uboBinding};
364 VkDescriptorSetLayoutCreateInfo layoutInfo{};
365 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
366 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
367 layoutInfo.pBindings = bindings.data();
369 if (vkCreateDescriptorSetLayout(
device, &layoutInfo,
nullptr, &starDescriptorSetLayout) != VK_SUCCESS) {
370 throw mxvk::Exception(
"Failed to create star descriptor set layout");
374 void createStarUniformBuffers() {
376 const VkDeviceSize bufferSize =
sizeof(StarUniformBufferObject);
378 starUniformBuffers.resize(imageCount, VK_NULL_HANDLE);
379 starUniformBufferMemories.resize(imageCount, VK_NULL_HANDLE);
380 starUniformBufferMapped.resize(imageCount,
nullptr);
382 for (
size_t i = 0; i < imageCount; ++i) {
383 createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, starUniformBuffers[i], starUniformBufferMemories[i]);
385 if (vkMapMemory(
device, starUniformBufferMemories[i], 0, bufferSize, 0, &starUniformBufferMapped[i]) != VK_SUCCESS || starUniformBufferMapped[i] ==
nullptr) {
386 throw mxvk::Exception(
"Failed to map star uniform buffer");
391 void destroyStarUniformBuffers() {
392 for (
size_t i = 0; i < starUniformBuffers.size(); ++i) {
393 if (starUniformBufferMapped[i] !=
nullptr && starUniformBufferMemories[i] != VK_NULL_HANDLE) {
394 vkUnmapMemory(
device, starUniformBufferMemories[i]);
395 starUniformBufferMapped[i] =
nullptr;
397 if (starUniformBuffers[i] != VK_NULL_HANDLE) {
398 vkDestroyBuffer(
device, starUniformBuffers[i],
nullptr);
399 starUniformBuffers[i] = VK_NULL_HANDLE;
401 if (starUniformBufferMemories[i] != VK_NULL_HANDLE) {
402 vkFreeMemory(
device, starUniformBufferMemories[i],
nullptr);
403 starUniformBufferMemories[i] = VK_NULL_HANDLE;
407 starUniformBuffers.clear();
408 starUniformBufferMemories.clear();
409 starUniformBufferMapped.clear();
412 void createStarDescriptorPool() {
415 std::array<VkDescriptorPoolSize, 2> poolSizes{};
416 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
417 poolSizes[0].descriptorCount = imageCount;
418 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
419 poolSizes[1].descriptorCount = imageCount;
421 VkDescriptorPoolCreateInfo poolInfo{};
422 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
423 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
424 poolInfo.pPoolSizes = poolSizes.data();
425 poolInfo.maxSets = imageCount;
427 if (vkCreateDescriptorPool(
device, &poolInfo,
nullptr, &starDescriptorPool) != VK_SUCCESS) {
428 throw mxvk::Exception(
"Failed to create star descriptor pool");
432 void createStarDescriptorSets() {
434 std::vector<VkDescriptorSetLayout> layouts(imageCount, starDescriptorSetLayout);
436 VkDescriptorSetAllocateInfo allocInfo{};
437 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
438 allocInfo.descriptorPool = starDescriptorPool;
439 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(imageCount);
440 allocInfo.pSetLayouts = layouts.data();
442 starDescriptorSets.resize(imageCount, VK_NULL_HANDLE);
443 if (vkAllocateDescriptorSets(
device, &allocInfo, starDescriptorSets.data()) != VK_SUCCESS) {
444 throw mxvk::Exception(
"Failed to allocate star descriptor sets");
447 for (
size_t i = 0; i < imageCount; ++i) {
448 VkDescriptorImageInfo imageInfo{};
449 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
450 imageInfo.imageView = starTextureView;
451 imageInfo.sampler = starSampler;
453 VkDescriptorBufferInfo bufferInfo{};
454 bufferInfo.buffer = starUniformBuffers[i];
455 bufferInfo.offset = 0;
456 bufferInfo.range =
sizeof(StarUniformBufferObject);
458 std::array<VkWriteDescriptorSet, 2> writes{};
459 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
460 writes[0].dstSet = starDescriptorSets[i];
461 writes[0].dstBinding = 0;
462 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
463 writes[0].descriptorCount = 1;
464 writes[0].pImageInfo = &imageInfo;
466 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
467 writes[1].dstSet = starDescriptorSets[i];
468 writes[1].dstBinding = 1;
469 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
470 writes[1].descriptorCount = 1;
471 writes[1].pBufferInfo = &bufferInfo;
473 vkUpdateDescriptorSets(
device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
477 void createStarPipeline() {
478 const std::vector<char> vertShaderCode =
loadSpv(dataRoot +
"/star.vert.spv");
479 const std::vector<char> fragShaderCode =
loadSpv(dataRoot +
"/star.frag.spv");
482 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
487 VkPipelineShaderStageCreateInfo vertStage{};
488 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
489 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
490 vertStage.module = vertShaderModule;
491 vertStage.pName =
"main";
493 VkPipelineShaderStageCreateInfo fragStage{};
494 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
495 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
496 fragStage.module = fragShaderModule;
497 fragStage.pName =
"main";
499 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
501 VkVertexInputBindingDescription bindingDescription{};
502 bindingDescription.binding = 0;
503 bindingDescription.stride =
sizeof(StarVertex);
504 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
506 std::array<VkVertexInputAttributeDescription, 3> attributes{};
507 attributes[0].binding = 0;
508 attributes[0].location = 0;
509 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
510 attributes[0].offset = offsetof(StarVertex, pos);
512 attributes[1].binding = 0;
513 attributes[1].location = 1;
514 attributes[1].format = VK_FORMAT_R32_SFLOAT;
515 attributes[1].offset = offsetof(StarVertex, size);
517 attributes[2].binding = 0;
518 attributes[2].location = 2;
519 attributes[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
520 attributes[2].offset = offsetof(StarVertex, color);
522 VkPipelineVertexInputStateCreateInfo vertexInput{};
523 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
524 vertexInput.vertexBindingDescriptionCount = 1;
525 vertexInput.pVertexBindingDescriptions = &bindingDescription;
526 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
527 vertexInput.pVertexAttributeDescriptions = attributes.data();
529 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
530 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
531 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
532 inputAssembly.primitiveRestartEnable = VK_FALSE;
534 VkPipelineViewportStateCreateInfo viewportState{};
535 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
536 viewportState.viewportCount = 1;
537 viewportState.scissorCount = 1;
539 std::array<VkDynamicState, 2> dynamicStates = {
540 VK_DYNAMIC_STATE_VIEWPORT,
541 VK_DYNAMIC_STATE_SCISSOR,
543 VkPipelineDynamicStateCreateInfo dynamicInfo{};
544 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
545 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
546 dynamicInfo.pDynamicStates = dynamicStates.data();
548 VkPipelineRasterizationStateCreateInfo rasterizer{};
549 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
550 rasterizer.depthClampEnable = VK_FALSE;
551 rasterizer.rasterizerDiscardEnable = VK_FALSE;
552 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
553 rasterizer.lineWidth = 1.0f;
554 rasterizer.cullMode = VK_CULL_MODE_NONE;
555 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
556 rasterizer.depthBiasEnable = VK_FALSE;
558 VkPipelineMultisampleStateCreateInfo multisampling{};
559 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
560 multisampling.sampleShadingEnable = VK_FALSE;
561 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
563 VkPipelineDepthStencilStateCreateInfo depthStencil{};
564 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
565 depthStencil.depthTestEnable = VK_FALSE;
566 depthStencil.depthWriteEnable = VK_FALSE;
567 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
568 depthStencil.depthBoundsTestEnable = VK_FALSE;
569 depthStencil.stencilTestEnable = VK_FALSE;
571 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
572 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
573 colorBlendAttachment.blendEnable = VK_TRUE;
574 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
575 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
576 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
577 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
578 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
579 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
581 VkPipelineColorBlendStateCreateInfo colorBlending{};
582 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
583 colorBlending.logicOpEnable = VK_FALSE;
584 colorBlending.attachmentCount = 1;
585 colorBlending.pAttachments = &colorBlendAttachment;
587 VkPipelineLayoutCreateInfo layoutInfo{};
588 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
589 layoutInfo.setLayoutCount = 1;
590 layoutInfo.pSetLayouts = &starDescriptorSetLayout;
592 if (vkCreatePipelineLayout(
device, &layoutInfo,
nullptr, &starPipelineLayout) != VK_SUCCESS) {
593 throw mxvk::Exception(
"Failed to create star pipeline layout");
599 VkPipelineRenderingCreateInfo renderingInfo{};
600 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
601 renderingInfo.colorAttachmentCount = 1;
602 renderingInfo.pColorAttachmentFormats = &colorFormat;
603 if (depthFormat != VK_FORMAT_UNDEFINED) {
604 renderingInfo.depthAttachmentFormat = depthFormat;
606 renderingInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
608 VkGraphicsPipelineCreateInfo pipelineInfo{};
609 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
610 pipelineInfo.pNext = &renderingInfo;
611 pipelineInfo.stageCount =
static_cast<uint32_t
>(shaderStages.size());
612 pipelineInfo.pStages = shaderStages.data();
613 pipelineInfo.pVertexInputState = &vertexInput;
614 pipelineInfo.pInputAssemblyState = &inputAssembly;
615 pipelineInfo.pViewportState = &viewportState;
616 pipelineInfo.pRasterizationState = &rasterizer;
617 pipelineInfo.pMultisampleState = &multisampling;
618 pipelineInfo.pDepthStencilState = &depthStencil;
619 pipelineInfo.pColorBlendState = &colorBlending;
620 pipelineInfo.pDynamicState = &dynamicInfo;
621 pipelineInfo.layout = starPipelineLayout;
622 pipelineInfo.renderPass = VK_NULL_HANDLE;
623 pipelineInfo.subpass = 0;
625 if (vkCreateGraphicsPipelines(
device, VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &starPipeline) != VK_SUCCESS) {
626 throw mxvk::Exception(
"Failed to create star pipeline");
629 if (fragShaderModule != VK_NULL_HANDLE) {
630 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
632 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
633 if (starPipeline != VK_NULL_HANDLE) {
634 vkDestroyPipeline(
device, starPipeline,
nullptr);
635 starPipeline = VK_NULL_HANDLE;
637 if (starPipelineLayout != VK_NULL_HANDLE) {
638 vkDestroyPipelineLayout(
device, starPipelineLayout,
nullptr);
639 starPipelineLayout = VK_NULL_HANDLE;
644 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
645 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
648 void updateStarfield(
float deltaTime) {
649 if (!starfieldInitialized || starVertexBufferMapped ==
nullptr) {
653 deltaTime = std::clamp(deltaTime, 0.0f, 0.1f) * 4.0f;
654 const float time = SDL_GetTicks() * 0.001f;
655 auto *vertices =
static_cast<StarVertex *
>(starVertexBufferMapped);
657 for (
int i = 0; i < numStars; ++i) {
658 auto &star = stars[
static_cast<std::size_t
>(i)];
660 star.
x += star.vx * deltaTime;
661 star.
y += star.vy * deltaTime;
662 star.z += star.vz * deltaTime;
664 const float radiusSquared = star.
x * star.
x + star.
y * star.
y + star.z * star.z;
665 if (radiusSquared < 20.0f * 20.0f || radiusSquared > 260.0f * 260.0f) {
669 vertices[i].pos[0] = star.
x;
670 vertices[i].pos[1] = star.
y;
671 vertices[i].pos[2] = star.z;
673 float twinkleFactor = 1.0f;
674 if (atmosphericTwinkle > 0.0f) {
675 twinkleFactor = 0.7f + 0.3f * std::sin(time * star.twinkle) * atmosphericTwinkle;
678 float size = star.
size * twinkleFactor;
679 if (star.isConstellation) {
682 vertices[i].size = size;
684 const glm::vec3 starColor = getStarColor(star.temperature);
685 const float alpha = magnitudeToAlpha(star.magnitude) * twinkleFactor;
687 vertices[i].color[0] = starColor.r;
688 vertices[i].color[1] = starColor.g;
689 vertices[i].color[2] = starColor.b;
690 vertices[i].color[3] = alpha;
694 void updateStarUniform(uint32_t imageIndex, [[maybe_unused]]
const VkExtent2D &extent,
const glm::mat4 &view,
const glm::mat4 &proj,
float timeSeconds) {
695 if (imageIndex >= starUniformBufferMapped.size() || starUniformBufferMapped[imageIndex] ==
nullptr) {
699 StarUniformBufferObject ubo{};
700 ubo.model = glm::mat4(1.0f);
703 ubo.params = glm::vec4(timeSeconds, 0.0f, 0.0f, 0.0f);
704 ubo.color = glm::vec4(1.0f);
705 std::memcpy(starUniformBufferMapped[imageIndex], &ubo,
sizeof(ubo));
708 void drawStarfield(VkCommandBuffer cmd, uint32_t imageIndex,
const VkExtent2D &extent,
float elapsedSeconds) {
709 if (!starfieldInitialized || starPipeline == VK_NULL_HANDLE || imageIndex >= starDescriptorSets.size()) {
713 const Uint32 currentTime = SDL_GetTicks();
714 const float deltaTime =
static_cast<float>(currentTime - lastStarUpdateTime) / 1000.0f;
715 lastStarUpdateTime = currentTime;
716 updateStarfield(deltaTime);
718 const glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 4.2f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
719 glm::mat4 proj = glm::perspective(glm::radians(60.0f),
static_cast<float>(extent.width) /
static_cast<float>(extent.height), 0.1f, 1000.0f);
722 updateStarUniform(imageIndex, extent, view, proj, elapsedSeconds);
724 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, starPipeline);
726 VkViewport viewport{};
729 viewport.width =
static_cast<float>(extent.width);
730 viewport.height =
static_cast<float>(extent.height);
731 viewport.minDepth = 0.0f;
732 viewport.maxDepth = 1.0f;
733 vkCmdSetViewport(cmd, 0, 1, &viewport);
736 scissor.offset = {0, 0};
737 scissor.extent = extent;
738 vkCmdSetScissor(cmd, 0, 1, &scissor);
740 VkBuffer vertexBuffers[] = {starVertexBuffer};
741 VkDeviceSize offsets[] = {0};
742 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
744 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, starPipelineLayout, 0, 1, &starDescriptorSets[imageIndex], 0,
nullptr);
746 vkCmdDraw(cmd,
static_cast<uint32_t
>(numStars), 1, 0, 0);
749 void createFlameResources() {
751 createFlameSwapchainResources();
754 void cleanupFlameResources() {
755 cleanupFlameSwapchainResources();
757 if (flameVertexBuffer != VK_NULL_HANDLE) {
758 vkDestroyBuffer(
device, flameVertexBuffer,
nullptr);
759 flameVertexBuffer = VK_NULL_HANDLE;
761 if (flameVertexBufferMemory != VK_NULL_HANDLE) {
762 vkFreeMemory(
device, flameVertexBufferMemory,
nullptr);
763 flameVertexBufferMemory = VK_NULL_HANDLE;
767 void cleanupFlameSwapchainResources() {
768 if (flamePipeline != VK_NULL_HANDLE) {
769 vkDestroyPipeline(
device, flamePipeline,
nullptr);
770 flamePipeline = VK_NULL_HANDLE;
772 if (flamePipelineLayout != VK_NULL_HANDLE) {
773 vkDestroyPipelineLayout(
device, flamePipelineLayout,
nullptr);
774 flamePipelineLayout = VK_NULL_HANDLE;
778 void createFlameSwapchainResources() {
779 if (flameVertexCount == 0 ||
device == VK_NULL_HANDLE) {
783 createFlamePipeline();
786 void createFlameMesh() {
787 constexpr int segments = 40;
788 constexpr float baseZ = 0.555f;
789 constexpr float tipZ = 1.02f;
790 constexpr float baseY = 0.040f;
791 constexpr float outerRadius = 0.052f;
792 constexpr float innerRadius = 0.026f;
794 std::vector<FlameVertex> vertices{};
795 vertices.reserve(
static_cast<std::size_t
>(segments) * 6U);
797 const glm::vec4 outerBaseColor{1.0f, 0.42f, 0.08f, 0.50f};
798 const glm::vec4 outerTipColor{0.7f, 0.08f, 0.0f, 0.0f};
799 const glm::vec4 innerBaseColor{1.0f, 0.92f, 0.45f, 0.72f};
800 const glm::vec4 innerTipColor{1.0f, 0.32f, 0.04f, 0.0f};
802 auto addCone = [&](
float radius,
const glm::vec4 &baseColor,
const glm::vec4 &tipColor) {
803 const glm::vec3 tip{0.0f, baseY, tipZ};
804 for (
int i = 0; i < segments; ++i) {
805 const float a0 = (
static_cast<float>(i) /
static_cast<float>(segments)) * 2.0f * PI;
806 const float a1 = (
static_cast<float>(i + 1) /
static_cast<float>(segments)) * 2.0f * PI;
807 const glm::vec3 p0{std::cos(a0) * radius, baseY + std::sin(a0) * radius, baseZ};
808 const glm::vec3 p1{std::cos(a1) * radius, baseY + std::sin(a1) * radius, baseZ};
809 vertices.push_back({p0, baseColor});
810 vertices.push_back({p1, baseColor});
811 vertices.push_back({tip, tipColor});
815 addCone(outerRadius, outerBaseColor, outerTipColor);
816 addCone(innerRadius, innerBaseColor, innerTipColor);
818 flameVertexCount =
static_cast<uint32_t
>(vertices.size());
819 const VkDeviceSize bufferSize =
sizeof(FlameVertex) *
static_cast<VkDeviceSize
>(vertices.size());
820 createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, flameVertexBuffer, flameVertexBufferMemory);
822 void *data =
nullptr;
823 if (vkMapMemory(
device, flameVertexBufferMemory, 0, bufferSize, 0, &data) != VK_SUCCESS || data ==
nullptr) {
824 throw mxvk::Exception(
"Failed to map flame vertex buffer");
826 std::memcpy(data, vertices.data(),
static_cast<std::size_t
>(bufferSize));
827 vkUnmapMemory(
device, flameVertexBufferMemory);
830 void createFlamePipeline() {
831 const std::vector<char> vertShaderCode =
loadSpv(dataRoot +
"/flame.vert.spv");
832 const std::vector<char> fragShaderCode =
loadSpv(dataRoot +
"/flame.frag.spv");
835 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
840 VkPipelineShaderStageCreateInfo vertStage{};
841 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
842 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
843 vertStage.module = vertShaderModule;
844 vertStage.pName =
"main";
846 VkPipelineShaderStageCreateInfo fragStage{};
847 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
848 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
849 fragStage.module = fragShaderModule;
850 fragStage.pName =
"main";
852 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
854 VkVertexInputBindingDescription bindingDescription{};
855 bindingDescription.binding = 0;
856 bindingDescription.stride =
sizeof(FlameVertex);
857 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
859 std::array<VkVertexInputAttributeDescription, 2> attributes{};
860 attributes[0].binding = 0;
861 attributes[0].location = 0;
862 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
863 attributes[0].offset = offsetof(FlameVertex, pos);
864 attributes[1].binding = 0;
865 attributes[1].location = 1;
866 attributes[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
867 attributes[1].offset = offsetof(FlameVertex, color);
869 VkPipelineVertexInputStateCreateInfo vertexInput{};
870 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
871 vertexInput.vertexBindingDescriptionCount = 1;
872 vertexInput.pVertexBindingDescriptions = &bindingDescription;
873 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
874 vertexInput.pVertexAttributeDescriptions = attributes.data();
876 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
877 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
878 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
879 inputAssembly.primitiveRestartEnable = VK_FALSE;
881 VkPipelineViewportStateCreateInfo viewportState{};
882 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
883 viewportState.viewportCount = 1;
884 viewportState.scissorCount = 1;
886 const std::array<VkDynamicState, 2> dynamicStates = {
887 VK_DYNAMIC_STATE_VIEWPORT,
888 VK_DYNAMIC_STATE_SCISSOR,
890 VkPipelineDynamicStateCreateInfo dynamicInfo{};
891 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
892 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
893 dynamicInfo.pDynamicStates = dynamicStates.data();
895 VkPipelineRasterizationStateCreateInfo rasterizer{};
896 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
897 rasterizer.depthClampEnable = VK_FALSE;
898 rasterizer.rasterizerDiscardEnable = VK_FALSE;
899 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
900 rasterizer.lineWidth = 1.0f;
901 rasterizer.cullMode = VK_CULL_MODE_NONE;
902 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
903 rasterizer.depthBiasEnable = VK_FALSE;
905 VkPipelineMultisampleStateCreateInfo multisampling{};
906 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
907 multisampling.sampleShadingEnable = VK_FALSE;
908 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
910 VkPipelineDepthStencilStateCreateInfo depthStencil{};
911 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
912 depthStencil.depthTestEnable = VK_TRUE;
913 depthStencil.depthWriteEnable = VK_FALSE;
914 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
915 depthStencil.depthBoundsTestEnable = VK_FALSE;
916 depthStencil.stencilTestEnable = VK_FALSE;
918 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
919 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
920 colorBlendAttachment.blendEnable = VK_TRUE;
921 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
922 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
923 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
924 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
925 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
926 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
928 VkPipelineColorBlendStateCreateInfo colorBlending{};
929 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
930 colorBlending.logicOpEnable = VK_FALSE;
931 colorBlending.attachmentCount = 1;
932 colorBlending.pAttachments = &colorBlendAttachment;
934 VkPushConstantRange pushRange{};
935 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
936 pushRange.offset = 0;
937 pushRange.size =
sizeof(FlamePushConstants);
939 VkPipelineLayoutCreateInfo layoutInfo{};
940 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
941 layoutInfo.pushConstantRangeCount = 1;
942 layoutInfo.pPushConstantRanges = &pushRange;
944 if (vkCreatePipelineLayout(
device, &layoutInfo,
nullptr, &flamePipelineLayout) != VK_SUCCESS) {
945 throw mxvk::Exception(
"Failed to create flame pipeline layout");
951 VkPipelineRenderingCreateInfo renderingInfo{};
952 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
953 renderingInfo.colorAttachmentCount = 1;
954 renderingInfo.pColorAttachmentFormats = &colorFormat;
955 if (depthFormat != VK_FORMAT_UNDEFINED) {
956 renderingInfo.depthAttachmentFormat = depthFormat;
959 VkGraphicsPipelineCreateInfo pipelineInfo{};
960 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
961 pipelineInfo.pNext = &renderingInfo;
962 pipelineInfo.stageCount =
static_cast<uint32_t
>(shaderStages.size());
963 pipelineInfo.pStages = shaderStages.data();
964 pipelineInfo.pVertexInputState = &vertexInput;
965 pipelineInfo.pInputAssemblyState = &inputAssembly;
966 pipelineInfo.pViewportState = &viewportState;
967 pipelineInfo.pRasterizationState = &rasterizer;
968 pipelineInfo.pMultisampleState = &multisampling;
969 pipelineInfo.pDepthStencilState = &depthStencil;
970 pipelineInfo.pColorBlendState = &colorBlending;
971 pipelineInfo.pDynamicState = &dynamicInfo;
972 pipelineInfo.layout = flamePipelineLayout;
973 pipelineInfo.renderPass = VK_NULL_HANDLE;
974 pipelineInfo.subpass = 0;
976 if (vkCreateGraphicsPipelines(
device, VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &flamePipeline) != VK_SUCCESS) {
977 throw mxvk::Exception(
"Failed to create flame pipeline");
980 if (fragShaderModule != VK_NULL_HANDLE) {
981 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
983 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
984 cleanupFlameSwapchainResources();
988 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
989 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
992 void drawEngineFlame(VkCommandBuffer cmd,
const VkExtent2D &extent,
float elapsedSeconds,
const glm::mat4 &modelMatrix,
const glm::mat4 &view,
const glm::mat4 &proj) {
993 if (flamePipeline == VK_NULL_HANDLE || flameVertexBuffer == VK_NULL_HANDLE || flameVertexCount == 0) {
997 VkViewport viewport{};
1000 viewport.width =
static_cast<float>(extent.width);
1001 viewport.height =
static_cast<float>(extent.height);
1002 viewport.minDepth = 0.0f;
1003 viewport.maxDepth = 1.0f;
1004 vkCmdSetViewport(cmd, 0, 1, &viewport);
1007 scissor.offset = {0, 0};
1008 scissor.extent = extent;
1009 vkCmdSetScissor(cmd, 0, 1, &scissor);
1011 FlamePushConstants pc{};
1012 pc.mvp = proj * view * modelMatrix;
1013 pc.params = glm::vec4(elapsedSeconds, 0.0f, 0.0f, 0.0f);
1015 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, flamePipeline);
1016 vkCmdPushConstants(cmd, flamePipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(pc), &pc);
1018 VkBuffer vertexBuffers[] = {flameVertexBuffer};
1019 VkDeviceSize offsets[] = {0};
1020 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
1021 vkCmdDraw(cmd, flameVertexCount, 1, 0, 0);
1024 float randomFloat(
float minv,
float maxv) {
1025 static std::random_device rd;
1026 static std::default_random_engine eng(rd());
1027 std::uniform_real_distribution<float> dist(minv, maxv);
1031 glm::vec3 getStarColor(
float temperature)
const {
1036 if (temperature < 3700.0f) {
1038 g = temperature / 3700.0f * 0.6f;
1040 }
else if (temperature < 5200.0f) {
1042 g = 0.6f + (temperature - 3700.0f) / 1500.0f * 0.4f;
1043 b = (temperature - 3700.0f) / 1500.0f * 0.3f;
1044 }
else if (temperature < 6000.0f) {
1047 b = (temperature - 5200.0f) / 800.0f * 0.7f;
1048 }
else if (temperature < 7500.0f) {
1051 b = 0.7f + (temperature - 6000.0f) / 1500.0f * 0.3f;
1053 r = 0.7f - (temperature - 7500.0f) / 10000.0f * 0.4f;
1054 g = 0.8f + (temperature - 7500.0f) / 10000.0f * 0.2f;
1058 return glm::vec3(r, g, b);
1061 float magnitudeToSize(
float magnitude)
const {
return glm::clamp(6.5f - magnitude * 0.85f, 0.75f, 7.0f); }
1063 float magnitudeToAlpha(
float magnitude)
const {
1064 const float alpha = (6.5f - magnitude) / 6.5f;
1065 return glm::clamp(alpha - lightPollution, 0.0f, 1.0f);
1068 void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
const {
1069 VkBufferCreateInfo bufferInfo{};
1070 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1071 bufferInfo.size = size;
1072 bufferInfo.usage = usage;
1073 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1075 if (vkCreateBuffer(
device, &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
1076 throw mxvk::Exception(
"Failed to create buffer");
1079 VkMemoryRequirements memRequirements{};
1080 vkGetBufferMemoryRequirements(
device, buffer, &memRequirements);
1082 VkMemoryAllocateInfo allocInfo{};
1083 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1084 allocInfo.allocationSize = memRequirements.size;
1087 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1088 if (vkAllocateMemory(
device, &allocInfo,
nullptr, &bufferMemory) != VK_SUCCESS) {
1089 throw mxvk::Exception(
"Failed to allocate buffer memory");
1091 if (vkBindBufferMemory(
device, buffer, bufferMemory, 0) != VK_SUCCESS) {
1092 throw mxvk::Exception(
"Failed to bind buffer memory");
1095 if (bufferMemory != VK_NULL_HANDLE) {
1096 vkFreeMemory(
device, bufferMemory,
nullptr);
1097 bufferMemory = VK_NULL_HANDLE;
1099 if (buffer != VK_NULL_HANDLE) {
1100 vkDestroyBuffer(
device, buffer,
nullptr);
1101 buffer = VK_NULL_HANDLE;
1107 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
1108 VkPhysicalDeviceMemoryProperties memProperties{};
1111 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
1112 if ((typeFilter & (1U << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1117 throw mxvk::Exception(
"Failed to find suitable memory type");
1120 [[nodiscard]] VkCommandBuffer beginSingleTimeCommands()
const {
1121 VkCommandBufferAllocateInfo allocInfo{};
1122 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1123 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1125 allocInfo.commandBufferCount = 1;
1127 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
1128 if (vkAllocateCommandBuffers(
device, &allocInfo, &commandBuffer) != VK_SUCCESS) {
1129 throw mxvk::Exception(
"Failed to allocate command buffer");
1132 VkCommandBufferBeginInfo beginInfo{};
1133 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1134 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1135 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
1137 throw mxvk::Exception(
"Failed to begin command buffer");
1140 return commandBuffer;
1143 void endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
1144 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
1146 throw mxvk::Exception(
"Failed to end command buffer");
1149 VkSubmitInfo submitInfo{};
1150 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1151 submitInfo.commandBufferCount = 1;
1152 submitInfo.pCommandBuffers = &commandBuffer;
1154 if (vkQueueSubmit(
graphics_queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
1156 throw mxvk::Exception(
"Failed to submit command buffer");
1160 throw mxvk::Exception(
"Failed to wait for graphics queue idle");
1166 void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &memory)
const {
1167 VkImageCreateInfo imageInfo{};
1168 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1169 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1170 imageInfo.extent.width = width;
1171 imageInfo.extent.height = height;
1172 imageInfo.extent.depth = 1;
1173 imageInfo.mipLevels = 1;
1174 imageInfo.arrayLayers = 1;
1175 imageInfo.format = format;
1176 imageInfo.tiling = tiling;
1177 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1178 imageInfo.usage = usage;
1179 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1180 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1182 if (vkCreateImage(
device, &imageInfo,
nullptr, &image) != VK_SUCCESS) {
1183 throw mxvk::Exception(
"Failed to create image");
1186 VkMemoryRequirements memRequirements{};
1187 vkGetImageMemoryRequirements(
device, image, &memRequirements);
1189 VkMemoryAllocateInfo allocInfo{};
1190 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1191 allocInfo.allocationSize = memRequirements.size;
1194 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1195 if (vkAllocateMemory(
device, &allocInfo,
nullptr, &memory) != VK_SUCCESS) {
1196 throw mxvk::Exception(
"Failed to allocate image memory");
1198 if (vkBindImageMemory(
device, image, memory, 0) != VK_SUCCESS) {
1199 throw mxvk::Exception(
"Failed to bind image memory");
1202 if (memory != VK_NULL_HANDLE) {
1203 vkFreeMemory(
device, memory,
nullptr);
1204 memory = VK_NULL_HANDLE;
1206 if (image != VK_NULL_HANDLE) {
1207 vkDestroyImage(
device, image,
nullptr);
1208 image = VK_NULL_HANDLE;
1214 [[nodiscard]] VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect)
const {
1215 VkImageViewCreateInfo viewInfo{};
1216 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1217 viewInfo.image = image;
1218 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1219 viewInfo.format = format;
1220 viewInfo.subresourceRange.aspectMask = aspect;
1221 viewInfo.subresourceRange.baseMipLevel = 0;
1222 viewInfo.subresourceRange.levelCount = 1;
1223 viewInfo.subresourceRange.baseArrayLayer = 0;
1224 viewInfo.subresourceRange.layerCount = 1;
1226 VkImageView imageView = VK_NULL_HANDLE;
1227 if (vkCreateImageView(
device, &viewInfo,
nullptr, &imageView) != VK_SUCCESS) {
1228 throw mxvk::Exception(
"Failed to create image view");
1233 void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
1234 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1236 VkImageMemoryBarrier barrier{};
1237 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1238 barrier.oldLayout = oldLayout;
1239 barrier.newLayout = newLayout;
1240 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1241 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1242 barrier.image = image;
1243 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1244 barrier.subresourceRange.baseMipLevel = 0;
1245 barrier.subresourceRange.levelCount = 1;
1246 barrier.subresourceRange.baseArrayLayer = 0;
1247 barrier.subresourceRange.layerCount = 1;
1249 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1250 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1252 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1253 barrier.srcAccessMask = 0;
1254 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1255 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1256 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1257 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1258 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1259 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1260 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1261 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1263 throw mxvk::Exception(
"Unsupported image layout transition");
1266 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1268 endSingleTimeCommands(commandBuffer);
1271 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
1272 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1274 VkBufferImageCopy region{};
1275 region.bufferOffset = 0;
1276 region.bufferRowLength = 0;
1277 region.bufferImageHeight = 0;
1278 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1279 region.imageSubresource.mipLevel = 0;
1280 region.imageSubresource.baseArrayLayer = 0;
1281 region.imageSubresource.layerCount = 1;
1282 region.imageOffset = {0, 0, 0};
1283 region.imageExtent = {width, height, 1};
1285 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
1287 endSingleTimeCommands(commandBuffer);
1290 bool starfieldInitialized =
false;
1292 std::vector<Star> stars{};
1293 float atmosphericTwinkle = 1.0f;
1294 float lightPollution = 0.0f;
1296 VkImage starTexture = VK_NULL_HANDLE;
1297 VkDeviceMemory starTextureMemory = VK_NULL_HANDLE;
1298 VkImageView starTextureView = VK_NULL_HANDLE;
1299 VkSampler starSampler = VK_NULL_HANDLE;
1301 VkBuffer starVertexBuffer = VK_NULL_HANDLE;
1302 VkDeviceMemory starVertexBufferMemory = VK_NULL_HANDLE;
1303 void *starVertexBufferMapped =
nullptr;
1305 VkDescriptorSetLayout starDescriptorSetLayout = VK_NULL_HANDLE;
1306 VkDescriptorPool starDescriptorPool = VK_NULL_HANDLE;
1307 std::vector<VkDescriptorSet> starDescriptorSets{};
1308 std::vector<VkBuffer> starUniformBuffers{};
1309 std::vector<VkDeviceMemory> starUniformBufferMemories{};
1310 std::vector<void *> starUniformBufferMapped{};
1311 VkPipeline starPipeline = VK_NULL_HANDLE;
1312 VkPipelineLayout starPipelineLayout = VK_NULL_HANDLE;
1313 Uint32 lastStarUpdateTime = 0;
1315 VkBuffer flameVertexBuffer = VK_NULL_HANDLE;
1316 VkDeviceMemory flameVertexBufferMemory = VK_NULL_HANDLE;
1317 VkPipeline flamePipeline = VK_NULL_HANDLE;
1318 VkPipelineLayout flamePipelineLayout = VK_NULL_HANDLE;
1319 uint32_t flameVertexCount = 0;
1321 std::string assetRoot;
1322 std::string dataRoot;
1323 mxvk::VKAbstractModel model{};
1324 std::chrono::steady_clock::time_point start{std::chrono::steady_clock::now()};
1325 bool mouseDragging =
false;
1328 float mouseYawDegrees = 0.0f;
1329 float mousePitchDegrees = 0.0f;
1330 float mouseSensitivity = 0.35f;
1331 float autoSpinSpeed = 0.65f;