263 PongWindow(
const std::string &path,
int width,
int height,
bool fullscreen,
bool enable_vsync)
265 assetRoot((path.empty() || path ==
".") ? std::string(
pong_ASSET_DIR) : path),
266 dataRoot(assetRoot +
"/data"),
267 shaderRoot(dataRoot),
268 paddle1(glm::vec3(-1.5f, 0.0f, 0.0f), glm::vec3(0.1f, 0.4f, 0.1f)),
269 paddle2(glm::vec3(1.5f, 0.0f, 0.0f), glm::vec3(0.1f, 0.4f, 0.1f)),
270 ball(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.5f, 0.3f, 0.0f), 0.05f),
271 fallbackWidth(width),
272 fallbackHeight(height) {
273 std::srand(
static_cast<unsigned>(std::time(
nullptr)));
274 setFont(dataRoot +
"/font.ttf", 24);
277 createParticleBuffer();
278 initStarfield(30000);
280 tryOpenFirstGamepad();
284 if (
device != VK_NULL_HANDLE) {
288 cleanupStarSwapchainResources();
289 cleanupParticleResources();
290 cleanupStarResources();
291 paddleModel1.cleanup(
this);
292 paddleModel2.cleanup(
this);
293 ballModel.cleanup(
this);
297 cleanupStarSwapchainResources();
301 paddleModel1.resize(
this);
302 paddleModel2.resize(
this);
303 ballModel.resize(
this);
304 createStarSwapchainResources();
308 if (e.type == SDL_EVENT_QUIT) {
313 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
314 openGamepad(e.gdevice.which);
318 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
319 if (gamepad !=
nullptr && e.gdevice.which == gamepadId) {
321 tryOpenFirstGamepad();
326 if (e.type == SDL_EVENT_KEY_DOWN) {
329 wireframe = !wireframe;
334 gridYRotation = 0.0f;
347 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
348 if (e.button.button == SDL_BUTTON_LEFT || e.button.button == SDL_BUTTON_RIGHT) {
349 mouseDragging =
true;
350 lastMouseX =
static_cast<int>(e.button.x);
351 lastMouseY =
static_cast<int>(e.button.y);
356 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) {
357 if (e.button.button == SDL_BUTTON_LEFT || e.button.button == SDL_BUTTON_RIGHT) {
358 mouseDragging =
false;
363 if (e.type == SDL_EVENT_MOUSE_MOTION) {
365 const int deltaX =
static_cast<int>(e.motion.x) - lastMouseX;
366 const int deltaY =
static_cast<int>(e.motion.y) - lastMouseY;
368 gridYRotation +=
static_cast<float>(deltaX) * mouseSensitivity;
369 gridRotation +=
static_cast<float>(deltaY) * mouseSensitivity;
371 gridRotation = std::clamp(gridRotation, -89.0f, 89.0f);
373 lastMouseX =
static_cast<int>(e.motion.x);
374 lastMouseY =
static_cast<int>(e.motion.y);
376 const int renderH = std::max(1, fallbackHeight);
377 const float normalizedY = (
static_cast<float>(e.motion.y) /
static_cast<float>(renderH)) * 2.0f - 1.0f;
378 paddle1.position.y = -normalizedY;
379 clampPaddle(paddle1);
384 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
385 const float delta = (e.wheel.y != 0.0f) ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
386 cameraZ -= delta * 0.5f;
387 cameraZ = std::clamp(cameraZ, 1.0f, 20.0f);
391 if (e.type == SDL_EVENT_FINGER_MOTION) {
392 const float normalizedY = e.tfinger.y * 2.0f - 1.0f;
393 paddle1.position.y = -normalizedY;
394 clampPaddle(paddle1);
399 const auto currentTime = std::chrono::steady_clock::now();
400 float deltaTime = std::chrono::duration<float>(currentTime - lastFrameTime).count();
401 lastFrameTime = currentTime;
403 if (deltaTime > 0.1f) {
408 if (extent.width > 0U) {
409 fallbackWidth =
static_cast<int>(extent.width);
411 if (extent.height > 0U) {
412 fallbackHeight =
static_cast<int>(extent.height);
415 updateFromKeyboard(deltaTime);
416 updateFromGamepad(deltaTime);
420 paddle1.update(deltaTime);
421 paddle2.update(deltaTime);
422 ball.update(deltaTime, paddle1, paddle2, score1, score2);
424 if (ball.hitPaddle1) {
425 spawnBurst(ball.lastImpactPos, glm::vec3(1.0f, 0.0f, 0.0f), glm::vec4(0.3f, 0.6f, 1.0f, 1.0f));
426 playPaddleHitSound();
428 if (ball.hitPaddle2) {
429 spawnBurst(ball.lastImpactPos, glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec4(1.0f, 0.3f, 0.3f, 1.0f));
430 playPaddleHitSound();
433 updateParticles(deltaTime);
443 if (extent.width == 0U || extent.height == 0U) {
447 drawStarfield(cmd, imageIndex, extent);
449 const glm::mat4 view = buildViewMatrix();
450 glm::mat4 proj = glm::perspective(
452 static_cast<float>(extent.width) /
static_cast<float>(extent.height),
457 drawModel(cmd, imageIndex, paddleModel1, paddle1.modelMatrix(), glm::vec3(0.3f, 0.6f, 1.0f), view, proj);
458 drawModel(cmd, imageIndex, paddleModel2, paddle2.modelMatrix(), glm::vec3(1.0f, 0.3f, 0.3f), view, proj);
459 drawModel(cmd, imageIndex, ballModel, ball.modelMatrix(), glm::vec3(1.0f, 1.0f, 1.0f), view, proj);
461 drawParticles(cmd, imageIndex, extent, view, proj);
465 static constexpr int maxParticles = 500;
467 std::string assetRoot;
468 std::string dataRoot;
469 std::string shaderRoot;
477 mxvk::VKAbstractModel ballModel{};
479#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
480 std::unique_ptr<mxvk::VK_Mixer> mixer{};
481 int paddleHitSound = -1;
487 float gridRotation = 0.0f;
488 float gridYRotation = 0.0f;
489 float rotationSpeed = 50.0f;
490 float cameraZ = 5.0f;
491 float mouseSensitivity = 0.5f;
493 bool wireframe =
false;
494 bool mouseDragging =
false;
498 SDL_Gamepad *gamepad =
nullptr;
499 SDL_JoystickID gamepadId = 0;
500 static constexpr float controllerDeadzone = 8000.0f;
501 static constexpr float controllerMax = 32767.0f;
503 int fallbackWidth = 1280;
504 int fallbackHeight = 720;
506 std::chrono::steady_clock::time_point lastFrameTime = std::chrono::steady_clock::now();
507 float fpsAccumulator = 0.0f;
508 int fpsFrameCounter = 0;
509 float fpsValue = 0.0f;
511 std::vector<Particle> particles{};
512 uint32_t activeParticleCount = 0;
513 void *mappedParticleData =
nullptr;
514 VkBuffer particleBuffer = VK_NULL_HANDLE;
515 VkDeviceMemory particleBufferMemory = VK_NULL_HANDLE;
516 VkPipeline particlePipeline = VK_NULL_HANDLE;
517 VkPipelineLayout particlePipelineLayout = VK_NULL_HANDLE;
518 VkDescriptorPool particleDescriptorPool = VK_NULL_HANDLE;
519 std::vector<VkDescriptorSet> particleDescriptorSets{};
520 std::vector<VkBuffer> particleUniformBuffers{};
521 std::vector<VkDeviceMemory> particleUniformBufferMemories{};
522 std::vector<void *> particleUniformBufferMapped{};
524 std::vector<Star> stars{};
526 bool starfieldInitialized =
false;
527 Uint32 lastStarUpdateTime = 0;
528 float atmosphericTwinkle = 1.0f;
529 float lightPollution = 0.1f;
531 VkImage starTexture = VK_NULL_HANDLE;
532 VkDeviceMemory starTextureMemory = VK_NULL_HANDLE;
533 VkImageView starTextureView = VK_NULL_HANDLE;
534 VkSampler starSampler = VK_NULL_HANDLE;
536 VkBuffer starVertexBuffer = VK_NULL_HANDLE;
537 VkDeviceMemory starVertexBufferMemory = VK_NULL_HANDLE;
538 void *starVertexBufferMapped =
nullptr;
540 VkDescriptorSetLayout starDescriptorSetLayout = VK_NULL_HANDLE;
541 VkDescriptorPool starDescriptorPool = VK_NULL_HANDLE;
542 std::vector<VkDescriptorSet> starDescriptorSets{};
544 std::vector<VkBuffer> starUniformBuffers{};
545 std::vector<VkDeviceMemory> starUniformBufferMemories{};
546 std::vector<void *> starUniformBufferMapped{};
548 VkPipeline starPipeline = VK_NULL_HANDLE;
549 VkPipelineLayout starPipelineLayout = VK_NULL_HANDLE;
552 const std::string paddleModelPath = dataRoot +
"/cube.mxmod";
553 const std::string ballModelPath = dataRoot +
"/better_sphere.obj";
554 const std::string shaderVert = shaderRoot +
"/pong_model.vert.spv";
555 const std::string shaderFrag = shaderRoot +
"/pong_model.frag.spv";
556 const std::string paddleManifest = dataRoot +
"/paddle_texture_manifest.txt";
558 paddleModel1.load(
this, paddleModelPath, paddleManifest, dataRoot, 1.0f);
559 paddleModel1.setShaders(
this, shaderVert, shaderFrag);
561 paddleModel2.load(
this, paddleModelPath, paddleManifest, dataRoot, 1.0f);
562 paddleModel2.setShaders(
this, shaderVert, shaderFrag);
564 ballModel.load(
this, ballModelPath,
"", dataRoot, 0.1f);
565 ballModel.setShaders(
this, shaderVert, shaderFrag);
569#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
571 mixer = std::make_unique<mxvk::VK_Mixer>();
572 paddleHitSound = mixer->loadWav(dataRoot +
"/ping.wav");
573 }
catch (
const mxvk::Exception &e) {
574 std::cerr << std::format(
"pong: audio disabled: {}\n", e.
text());
581 void playPaddleHitSound() {
582#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
583 if (mixer !=
nullptr && paddleHitSound >= 0) {
584 mixer->playWav(paddleHitSound, 0, 0);
589 [[nodiscard]] glm::mat4 buildViewMatrix()
const {
590 glm::mat4 view(1.0f);
591 view = glm::translate(view, glm::vec3(0.0f, 0.0f, -cameraZ));
592 view = glm::rotate(view, glm::radians(gridRotation), glm::vec3(1.0f, 0.0f, 0.0f));
593 view = glm::rotate(view, glm::radians(gridYRotation), glm::vec3(0.0f, 1.0f, 0.0f));
597 void drawModel(VkCommandBuffer cmd,
599 mxvk::VKAbstractModel &model,
600 const glm::mat4 &world,
601 const glm::vec3 &color,
602 const glm::mat4 &view,
603 const glm::mat4 &proj) {
604 mxvk::UniformBufferObject ubo{};
608 ubo.
fx = glm::vec4(color, 1.0f);
610 model.
render(cmd, imageIndex, wireframe);
616 paddle1.position.y = 0.0f;
617 paddle2.position.y = 0.0f;
621 static void clampPaddle(Paddle &paddle) {
622 const float halfPaddleHeight = paddle.size.y / 2.0f;
623 paddle.position.y = std::clamp(paddle.position.y, -1.0f + halfPaddleHeight, 1.0f - halfPaddleHeight);
626 [[nodiscard]]
static float normalizeAxisWithDeadzone(
float axisValue) {
627 const float magnitude = std::abs(axisValue);
628 if (magnitude <= controllerDeadzone) {
632 const float normalized = (magnitude - controllerDeadzone) / (controllerMax - controllerDeadzone);
633 return std::copysign(std::clamp(normalized, 0.0f, 1.0f), axisValue);
636 void updateFromKeyboard(
float deltaTime) {
637 const bool *keyState = SDL_GetKeyboardState(
nullptr);
638 if (keyState ==
nullptr) {
642 if (keyState[SDL_SCANCODE_A]) {
643 gridRotation -= rotationSpeed * deltaTime;
645 if (keyState[SDL_SCANCODE_D]) {
646 gridRotation += rotationSpeed * deltaTime;
648 if (keyState[SDL_SCANCODE_S]) {
649 gridYRotation -= rotationSpeed * deltaTime;
651 if (keyState[SDL_SCANCODE_W]) {
652 gridYRotation += rotationSpeed * deltaTime;
654 if (keyState[SDL_SCANCODE_Q]) {
656 gridYRotation = 0.0f;
658 if (keyState[SDL_SCANCODE_PAGEUP]) {
659 cameraZ -= 3.0f * deltaTime;
661 if (keyState[SDL_SCANCODE_PAGEDOWN]) {
662 cameraZ += 3.0f * deltaTime;
664 cameraZ = std::clamp(cameraZ, 1.0f, 20.0f);
666 constexpr float speed = 2.0f;
667 if (keyState[SDL_SCANCODE_UP]) {
668 paddle1.position.y += speed * deltaTime;
670 if (keyState[SDL_SCANCODE_DOWN]) {
671 paddle1.position.y -= speed * deltaTime;
673 clampPaddle(paddle1);
676 void updateFromGamepad(
float deltaTime) {
677 if (gamepad ==
nullptr) {
681 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_BACK)) {
686 constexpr float paddleMoveSpeed = 2.0f;
688 const float leftY = normalizeAxisWithDeadzone(
static_cast<float>(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY)));
689 paddle1.position.y -= leftY * paddleMoveSpeed * deltaTime;
690 clampPaddle(paddle1);
692 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP)) {
693 paddle1.position.y += 2.0f * deltaTime;
695 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN)) {
696 paddle1.position.y -= 2.0f * deltaTime;
698 clampPaddle(paddle1);
700 const float rightX = normalizeAxisWithDeadzone(
static_cast<float>(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX)));
701 const float rightY = normalizeAxisWithDeadzone(
static_cast<float>(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY)));
702 gridRotation += rightX * rotationSpeed * deltaTime;
703 gridYRotation -= rightY * rotationSpeed * deltaTime;
705 const float leftTrigger =
static_cast<float>(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER));
706 const float rightTrigger =
static_cast<float>(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER));
707 if (leftTrigger > controllerDeadzone) {
708 cameraZ += (leftTrigger / controllerMax) * 3.0f * deltaTime;
710 if (rightTrigger > controllerDeadzone) {
711 cameraZ -= (rightTrigger / controllerMax) * 3.0f * deltaTime;
714 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) {
715 cameraZ += 3.0f * deltaTime;
717 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) {
718 cameraZ -= 3.0f * deltaTime;
721 cameraZ = std::clamp(cameraZ, 1.0f, 20.0f);
724 void updateAI(
float deltaTime) {
725 constexpr float paddleSpeed = 0.9f;
726 if (ball.position.y > paddle2.position.y + paddle2.size.y / 4.0f) {
727 paddle2.position.y += paddleSpeed * deltaTime;
729 if (ball.position.y < paddle2.position.y - paddle2.size.y / 4.0f) {
730 paddle2.position.y -= paddleSpeed * deltaTime;
732 clampPaddle(paddle2);
735 void printHud(
float deltaTime) {
736 fpsAccumulator += deltaTime;
738 if (fpsAccumulator >= 0.2f) {
739 fpsValue =
static_cast<float>(fpsFrameCounter) / fpsAccumulator;
740 fpsAccumulator = 0.0f;
744 const SDL_Color white{255, 255, 255, 255};
745 const SDL_Color yellow{255, 255, 0, 255};
748 printText(std::format(
"Player 1: {} : Player 2: {}", score1, score2), 50, 80, yellow);
750 std::ostringstream fpsStream;
751 fpsStream << std::fixed << std::setprecision(1) <<
"FPS: " << fpsValue;
752 const std::string polygonMode = wireframe ?
"WIREFRAME" :
"SOLID";
753 const std::string controllerStatus = (gamepad !=
nullptr) ?
"Controller: Connected" :
"Controller: None";
755 printText(fpsStream.str() +
" | Mode: " + polygonMode, 50, 110, white);
756 printText(controllerStatus, 50, 140, white);
759 void spawnBurst(
const glm::vec3 &impactPos,
const glm::vec3 &normal,
const glm::vec4 &paddleColor) {
760 for (
int i = 0; i < 35 && particles.size() <
static_cast<size_t>(maxParticles); ++i) {
762 p.position = impactPos;
763 p.velocity = normal *
static_cast<float>((std::rand() % 50) / 10.0f + 0.5f) +
765 static_cast<float>((std::rand() % 60) - 30) / 30.0f,
766 static_cast<float>((std::rand() % 40) - 20) / 40.0f);
768 p.color = paddleColor;
769 particles.push_back(p);
773 void updateParticles(
float deltaTime) {
774 if (mappedParticleData ==
nullptr) {
775 activeParticleCount = 0;
779 const glm::vec3 gravity(0.0f, -2.0f, 0.0f);
780 activeParticleCount = 0;
781 auto *particleBufferData =
static_cast<ParticleVertex *
>(mappedParticleData);
784 std::remove_if(particles.begin(), particles.end(), [](
const Particle &p) { return p.life <= 0.0f; }),
787 for (
auto &p : particles) {
788 if (p.life <= 0.0f || activeParticleCount >=
static_cast<uint32_t
>(maxParticles)) {
792 p.velocity += gravity * deltaTime;
793 p.position += p.velocity * deltaTime;
794 p.life -= deltaTime * 1.5f;
797 particleBufferData[activeParticleCount].position = p.position;
798 particleBufferData[activeParticleCount].color = p.color;
799 ++activeParticleCount;
803 void createParticleBuffer() {
804 const VkDeviceSize bufferSize =
sizeof(ParticleVertex) * maxParticles;
807 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
808 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
810 particleBufferMemory);
815 void cleanupParticleResources() {
816 cleanupParticleSwapchainResources();
817 if (mappedParticleData !=
nullptr && particleBufferMemory != VK_NULL_HANDLE) {
818 vkUnmapMemory(
device, particleBufferMemory);
819 mappedParticleData =
nullptr;
821 if (particleBuffer != VK_NULL_HANDLE) {
822 vkDestroyBuffer(
device, particleBuffer,
nullptr);
823 particleBuffer = VK_NULL_HANDLE;
825 if (particleBufferMemory != VK_NULL_HANDLE) {
826 vkFreeMemory(
device, particleBufferMemory,
nullptr);
827 particleBufferMemory = VK_NULL_HANDLE;
831 void destroyParticleUniformBuffers() {
832 for (
size_t i = 0; i < particleUniformBuffers.size(); ++i) {
833 if (particleUniformBufferMapped[i] !=
nullptr && particleUniformBufferMemories[i] != VK_NULL_HANDLE) {
834 vkUnmapMemory(
device, particleUniformBufferMemories[i]);
835 particleUniformBufferMapped[i] =
nullptr;
837 if (particleUniformBuffers[i] != VK_NULL_HANDLE) {
838 vkDestroyBuffer(
device, particleUniformBuffers[i],
nullptr);
839 particleUniformBuffers[i] = VK_NULL_HANDLE;
841 if (particleUniformBufferMemories[i] != VK_NULL_HANDLE) {
842 vkFreeMemory(
device, particleUniformBufferMemories[i],
nullptr);
843 particleUniformBufferMemories[i] = VK_NULL_HANDLE;
846 particleUniformBuffers.clear();
847 particleUniformBufferMemories.clear();
848 particleUniformBufferMapped.clear();
851 void cleanupParticleSwapchainResources() {
852 cleanupParticlePipeline();
853 if (particleDescriptorPool != VK_NULL_HANDLE) {
854 vkDestroyDescriptorPool(
device, particleDescriptorPool,
nullptr);
855 particleDescriptorPool = VK_NULL_HANDLE;
857 particleDescriptorSets.clear();
858 destroyParticleUniformBuffers();
861 void createParticleUniformBuffers() {
863 const VkDeviceSize bufferSize =
sizeof(PongUniformBufferObject);
865 particleUniformBuffers.resize(imageCount, VK_NULL_HANDLE);
866 particleUniformBufferMemories.resize(imageCount, VK_NULL_HANDLE);
867 particleUniformBufferMapped.resize(imageCount,
nullptr);
869 for (
size_t i = 0; i < imageCount; ++i) {
872 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
873 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
874 particleUniformBuffers[i],
875 particleUniformBufferMemories[i]);
876 VK_CHECK_RESULT(vkMapMemory(
device, particleUniformBufferMemories[i], 0, bufferSize, 0, &particleUniformBufferMapped[i]));
880 void createParticleDescriptorPool() {
883 std::array<VkDescriptorPoolSize, 2> poolSizes{};
884 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
885 poolSizes[0].descriptorCount = imageCount;
886 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
887 poolSizes[1].descriptorCount = imageCount;
889 VkDescriptorPoolCreateInfo poolInfo{};
890 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
891 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
892 poolInfo.pPoolSizes = poolSizes.data();
893 poolInfo.maxSets = imageCount;
898 void createParticleDescriptorSets() {
900 std::vector<VkDescriptorSetLayout> layouts(imageCount, starDescriptorSetLayout);
902 VkDescriptorSetAllocateInfo allocInfo{};
903 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
904 allocInfo.descriptorPool = particleDescriptorPool;
905 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(imageCount);
906 allocInfo.pSetLayouts = layouts.data();
908 particleDescriptorSets.resize(imageCount, VK_NULL_HANDLE);
911 for (
size_t i = 0; i < imageCount; ++i) {
912 VkDescriptorImageInfo imageInfo{};
913 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
914 imageInfo.imageView = starTextureView;
915 imageInfo.sampler = starSampler;
917 VkDescriptorBufferInfo bufferInfo{};
918 bufferInfo.buffer = particleUniformBuffers[i];
919 bufferInfo.offset = 0;
920 bufferInfo.range =
sizeof(PongUniformBufferObject);
922 std::array<VkWriteDescriptorSet, 2> writes{};
923 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
924 writes[0].dstSet = particleDescriptorSets[i];
925 writes[0].dstBinding = 0;
926 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
927 writes[0].descriptorCount = 1;
928 writes[0].pImageInfo = &imageInfo;
930 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
931 writes[1].dstSet = particleDescriptorSets[i];
932 writes[1].dstBinding = 1;
933 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
934 writes[1].descriptorCount = 1;
935 writes[1].pBufferInfo = &bufferInfo;
937 vkUpdateDescriptorSets(
device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
941 void updateParticleUniform(uint32_t imageIndex,
942 [[maybe_unused]]
const VkExtent2D &extent,
943 const glm::mat4 &view,
944 const glm::mat4 &proj,
946 if (imageIndex >= particleUniformBufferMapped.size() || particleUniformBufferMapped[imageIndex] ==
nullptr) {
950 PongUniformBufferObject ubo{};
951 ubo.
model = glm::mat4(1.0f);
954 ubo.params = glm::vec4(timeSeconds, 0.0f, 0.0f, 0.0f);
955 ubo.color = glm::vec4(1.0f);
956 std::memcpy(particleUniformBufferMapped[imageIndex], &ubo,
sizeof(ubo));
959 void cleanupParticlePipeline() {
960 if (particlePipeline != VK_NULL_HANDLE) {
961 vkDestroyPipeline(
device, particlePipeline,
nullptr);
962 particlePipeline = VK_NULL_HANDLE;
964 if (particlePipelineLayout != VK_NULL_HANDLE) {
965 vkDestroyPipelineLayout(
device, particlePipelineLayout,
nullptr);
966 particlePipelineLayout = VK_NULL_HANDLE;
970 void drawParticles(VkCommandBuffer cmd, uint32_t imageIndex,
const VkExtent2D &extent,
const glm::mat4 &view,
const glm::mat4 &proj) {
971 if (particlePipeline == VK_NULL_HANDLE || activeParticleCount == 0 || imageIndex >= particleDescriptorSets.size()) {
975 updateParticleUniform(imageIndex, extent, view, proj, SDL_GetTicks() * 0.001f);
977 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, particlePipeline);
979 VkViewport viewport{};
982 viewport.width =
static_cast<float>(extent.width);
983 viewport.height =
static_cast<float>(extent.height);
984 viewport.minDepth = 0.0f;
985 viewport.maxDepth = 1.0f;
986 vkCmdSetViewport(cmd, 0, 1, &viewport);
989 scissor.offset = {0, 0};
990 scissor.extent = extent;
991 vkCmdSetScissor(cmd, 0, 1, &scissor);
993 VkBuffer vertexBuffers[] = {particleBuffer};
994 VkDeviceSize offsets[] = {0};
995 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
997 vkCmdBindDescriptorSets(
999 VK_PIPELINE_BIND_POINT_GRAPHICS,
1000 particlePipelineLayout,
1003 &particleDescriptorSets[imageIndex],
1007 vkCmdDraw(cmd, activeParticleCount, 1, 0, 0);
1010 float randomFloat(
float minv,
float maxv) {
1011 static std::random_device rd;
1012 static std::default_random_engine eng(rd());
1013 std::uniform_real_distribution<float> dist(minv, maxv);
1017 glm::vec3 getStarColor(
float temperature)
const {
1022 if (temperature < 3700.0f) {
1024 g = temperature / 3700.0f * 0.6f;
1026 }
else if (temperature < 5200.0f) {
1028 g = 0.6f + (temperature - 3700.0f) / 1500.0f * 0.4f;
1029 b = (temperature - 3700.0f) / 1500.0f * 0.3f;
1030 }
else if (temperature < 6000.0f) {
1033 b = (temperature - 5200.0f) / 800.0f * 0.7f;
1034 }
else if (temperature < 7500.0f) {
1037 b = 0.7f + (temperature - 6000.0f) / 1500.0f * 0.3f;
1039 r = 0.7f - (temperature - 7500.0f) / 10000.0f * 0.4f;
1040 g = 0.8f + (temperature - 7500.0f) / 10000.0f * 0.2f;
1044 return glm::vec3(r, g, b);
1047 float magnitudeToSize(
float magnitude)
const {
1048 return glm::clamp(15.0f - magnitude * 2.0f, 1.0f, 25.0f);
1051 float magnitudeToAlpha(
float magnitude)
const {
1052 const float alpha = (6.5f - magnitude) / 6.5f;
1053 return glm::clamp(alpha - lightPollution, 0.0f, 1.0f);
1056 void initStarfield(
int numStarsParam) {
1057 if (starfieldInitialized) {
1061 numStars = numStarsParam;
1062 stars.resize(
static_cast<size_t>(numStars));
1064 constexpr float pi = 3.14159265358979323846f;
1065 for (
int i = 0; i < numStars; ++i) {
1066 auto &star = stars[
static_cast<size_t>(i)];
1068 const float theta = randomFloat(0.0f, 2.0f * pi);
1069 const float phi = std::acos(randomFloat(-1.0f, 1.0f));
1070 const float radius = randomFloat(50.0f, 200.0f);
1072 star.x = radius * std::sin(phi) * std::cos(theta);
1073 star.y = radius * std::sin(phi) * std::sin(theta);
1074 star.z = radius * std::cos(phi);
1076 star.vx = randomFloat(-0.001f, 0.001f);
1077 star.vy = randomFloat(-0.001f, 0.001f);
1078 star.vz = randomFloat(-0.001f, 0.001f);
1080 const float r = randomFloat(0.0f, 1.0f);
1082 star.magnitude = randomFloat(-1.0f, 2.0f);
1084 }
else if (r < 0.3f) {
1085 star.magnitude = randomFloat(2.0f, 4.0f);
1088 star.magnitude = randomFloat(4.0f, 6.5f);
1092 if (star.starType == 1) {
1093 star.temperature = randomFloat(3000.0f, 5000.0f);
1094 }
else if (star.starType == 0) {
1095 star.temperature = randomFloat(4000.0f, 8000.0f);
1097 star.temperature = randomFloat(2500.0f, 4000.0f);
1100 star.twinkle = randomFloat(0.5f, 3.0f);
1101 star.size = magnitudeToSize(star.magnitude);
1102 star.isConstellation = (star.magnitude < 3.0f) && (randomFloat(0.0f, 1.0f) < 0.3f);
1105 createStarTexture();
1106 createStarVertexBuffer();
1107 createStarSwapchainResources();
1109 lastStarUpdateTime = SDL_GetTicks();
1110 starfieldInitialized =
true;
1113 void cleanupStarResources() {
1114 if (starVertexBufferMapped !=
nullptr && starVertexBufferMemory != VK_NULL_HANDLE) {
1115 vkUnmapMemory(
device, starVertexBufferMemory);
1116 starVertexBufferMapped =
nullptr;
1118 if (starVertexBuffer != VK_NULL_HANDLE) {
1119 vkDestroyBuffer(
device, starVertexBuffer,
nullptr);
1120 starVertexBuffer = VK_NULL_HANDLE;
1122 if (starVertexBufferMemory != VK_NULL_HANDLE) {
1123 vkFreeMemory(
device, starVertexBufferMemory,
nullptr);
1124 starVertexBufferMemory = VK_NULL_HANDLE;
1127 if (starSampler != VK_NULL_HANDLE) {
1128 vkDestroySampler(
device, starSampler,
nullptr);
1129 starSampler = VK_NULL_HANDLE;
1131 if (starTextureView != VK_NULL_HANDLE) {
1132 vkDestroyImageView(
device, starTextureView,
nullptr);
1133 starTextureView = VK_NULL_HANDLE;
1135 if (starTexture != VK_NULL_HANDLE) {
1136 vkDestroyImage(
device, starTexture,
nullptr);
1137 starTexture = VK_NULL_HANDLE;
1139 if (starTextureMemory != VK_NULL_HANDLE) {
1140 vkFreeMemory(
device, starTextureMemory,
nullptr);
1141 starTextureMemory = VK_NULL_HANDLE;
1145 void cleanupStarSwapchainResources() {
1146 cleanupParticleSwapchainResources();
1148 if (starPipeline != VK_NULL_HANDLE) {
1149 vkDestroyPipeline(
device, starPipeline,
nullptr);
1150 starPipeline = VK_NULL_HANDLE;
1152 if (starPipelineLayout != VK_NULL_HANDLE) {
1153 vkDestroyPipelineLayout(
device, starPipelineLayout,
nullptr);
1154 starPipelineLayout = VK_NULL_HANDLE;
1156 if (starDescriptorPool != VK_NULL_HANDLE) {
1157 vkDestroyDescriptorPool(
device, starDescriptorPool,
nullptr);
1158 starDescriptorPool = VK_NULL_HANDLE;
1160 if (starDescriptorSetLayout != VK_NULL_HANDLE) {
1161 vkDestroyDescriptorSetLayout(
device, starDescriptorSetLayout,
nullptr);
1162 starDescriptorSetLayout = VK_NULL_HANDLE;
1165 destroyStarUniformBuffers();
1166 starDescriptorSets.clear();
1169 void createStarSwapchainResources() {
1170 if (!starfieldInitialized && stars.empty()) {
1173 createStarDescriptorSetLayout();
1174 createStarUniformBuffers();
1175 createStarDescriptorPool();
1176 createStarDescriptorSets();
1177 createStarPipeline();
1178 createParticleUniformBuffers();
1179 createParticleDescriptorPool();
1180 createParticleDescriptorSets();
1181 createParticlePipeline();
1184 void createStarTexture() {
1185 SDL_Surface *starImg =
mxvk::LoadPNG((dataRoot +
"/star.png").c_str());
1186 if (starImg ==
nullptr) {
1187 throw mxvk::Exception(
"Failed to load star.png texture");
1190 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(starImg->w) *
static_cast<VkDeviceSize
>(starImg->h) * 4U;
1192 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1193 VkDeviceMemory stagingBufferMemory = VK_NULL_HANDLE;
1196 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1197 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1199 stagingBufferMemory);
1201 void *data =
nullptr;
1203 std::memcpy(data, starImg->pixels,
static_cast<size_t>(imageSize));
1204 vkUnmapMemory(
device, stagingBufferMemory);
1207 static_cast<uint32_t
>(starImg->w),
1208 static_cast<uint32_t
>(starImg->h),
1209 VK_FORMAT_R8G8B8A8_UNORM,
1210 VK_IMAGE_TILING_OPTIMAL,
1211 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1212 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
1216 transitionImageLayout(starTexture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1217 copyBufferToImage(stagingBuffer, starTexture,
static_cast<uint32_t
>(starImg->w),
static_cast<uint32_t
>(starImg->h));
1218 transitionImageLayout(starTexture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1220 vkDestroyBuffer(
device, stagingBuffer,
nullptr);
1221 vkFreeMemory(
device, stagingBufferMemory,
nullptr);
1223 starTextureView = createImageView(starTexture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1225 VkSamplerCreateInfo samplerInfo{};
1226 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1227 samplerInfo.magFilter = VK_FILTER_LINEAR;
1228 samplerInfo.minFilter = VK_FILTER_LINEAR;
1229 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1230 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1231 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1232 samplerInfo.anisotropyEnable = VK_FALSE;
1233 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1234 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1235 samplerInfo.compareEnable = VK_FALSE;
1236 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1237 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1240 SDL_DestroySurface(starImg);
1243 void createStarVertexBuffer() {
1244 const VkDeviceSize bufferSize =
sizeof(StarVertex) *
static_cast<VkDeviceSize
>(numStars);
1247 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
1248 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1250 starVertexBufferMemory);
1252 VK_CHECK_RESULT(vkMapMemory(
device, starVertexBufferMemory, 0, bufferSize, 0, &starVertexBufferMapped));
1255 void createStarDescriptorSetLayout() {
1256 VkDescriptorSetLayoutBinding samplerBinding{};
1257 samplerBinding.binding = 0;
1258 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1259 samplerBinding.descriptorCount = 1;
1260 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
1262 VkDescriptorSetLayoutBinding uboBinding{};
1263 uboBinding.binding = 1;
1264 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1265 uboBinding.descriptorCount = 1;
1266 uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1268 std::array<VkDescriptorSetLayoutBinding, 2> bindings = {samplerBinding, uboBinding};
1269 VkDescriptorSetLayoutCreateInfo layoutInfo{};
1270 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1271 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
1272 layoutInfo.pBindings = bindings.data();
1273 VK_CHECK_RESULT(vkCreateDescriptorSetLayout(
device, &layoutInfo,
nullptr, &starDescriptorSetLayout));
1276 void createStarUniformBuffers() {
1278 const VkDeviceSize bufferSize =
sizeof(PongUniformBufferObject);
1280 starUniformBuffers.resize(imageCount, VK_NULL_HANDLE);
1281 starUniformBufferMemories.resize(imageCount, VK_NULL_HANDLE);
1282 starUniformBufferMapped.resize(imageCount,
nullptr);
1284 for (
size_t i = 0; i < imageCount; ++i) {
1287 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
1288 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1289 starUniformBuffers[i],
1290 starUniformBufferMemories[i]);
1291 VK_CHECK_RESULT(vkMapMemory(
device, starUniformBufferMemories[i], 0, bufferSize, 0, &starUniformBufferMapped[i]));
1295 void destroyStarUniformBuffers() {
1296 for (
size_t i = 0; i < starUniformBuffers.size(); ++i) {
1297 if (starUniformBufferMapped[i] !=
nullptr && starUniformBufferMemories[i] != VK_NULL_HANDLE) {
1298 vkUnmapMemory(
device, starUniformBufferMemories[i]);
1299 starUniformBufferMapped[i] =
nullptr;
1301 if (starUniformBuffers[i] != VK_NULL_HANDLE) {
1302 vkDestroyBuffer(
device, starUniformBuffers[i],
nullptr);
1303 starUniformBuffers[i] = VK_NULL_HANDLE;
1305 if (starUniformBufferMemories[i] != VK_NULL_HANDLE) {
1306 vkFreeMemory(
device, starUniformBufferMemories[i],
nullptr);
1307 starUniformBufferMemories[i] = VK_NULL_HANDLE;
1310 starUniformBuffers.clear();
1311 starUniformBufferMemories.clear();
1312 starUniformBufferMapped.clear();
1315 void createStarDescriptorPool() {
1318 std::array<VkDescriptorPoolSize, 2> poolSizes{};
1319 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1320 poolSizes[0].descriptorCount = imageCount;
1321 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1322 poolSizes[1].descriptorCount = imageCount;
1324 VkDescriptorPoolCreateInfo poolInfo{};
1325 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1326 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
1327 poolInfo.pPoolSizes = poolSizes.data();
1328 poolInfo.maxSets = imageCount;
1333 void createStarDescriptorSets() {
1335 std::vector<VkDescriptorSetLayout> layouts(imageCount, starDescriptorSetLayout);
1337 VkDescriptorSetAllocateInfo allocInfo{};
1338 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1339 allocInfo.descriptorPool = starDescriptorPool;
1340 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(imageCount);
1341 allocInfo.pSetLayouts = layouts.data();
1343 starDescriptorSets.resize(imageCount, VK_NULL_HANDLE);
1346 for (
size_t i = 0; i < imageCount; ++i) {
1347 VkDescriptorImageInfo imageInfo{};
1348 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1349 imageInfo.imageView = starTextureView;
1350 imageInfo.sampler = starSampler;
1352 VkDescriptorBufferInfo bufferInfo{};
1353 bufferInfo.buffer = starUniformBuffers[i];
1354 bufferInfo.offset = 0;
1355 bufferInfo.range =
sizeof(PongUniformBufferObject);
1357 std::array<VkWriteDescriptorSet, 2> writes{};
1358 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1359 writes[0].dstSet = starDescriptorSets[i];
1360 writes[0].dstBinding = 0;
1361 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1362 writes[0].descriptorCount = 1;
1363 writes[0].pImageInfo = &imageInfo;
1365 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1366 writes[1].dstSet = starDescriptorSets[i];
1367 writes[1].dstBinding = 1;
1368 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1369 writes[1].descriptorCount = 1;
1370 writes[1].pBufferInfo = &bufferInfo;
1372 vkUpdateDescriptorSets(
device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
1376 void createStarPipeline() {
1377 const std::vector<char> vertShaderCode =
loadSpv(dataRoot +
"/star_vert.spv");
1378 const std::vector<char> fragShaderCode =
loadSpv(dataRoot +
"/star_frag.spv");
1381 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
1386 VkPipelineShaderStageCreateInfo vertStage{};
1387 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1388 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1389 vertStage.module = vertShaderModule;
1390 vertStage.pName =
"main";
1392 VkPipelineShaderStageCreateInfo fragStage{};
1393 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1394 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1395 fragStage.module = fragShaderModule;
1396 fragStage.pName =
"main";
1398 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
1400 VkVertexInputBindingDescription bindingDescription{};
1401 bindingDescription.binding = 0;
1402 bindingDescription.stride =
sizeof(StarVertex);
1403 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1405 std::array<VkVertexInputAttributeDescription, 3> attributes{};
1406 attributes[0].binding = 0;
1407 attributes[0].location = 0;
1408 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
1409 attributes[0].offset = offsetof(StarVertex, pos);
1411 attributes[1].binding = 0;
1412 attributes[1].location = 1;
1413 attributes[1].format = VK_FORMAT_R32_SFLOAT;
1414 attributes[1].offset = offsetof(StarVertex, size);
1416 attributes[2].binding = 0;
1417 attributes[2].location = 2;
1418 attributes[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1419 attributes[2].offset = offsetof(StarVertex, color);
1421 VkPipelineVertexInputStateCreateInfo vertexInput{};
1422 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1423 vertexInput.vertexBindingDescriptionCount = 1;
1424 vertexInput.pVertexBindingDescriptions = &bindingDescription;
1425 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
1426 vertexInput.pVertexAttributeDescriptions = attributes.data();
1428 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1429 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1430 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
1431 inputAssembly.primitiveRestartEnable = VK_FALSE;
1433 VkPipelineViewportStateCreateInfo viewportState{};
1434 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1435 viewportState.viewportCount = 1;
1436 viewportState.scissorCount = 1;
1438 std::array<VkDynamicState, 2> dynamicStates = {
1439 VK_DYNAMIC_STATE_VIEWPORT,
1440 VK_DYNAMIC_STATE_SCISSOR,
1442 VkPipelineDynamicStateCreateInfo dynamicInfo{};
1443 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1444 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1445 dynamicInfo.pDynamicStates = dynamicStates.data();
1447 VkPipelineRasterizationStateCreateInfo rasterizer{};
1448 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1449 rasterizer.depthClampEnable = VK_FALSE;
1450 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1451 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1452 rasterizer.lineWidth = 1.0f;
1453 rasterizer.cullMode = VK_CULL_MODE_NONE;
1454 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1455 rasterizer.depthBiasEnable = VK_FALSE;
1457 VkPipelineMultisampleStateCreateInfo multisampling{};
1458 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1459 multisampling.sampleShadingEnable = VK_FALSE;
1460 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1462 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1463 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1464 depthStencil.depthTestEnable = VK_FALSE;
1465 depthStencil.depthWriteEnable = VK_FALSE;
1466 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1467 depthStencil.depthBoundsTestEnable = VK_FALSE;
1468 depthStencil.stencilTestEnable = VK_FALSE;
1470 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
1471 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
1472 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1473 colorBlendAttachment.blendEnable = VK_TRUE;
1474 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1475 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1476 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
1477 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1478 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
1479 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
1481 VkPipelineColorBlendStateCreateInfo colorBlending{};
1482 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1483 colorBlending.logicOpEnable = VK_FALSE;
1484 colorBlending.attachmentCount = 1;
1485 colorBlending.pAttachments = &colorBlendAttachment;
1487 VkPipelineLayoutCreateInfo layoutInfo{};
1488 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1489 layoutInfo.setLayoutCount = 1;
1490 layoutInfo.pSetLayouts = &starDescriptorSetLayout;
1491 layoutInfo.pushConstantRangeCount = 0;
1498 VkPipelineRenderingCreateInfo renderingInfo{};
1499 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1500 renderingInfo.colorAttachmentCount = 1;
1501 renderingInfo.pColorAttachmentFormats = &colorFormat;
1502 renderingInfo.depthAttachmentFormat = depthFormat;
1503 renderingInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
1505 VkGraphicsPipelineCreateInfo pipelineInfo{};
1506 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1507 pipelineInfo.pNext = &renderingInfo;
1508 pipelineInfo.stageCount =
static_cast<uint32_t
>(shaderStages.size());
1509 pipelineInfo.pStages = shaderStages.data();
1510 pipelineInfo.pVertexInputState = &vertexInput;
1511 pipelineInfo.pInputAssemblyState = &inputAssembly;
1512 pipelineInfo.pViewportState = &viewportState;
1513 pipelineInfo.pRasterizationState = &rasterizer;
1514 pipelineInfo.pMultisampleState = &multisampling;
1515 pipelineInfo.pDepthStencilState = &depthStencil;
1516 pipelineInfo.pColorBlendState = &colorBlending;
1517 pipelineInfo.pDynamicState = &dynamicInfo;
1518 pipelineInfo.layout = starPipelineLayout;
1519 pipelineInfo.renderPass = VK_NULL_HANDLE;
1520 pipelineInfo.subpass = 0;
1521 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
1522 pipelineInfo.basePipelineIndex = -1;
1524 VK_CHECK_RESULT(vkCreateGraphicsPipelines(
device, VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &starPipeline));
1526 if (fragShaderModule != VK_NULL_HANDLE) {
1527 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
1529 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
1533 if (fragShaderModule != VK_NULL_HANDLE) {
1534 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
1536 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
1539 void updateStarfield(
float deltaTime) {
1540 if (!starfieldInitialized || starVertexBufferMapped ==
nullptr) {
1543 if (deltaTime > 0.1f) {
1547 const float time = SDL_GetTicks() * 0.001f;
1548 auto *vertices =
static_cast<StarVertex *
>(starVertexBufferMapped);
1550 for (
int i = 0; i < numStars; ++i) {
1551 auto &star = stars[
static_cast<size_t>(i)];
1553 star.x += star.vx * deltaTime;
1554 star.y += star.vy * deltaTime;
1555 star.z += star.vz * deltaTime;
1557 vertices[i].pos[0] = star.x;
1558 vertices[i].pos[1] = star.y;
1559 vertices[i].pos[2] = star.z;
1561 float twinkleFactor = 1.0f;
1562 if (atmosphericTwinkle > 0.0f) {
1563 twinkleFactor = 0.7f + 0.3f * std::sin(time * star.twinkle) * atmosphericTwinkle;
1566 float size = star.size * twinkleFactor;
1567 if (star.isConstellation) {
1570 vertices[i].size = size;
1572 const glm::vec3 starColor = getStarColor(star.temperature);
1573 const float alpha = magnitudeToAlpha(star.magnitude) * twinkleFactor;
1575 vertices[i].color[0] = starColor.r;
1576 vertices[i].color[1] = starColor.g;
1577 vertices[i].color[2] = starColor.b;
1578 vertices[i].color[3] = alpha;
1582 void updateStarUniform(uint32_t imageIndex,
1583 [[maybe_unused]]
const VkExtent2D &extent,
1584 const glm::mat4 &view,
1585 const glm::mat4 &proj,
1586 float timeSeconds) {
1587 if (imageIndex >= starUniformBufferMapped.size() || starUniformBufferMapped[imageIndex] ==
nullptr) {
1591 PongUniformBufferObject ubo{};
1592 ubo.model = glm::mat4(1.0f);
1595 ubo.params = glm::vec4(timeSeconds, 0.0f, 0.0f, 0.0f);
1596 ubo.color = glm::vec4(1.0f);
1597 std::memcpy(starUniformBufferMapped[imageIndex], &ubo,
sizeof(ubo));
1600 void drawStarfield(VkCommandBuffer cmd, uint32_t imageIndex,
const VkExtent2D &extent) {
1601 if (!starfieldInitialized || starPipeline == VK_NULL_HANDLE || imageIndex >= starDescriptorSets.size()) {
1605 const Uint32 currentTime = SDL_GetTicks();
1606 const float deltaTime =
static_cast<float>(currentTime - lastStarUpdateTime) / 1000.0f;
1607 lastStarUpdateTime = currentTime;
1608 updateStarfield(deltaTime);
1610 const glm::mat4 view = buildViewMatrix();
1611 glm::mat4 proj = glm::perspective(
1612 glm::radians(60.0f),
1613 static_cast<float>(extent.width) /
static_cast<float>(extent.height),
1616 proj[1][1] *= -1.0f;
1618 updateStarUniform(imageIndex, extent, view, proj, SDL_GetTicks() * 0.001f);
1620 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, starPipeline);
1622 VkViewport viewport{};
1625 viewport.width =
static_cast<float>(extent.width);
1626 viewport.height =
static_cast<float>(extent.height);
1627 viewport.minDepth = 0.0f;
1628 viewport.maxDepth = 1.0f;
1629 vkCmdSetViewport(cmd, 0, 1, &viewport);
1632 scissor.offset = {0, 0};
1633 scissor.extent = extent;
1634 vkCmdSetScissor(cmd, 0, 1, &scissor);
1636 VkBuffer vertexBuffers[] = {starVertexBuffer};
1637 VkDeviceSize offsets[] = {0};
1638 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
1640 vkCmdBindDescriptorSets(
1642 VK_PIPELINE_BIND_POINT_GRAPHICS,
1646 &starDescriptorSets[imageIndex],
1650 vkCmdDraw(cmd,
static_cast<uint32_t
>(numStars), 1, 0, 0);
1653 void createParticlePipeline() {
1654 const std::vector<char> vertShaderCode =
loadSpv(dataRoot +
"/particle_vert.spv");
1655 const std::vector<char> fragShaderCode =
loadSpv(dataRoot +
"/particle_frag.spv");
1658 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
1663 VkPipelineShaderStageCreateInfo vertStage{};
1664 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1665 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1666 vertStage.module = vertShaderModule;
1667 vertStage.pName =
"main";
1669 VkPipelineShaderStageCreateInfo fragStage{};
1670 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1671 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1672 fragStage.module = fragShaderModule;
1673 fragStage.pName =
"main";
1675 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
1677 VkVertexInputBindingDescription bindingDescription{};
1678 bindingDescription.binding = 0;
1679 bindingDescription.stride =
sizeof(ParticleVertex);
1680 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1682 std::array<VkVertexInputAttributeDescription, 2> attributes{};
1683 attributes[0].binding = 0;
1684 attributes[0].location = 0;
1685 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
1686 attributes[0].offset = offsetof(ParticleVertex, position);
1688 attributes[1].binding = 0;
1689 attributes[1].location = 1;
1690 attributes[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1691 attributes[1].offset = offsetof(ParticleVertex, color);
1693 VkPipelineVertexInputStateCreateInfo vertexInput{};
1694 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1695 vertexInput.vertexBindingDescriptionCount = 1;
1696 vertexInput.pVertexBindingDescriptions = &bindingDescription;
1697 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
1698 vertexInput.pVertexAttributeDescriptions = attributes.data();
1700 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1701 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1702 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
1703 inputAssembly.primitiveRestartEnable = VK_FALSE;
1705 VkPipelineViewportStateCreateInfo viewportState{};
1706 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1707 viewportState.viewportCount = 1;
1708 viewportState.scissorCount = 1;
1710 std::array<VkDynamicState, 2> dynamicStates = {
1711 VK_DYNAMIC_STATE_VIEWPORT,
1712 VK_DYNAMIC_STATE_SCISSOR,
1714 VkPipelineDynamicStateCreateInfo dynamicInfo{};
1715 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1716 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1717 dynamicInfo.pDynamicStates = dynamicStates.data();
1719 VkPipelineRasterizationStateCreateInfo rasterizer{};
1720 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1721 rasterizer.depthClampEnable = VK_FALSE;
1722 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1723 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1724 rasterizer.lineWidth = 1.0f;
1725 rasterizer.cullMode = VK_CULL_MODE_NONE;
1726 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1727 rasterizer.depthBiasEnable = VK_FALSE;
1729 VkPipelineMultisampleStateCreateInfo multisampling{};
1730 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1731 multisampling.sampleShadingEnable = VK_FALSE;
1732 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1734 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1735 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1736 depthStencil.depthTestEnable = VK_FALSE;
1737 depthStencil.depthWriteEnable = VK_FALSE;
1738 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1739 depthStencil.depthBoundsTestEnable = VK_FALSE;
1740 depthStencil.stencilTestEnable = VK_FALSE;
1742 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
1743 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
1744 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1745 colorBlendAttachment.blendEnable = VK_TRUE;
1746 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1747 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
1748 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
1749 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1750 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
1751 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
1753 VkPipelineColorBlendStateCreateInfo colorBlending{};
1754 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1755 colorBlending.logicOpEnable = VK_FALSE;
1756 colorBlending.attachmentCount = 1;
1757 colorBlending.pAttachments = &colorBlendAttachment;
1759 VkPipelineLayoutCreateInfo layoutInfo{};
1760 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1761 layoutInfo.setLayoutCount = 1;
1762 layoutInfo.pSetLayouts = &starDescriptorSetLayout;
1763 layoutInfo.pushConstantRangeCount = 0;
1770 VkPipelineRenderingCreateInfo renderingInfo{};
1771 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1772 renderingInfo.colorAttachmentCount = 1;
1773 renderingInfo.pColorAttachmentFormats = &colorFormat;
1774 renderingInfo.depthAttachmentFormat = depthFormat;
1775 renderingInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
1777 VkGraphicsPipelineCreateInfo pipelineInfo{};
1778 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1779 pipelineInfo.pNext = &renderingInfo;
1780 pipelineInfo.stageCount =
static_cast<uint32_t
>(shaderStages.size());
1781 pipelineInfo.pStages = shaderStages.data();
1782 pipelineInfo.pVertexInputState = &vertexInput;
1783 pipelineInfo.pInputAssemblyState = &inputAssembly;
1784 pipelineInfo.pViewportState = &viewportState;
1785 pipelineInfo.pRasterizationState = &rasterizer;
1786 pipelineInfo.pMultisampleState = &multisampling;
1787 pipelineInfo.pDepthStencilState = &depthStencil;
1788 pipelineInfo.pColorBlendState = &colorBlending;
1789 pipelineInfo.pDynamicState = &dynamicInfo;
1790 pipelineInfo.layout = particlePipelineLayout;
1791 pipelineInfo.renderPass = VK_NULL_HANDLE;
1792 pipelineInfo.subpass = 0;
1793 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
1794 pipelineInfo.basePipelineIndex = -1;
1796 VK_CHECK_RESULT(vkCreateGraphicsPipelines(
device, VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &particlePipeline));
1798 if (fragShaderModule != VK_NULL_HANDLE) {
1799 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
1801 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
1805 if (fragShaderModule != VK_NULL_HANDLE) {
1806 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
1808 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
1811 bool openGamepad(SDL_JoystickID
id) {
1812 if (gamepad !=
nullptr && gamepadId ==
id) {
1816 gamepad = SDL_OpenGamepad(
id);
1817 if (gamepad ==
nullptr) {
1824 void closeGamepad() {
1825 if (gamepad !=
nullptr) {
1826 SDL_CloseGamepad(gamepad);
1832 void tryOpenFirstGamepad() {
1833 if (gamepad !=
nullptr) {
1837 SDL_JoystickID *ids = SDL_GetGamepads(&count);
1838 if (ids ==
nullptr || count <= 0) {
1839 if (ids !=
nullptr) {
1844 openGamepad(ids[0]);
1848 void createBuffer(VkDeviceSize size,
1849 VkBufferUsageFlags usage,
1850 VkMemoryPropertyFlags properties,
1852 VkDeviceMemory &bufferMemory)
const {
1853 VkBuffer newBuffer = VK_NULL_HANDLE;
1854 VkDeviceMemory newMemory = VK_NULL_HANDLE;
1856 VkBufferCreateInfo bufferInfo{};
1857 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1858 bufferInfo.size = size;
1859 bufferInfo.usage = usage;
1860 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1865 VkMemoryRequirements memRequirements{};
1866 vkGetBufferMemoryRequirements(
device, newBuffer, &memRequirements);
1868 VkMemoryAllocateInfo allocInfo{};
1869 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1870 allocInfo.allocationSize = memRequirements.size;
1871 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1875 if (newBuffer != VK_NULL_HANDLE) {
1876 vkDestroyBuffer(
device, newBuffer,
nullptr);
1878 if (newMemory != VK_NULL_HANDLE) {
1879 vkFreeMemory(
device, newMemory,
nullptr);
1884 if (buffer != VK_NULL_HANDLE) {
1885 vkDestroyBuffer(
device, buffer,
nullptr);
1887 if (bufferMemory != VK_NULL_HANDLE) {
1888 vkFreeMemory(
device, bufferMemory,
nullptr);
1891 bufferMemory = newMemory;
1894 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
1895 VkPhysicalDeviceMemoryProperties memProperties{};
1898 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
1899 if ((typeFilter & (1U << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1904 throw mxvk::Exception(
"Failed to find suitable memory type");
1907 [[nodiscard]] VkCommandBuffer beginSingleTimeCommands()
const {
1908 VkCommandBufferAllocateInfo allocInfo{};
1909 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1910 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1912 allocInfo.commandBufferCount = 1;
1914 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
1917 VkCommandBufferBeginInfo beginInfo{};
1918 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1919 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1922 return commandBuffer;
1925 void endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
1928 VkSubmitInfo submitInfo{};
1929 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1930 submitInfo.commandBufferCount = 1;
1931 submitInfo.pCommandBuffers = &commandBuffer;
1939 void createImage(uint32_t width,
1942 VkImageTiling tiling,
1943 VkImageUsageFlags usage,
1944 VkMemoryPropertyFlags properties,
1946 VkDeviceMemory &memory)
const {
1947 VkImage newImage = VK_NULL_HANDLE;
1948 VkDeviceMemory newMemory = VK_NULL_HANDLE;
1950 VkImageCreateInfo imageInfo{};
1951 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1952 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1953 imageInfo.extent.width = width;
1954 imageInfo.extent.height = height;
1955 imageInfo.extent.depth = 1;
1956 imageInfo.mipLevels = 1;
1957 imageInfo.arrayLayers = 1;
1958 imageInfo.format = format;
1959 imageInfo.tiling = tiling;
1960 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1961 imageInfo.usage = usage;
1962 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1963 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1968 VkMemoryRequirements memRequirements{};
1969 vkGetImageMemoryRequirements(
device, newImage, &memRequirements);
1971 VkMemoryAllocateInfo allocInfo{};
1972 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1973 allocInfo.allocationSize = memRequirements.size;
1974 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1978 if (newImage != VK_NULL_HANDLE) {
1979 vkDestroyImage(
device, newImage,
nullptr);
1981 if (newMemory != VK_NULL_HANDLE) {
1982 vkFreeMemory(
device, newMemory,
nullptr);
1987 if (image != VK_NULL_HANDLE) {
1988 vkDestroyImage(
device, image,
nullptr);
1990 if (memory != VK_NULL_HANDLE) {
1991 vkFreeMemory(
device, memory,
nullptr);
1997 [[nodiscard]] VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect)
const {
1998 VkImageViewCreateInfo viewInfo{};
1999 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2000 viewInfo.image = image;
2001 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
2002 viewInfo.format = format;
2003 viewInfo.subresourceRange.aspectMask = aspect;
2004 viewInfo.subresourceRange.baseMipLevel = 0;
2005 viewInfo.subresourceRange.levelCount = 1;
2006 viewInfo.subresourceRange.baseArrayLayer = 0;
2007 viewInfo.subresourceRange.layerCount = 1;
2009 VkImageView imageView = VK_NULL_HANDLE;
2014 void transitionImageLayout(VkImage image,
2016 VkImageLayout oldLayout,
2017 VkImageLayout newLayout)
const {
2018 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2020 VkImageMemoryBarrier barrier{};
2021 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2022 barrier.oldLayout = oldLayout;
2023 barrier.newLayout = newLayout;
2024 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2025 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2026 barrier.image = image;
2027 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2028 barrier.subresourceRange.baseMipLevel = 0;
2029 barrier.subresourceRange.levelCount = 1;
2030 barrier.subresourceRange.baseArrayLayer = 0;
2031 barrier.subresourceRange.layerCount = 1;
2033 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2034 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2036 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
2037 barrier.srcAccessMask = 0;
2038 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2039 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2040 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2041 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
2042 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2043 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
2044 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2045 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
2047 throw mxvk::Exception(
"Unsupported image layout transition");
2050 vkCmdPipelineBarrier(
2062 endSingleTimeCommands(commandBuffer);
2065 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
2066 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2068 VkBufferImageCopy region{};
2069 region.bufferOffset = 0;
2070 region.bufferRowLength = 0;
2071 region.bufferImageHeight = 0;
2072 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2073 region.imageSubresource.mipLevel = 0;
2074 region.imageSubresource.baseArrayLayer = 0;
2075 region.imageSubresource.layerCount = 1;
2076 region.imageOffset = {0, 0, 0};
2077 region.imageExtent = {width, height, 1};
2079 vkCmdCopyBufferToImage(
2083 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
2087 endSingleTimeCommands(commandBuffer);