259 PongWindow(
const std::string &path,
int width,
int height,
bool fullscreen,
bool enable_vsync) :
mxvk::
VK_Window(
"-[ MXVK Pong ]-", width, height, fullscreen,
MXVK_VALIDATION, enable_vsync), assetRoot((path.empty() || path ==
".") ? std::string(
pong_ASSET_DIR) : path), dataRoot(assetRoot +
"/data"), shaderRoot(dataRoot), paddle1(glm::vec3(-1.5f, 0.0f, 0.0f), glm::vec3(0.1f, 0.4f, 0.1f)), paddle2(glm::vec3(1.5f, 0.0f, 0.0f), glm::vec3(0.1f, 0.4f, 0.1f)), ball(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.5f, 0.3f, 0.0f), 0.05f), fallbackWidth(width), fallbackHeight(height) {
260 std::srand(
static_cast<unsigned>(std::time(
nullptr)));
261 setFont(dataRoot +
"/font.ttf", 24);
264 createParticleBuffer();
265 initStarfield(30000);
267 tryOpenFirstGamepad();
271 if (
device != VK_NULL_HANDLE) {
275 cleanupStarSwapchainResources();
276 cleanupParticleResources();
277 cleanupStarResources();
278 paddleModel1.cleanup(
this);
279 paddleModel2.cleanup(
this);
280 ballModel.cleanup(
this);
286 paddleModel1.resize(
this);
287 paddleModel2.resize(
this);
288 ballModel.resize(
this);
289 createStarSwapchainResources();
293 if (e.type == SDL_EVENT_QUIT) {
298 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
299 openGamepad(e.gdevice.which);
303 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
304 if (gamepad !=
nullptr && e.gdevice.which == gamepadId) {
306 tryOpenFirstGamepad();
311 if (e.type == SDL_EVENT_KEY_DOWN) {
314 wireframe = !wireframe;
319 gridYRotation = 0.0f;
332 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
333 if (e.button.button == SDL_BUTTON_LEFT || e.button.button == SDL_BUTTON_RIGHT) {
334 mouseDragging =
true;
335 lastMouseX =
static_cast<int>(e.button.x);
336 lastMouseY =
static_cast<int>(e.button.y);
341 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) {
342 if (e.button.button == SDL_BUTTON_LEFT || e.button.button == SDL_BUTTON_RIGHT) {
343 mouseDragging =
false;
348 if (e.type == SDL_EVENT_MOUSE_MOTION) {
350 const int deltaX =
static_cast<int>(e.motion.x) - lastMouseX;
351 const int deltaY =
static_cast<int>(e.motion.y) - lastMouseY;
353 gridYRotation +=
static_cast<float>(deltaX) * mouseSensitivity;
354 gridRotation +=
static_cast<float>(deltaY) * mouseSensitivity;
356 gridRotation = std::clamp(gridRotation, -89.0f, 89.0f);
358 lastMouseX =
static_cast<int>(e.motion.x);
359 lastMouseY =
static_cast<int>(e.motion.y);
361 const int renderH = std::max(1, fallbackHeight);
362 const float normalizedY = (
static_cast<float>(e.motion.y) /
static_cast<float>(renderH)) * 2.0f - 1.0f;
363 paddle1.position.y = -normalizedY;
364 clampPaddle(paddle1);
369 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
370 const float delta = (e.wheel.y != 0.0f) ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
371 cameraZ -= delta * 0.5f;
372 cameraZ = std::clamp(cameraZ, 1.0f, 20.0f);
376 if (e.type == SDL_EVENT_FINGER_MOTION) {
377 const float normalizedY = e.tfinger.y * 2.0f - 1.0f;
378 paddle1.position.y = -normalizedY;
379 clampPaddle(paddle1);
384 const auto currentTime = std::chrono::steady_clock::now();
385 float deltaTime = std::chrono::duration<float>(currentTime - lastFrameTime).count();
386 lastFrameTime = currentTime;
388 if (deltaTime > 0.1f) {
393 if (extent.width > 0U) {
394 fallbackWidth =
static_cast<int>(extent.width);
396 if (extent.height > 0U) {
397 fallbackHeight =
static_cast<int>(extent.height);
400 updateFromKeyboard(deltaTime);
401 updateFromGamepad(deltaTime);
405 paddle1.update(deltaTime);
406 paddle2.update(deltaTime);
407 ball.update(deltaTime, paddle1, paddle2, score1, score2);
409 if (ball.hitPaddle1) {
410 spawnBurst(ball.lastImpactPos, glm::vec3(1.0f, 0.0f, 0.0f), glm::vec4(0.3f, 0.6f, 1.0f, 1.0f));
411 playPaddleHitSound();
413 if (ball.hitPaddle2) {
414 spawnBurst(ball.lastImpactPos, glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec4(1.0f, 0.3f, 0.3f, 1.0f));
415 playPaddleHitSound();
418 updateParticles(deltaTime);
428 if (extent.width == 0U || extent.height == 0U) {
432 drawStarfield(cmd, imageIndex, extent);
434 const glm::mat4 view = buildViewMatrix();
435 glm::mat4 proj = glm::perspective(glm::radians(35.0f),
static_cast<float>(extent.width) /
static_cast<float>(extent.height), 0.1f, 100.0f);
438 drawModel(cmd, imageIndex, paddleModel1, paddle1.modelMatrix(), glm::vec3(0.3f, 0.6f, 1.0f), view, proj);
439 drawModel(cmd, imageIndex, paddleModel2, paddle2.modelMatrix(), glm::vec3(1.0f, 0.3f, 0.3f), view, proj);
440 drawModel(cmd, imageIndex, ballModel, ball.modelMatrix(), glm::vec3(1.0f, 1.0f, 1.0f), view, proj);
442 drawParticles(cmd, imageIndex, extent, view, proj);
446 static constexpr int maxParticles = 500;
448 std::string assetRoot;
449 std::string dataRoot;
450 std::string shaderRoot;
458 mxvk::VKAbstractModel ballModel{};
460#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
461 std::unique_ptr<mxvk::VK_Mixer> mixer{};
462 int paddleHitSound = -1;
468 float gridRotation = 0.0f;
469 float gridYRotation = 0.0f;
470 float rotationSpeed = 50.0f;
471 float cameraZ = 5.0f;
472 float mouseSensitivity = 0.5f;
474 bool wireframe =
false;
475 bool mouseDragging =
false;
479 SDL_Gamepad *gamepad =
nullptr;
480 SDL_JoystickID gamepadId = 0;
481 static constexpr float controllerDeadzone = 8000.0f;
482 static constexpr float controllerMax = 32767.0f;
484 int fallbackWidth = 1280;
485 int fallbackHeight = 720;
487 std::chrono::steady_clock::time_point lastFrameTime = std::chrono::steady_clock::now();
488 float fpsAccumulator = 0.0f;
489 int fpsFrameCounter = 0;
490 float fpsValue = 0.0f;
492 std::vector<Particle> particles{};
493 uint32_t activeParticleCount = 0;
494 void *mappedParticleData =
nullptr;
495 VkBuffer particleBuffer = VK_NULL_HANDLE;
496 VkDeviceMemory particleBufferMemory = VK_NULL_HANDLE;
497 VkPipeline particlePipeline = VK_NULL_HANDLE;
498 VkPipelineLayout particlePipelineLayout = VK_NULL_HANDLE;
499 VkDescriptorPool particleDescriptorPool = VK_NULL_HANDLE;
500 std::vector<VkDescriptorSet> particleDescriptorSets{};
501 std::vector<VkBuffer> particleUniformBuffers{};
502 std::vector<VkDeviceMemory> particleUniformBufferMemories{};
503 std::vector<void *> particleUniformBufferMapped{};
505 std::vector<Star> stars{};
507 bool starfieldInitialized =
false;
508 Uint32 lastStarUpdateTime = 0;
509 float atmosphericTwinkle = 1.0f;
510 float lightPollution = 0.1f;
512 VkImage starTexture = VK_NULL_HANDLE;
513 VkDeviceMemory starTextureMemory = VK_NULL_HANDLE;
514 VkImageView starTextureView = VK_NULL_HANDLE;
515 VkSampler starSampler = VK_NULL_HANDLE;
517 VkBuffer starVertexBuffer = VK_NULL_HANDLE;
518 VkDeviceMemory starVertexBufferMemory = VK_NULL_HANDLE;
519 void *starVertexBufferMapped =
nullptr;
521 VkDescriptorSetLayout starDescriptorSetLayout = VK_NULL_HANDLE;
522 VkDescriptorPool starDescriptorPool = VK_NULL_HANDLE;
523 std::vector<VkDescriptorSet> starDescriptorSets{};
525 std::vector<VkBuffer> starUniformBuffers{};
526 std::vector<VkDeviceMemory> starUniformBufferMemories{};
527 std::vector<void *> starUniformBufferMapped{};
529 VkPipeline starPipeline = VK_NULL_HANDLE;
530 VkPipelineLayout starPipelineLayout = VK_NULL_HANDLE;
533 const std::string paddleModelPath = dataRoot +
"/cube.mxmod";
534 const std::string ballModelPath = dataRoot +
"/better_sphere.obj";
535 const std::string shaderVert = shaderRoot +
"/pong_model.vert.spv";
536 const std::string shaderFrag = shaderRoot +
"/pong_model.frag.spv";
537 const std::string paddleManifest = dataRoot +
"/paddle_texture_manifest.txt";
539 paddleModel1.load(
this, paddleModelPath, paddleManifest, dataRoot, 1.0f);
540 paddleModel1.setShaders(
this, shaderVert, shaderFrag);
542 paddleModel2.load(
this, paddleModelPath, paddleManifest, dataRoot, 1.0f);
543 paddleModel2.setShaders(
this, shaderVert, shaderFrag);
545 ballModel.load(
this, ballModelPath,
"", dataRoot, 0.1f);
546 ballModel.setShaders(
this, shaderVert, shaderFrag);
550#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
552 mixer = std::make_unique<mxvk::VK_Mixer>();
553 paddleHitSound = mixer->loadWav(dataRoot +
"/ping.wav");
554 }
catch (
const mxvk::Exception &e) {
555 std::cerr << std::format(
"pong: audio disabled: {}\n", e.
text());
562 void playPaddleHitSound() {
563#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
564 if (mixer !=
nullptr && paddleHitSound >= 0) {
565 mixer->playWav(paddleHitSound, 0, 0);
570 [[nodiscard]] glm::mat4 buildViewMatrix()
const {
571 glm::mat4 view(1.0f);
572 view = glm::translate(view, glm::vec3(0.0f, 0.0f, -cameraZ));
573 view = glm::rotate(view, glm::radians(gridRotation), glm::vec3(1.0f, 0.0f, 0.0f));
574 view = glm::rotate(view, glm::radians(gridYRotation), glm::vec3(0.0f, 1.0f, 0.0f));
578 void drawModel(VkCommandBuffer cmd, uint32_t imageIndex, mxvk::VKAbstractModel &model,
const glm::mat4 &world,
const glm::vec3 &color,
const glm::mat4 &view,
const glm::mat4 &proj) {
579 mxvk::UniformBufferObject ubo{};
583 ubo.
fx = glm::vec4(color, 1.0f);
585 model.
render(cmd, imageIndex, wireframe);
591 paddle1.position.y = 0.0f;
592 paddle2.position.y = 0.0f;
596 static void clampPaddle(Paddle &paddle) {
597 const float halfPaddleHeight = paddle.size.y / 2.0f;
598 paddle.position.y = std::clamp(paddle.position.y, -1.0f + halfPaddleHeight, 1.0f - halfPaddleHeight);
601 [[nodiscard]]
static float normalizeAxisWithDeadzone(
float axisValue) {
602 const float magnitude = std::abs(axisValue);
603 if (magnitude <= controllerDeadzone) {
607 const float normalized = (magnitude - controllerDeadzone) / (controllerMax - controllerDeadzone);
608 return std::copysign(std::clamp(normalized, 0.0f, 1.0f), axisValue);
611 void updateFromKeyboard(
float deltaTime) {
612 const bool *keyState = SDL_GetKeyboardState(
nullptr);
613 if (keyState ==
nullptr) {
617 if (keyState[SDL_SCANCODE_A]) {
618 gridRotation -= rotationSpeed * deltaTime;
620 if (keyState[SDL_SCANCODE_D]) {
621 gridRotation += rotationSpeed * deltaTime;
623 if (keyState[SDL_SCANCODE_S]) {
624 gridYRotation -= rotationSpeed * deltaTime;
626 if (keyState[SDL_SCANCODE_W]) {
627 gridYRotation += rotationSpeed * deltaTime;
629 if (keyState[SDL_SCANCODE_Q]) {
631 gridYRotation = 0.0f;
633 if (keyState[SDL_SCANCODE_PAGEUP]) {
634 cameraZ -= 3.0f * deltaTime;
636 if (keyState[SDL_SCANCODE_PAGEDOWN]) {
637 cameraZ += 3.0f * deltaTime;
639 cameraZ = std::clamp(cameraZ, 1.0f, 20.0f);
641 constexpr float speed = 2.0f;
642 if (keyState[SDL_SCANCODE_UP]) {
643 paddle1.position.y += speed * deltaTime;
645 if (keyState[SDL_SCANCODE_DOWN]) {
646 paddle1.position.y -= speed * deltaTime;
648 clampPaddle(paddle1);
651 void updateFromGamepad(
float deltaTime) {
652 if (gamepad ==
nullptr) {
656 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_BACK)) {
661 constexpr float paddleMoveSpeed = 2.0f;
663 const float leftY = normalizeAxisWithDeadzone(
static_cast<float>(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY)));
664 paddle1.position.y -= leftY * paddleMoveSpeed * deltaTime;
665 clampPaddle(paddle1);
667 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP)) {
668 paddle1.position.y += 2.0f * deltaTime;
670 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN)) {
671 paddle1.position.y -= 2.0f * deltaTime;
673 clampPaddle(paddle1);
675 const float rightX = normalizeAxisWithDeadzone(
static_cast<float>(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX)));
676 const float rightY = normalizeAxisWithDeadzone(
static_cast<float>(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY)));
677 gridRotation += rightX * rotationSpeed * deltaTime;
678 gridYRotation -= rightY * rotationSpeed * deltaTime;
680 const float leftTrigger =
static_cast<float>(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER));
681 const float rightTrigger =
static_cast<float>(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER));
682 if (leftTrigger > controllerDeadzone) {
683 cameraZ += (leftTrigger / controllerMax) * 3.0f * deltaTime;
685 if (rightTrigger > controllerDeadzone) {
686 cameraZ -= (rightTrigger / controllerMax) * 3.0f * deltaTime;
689 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) {
690 cameraZ += 3.0f * deltaTime;
692 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) {
693 cameraZ -= 3.0f * deltaTime;
696 cameraZ = std::clamp(cameraZ, 1.0f, 20.0f);
699 void updateAI(
float deltaTime) {
700 constexpr float paddleSpeed = 0.9f;
701 if (ball.position.y > paddle2.position.y + paddle2.size.y / 4.0f) {
702 paddle2.position.y += paddleSpeed * deltaTime;
704 if (ball.position.y < paddle2.position.y - paddle2.size.y / 4.0f) {
705 paddle2.position.y -= paddleSpeed * deltaTime;
707 clampPaddle(paddle2);
710 void printHud(
float deltaTime) {
711 fpsAccumulator += deltaTime;
713 if (fpsAccumulator >= 0.2f) {
714 fpsValue =
static_cast<float>(fpsFrameCounter) / fpsAccumulator;
715 fpsAccumulator = 0.0f;
719 const SDL_Color white{255, 255, 255, 255};
720 const SDL_Color yellow{255, 255, 0, 255};
723 printText(std::format(
"Player 1: {} : Player 2: {}", score1, score2), 50, 80, yellow);
725 std::ostringstream fpsStream;
726 fpsStream << std::fixed << std::setprecision(1) <<
"FPS: " << fpsValue;
727 const std::string polygonMode = wireframe ?
"WIREFRAME" :
"SOLID";
728 const std::string controllerStatus = (gamepad !=
nullptr) ?
"Controller: Connected" :
"Controller: None";
730 printText(fpsStream.str() +
" | Mode: " + polygonMode, 50, 110, white);
731 printText(controllerStatus, 50, 140, white);
734 void spawnBurst(
const glm::vec3 &impactPos,
const glm::vec3 &normal,
const glm::vec4 &paddleColor) {
735 for (
int i = 0; i < 35 && particles.size() <
static_cast<size_t>(maxParticles); ++i) {
737 p.position = impactPos;
738 p.velocity = normal *
static_cast<float>((std::rand() % 50) / 10.0f + 0.5f) + glm::vec3(0.0f,
static_cast<float>((std::rand() % 60) - 30) / 30.0f,
static_cast<float>((std::rand() % 40) - 20) / 40.0f);
740 p.color = paddleColor;
741 particles.push_back(p);
745 void updateParticles(
float deltaTime) {
746 if (mappedParticleData ==
nullptr) {
747 activeParticleCount = 0;
751 const glm::vec3 gravity(0.0f, -2.0f, 0.0f);
752 activeParticleCount = 0;
753 auto *particleBufferData =
static_cast<ParticleVertex *
>(mappedParticleData);
755 particles.erase(std::remove_if(particles.begin(), particles.end(), [](
const Particle &p) { return p.life <= 0.0f; }), particles.end());
757 for (
auto &p : particles) {
758 if (p.life <= 0.0f || activeParticleCount >=
static_cast<uint32_t
>(maxParticles)) {
762 p.velocity += gravity * deltaTime;
763 p.position += p.velocity * deltaTime;
764 p.life -= deltaTime * 1.5f;
767 particleBufferData[activeParticleCount].position = p.position;
768 particleBufferData[activeParticleCount].color = p.color;
769 ++activeParticleCount;
773 void createParticleBuffer() {
774 const VkDeviceSize bufferSize =
sizeof(ParticleVertex) * maxParticles;
775 createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, particleBuffer, particleBufferMemory);
780 void cleanupParticleResources() {
781 cleanupParticleSwapchainResources();
782 if (mappedParticleData !=
nullptr && particleBufferMemory != VK_NULL_HANDLE) {
783 vkUnmapMemory(
device, particleBufferMemory);
784 mappedParticleData =
nullptr;
786 if (particleBuffer != VK_NULL_HANDLE) {
787 vkDestroyBuffer(
device, particleBuffer,
nullptr);
788 particleBuffer = VK_NULL_HANDLE;
790 if (particleBufferMemory != VK_NULL_HANDLE) {
791 vkFreeMemory(
device, particleBufferMemory,
nullptr);
792 particleBufferMemory = VK_NULL_HANDLE;
796 void destroyParticleUniformBuffers() {
797 for (
size_t i = 0; i < particleUniformBuffers.size(); ++i) {
798 if (particleUniformBufferMapped[i] !=
nullptr && particleUniformBufferMemories[i] != VK_NULL_HANDLE) {
799 vkUnmapMemory(
device, particleUniformBufferMemories[i]);
800 particleUniformBufferMapped[i] =
nullptr;
802 if (particleUniformBuffers[i] != VK_NULL_HANDLE) {
803 vkDestroyBuffer(
device, particleUniformBuffers[i],
nullptr);
804 particleUniformBuffers[i] = VK_NULL_HANDLE;
806 if (particleUniformBufferMemories[i] != VK_NULL_HANDLE) {
807 vkFreeMemory(
device, particleUniformBufferMemories[i],
nullptr);
808 particleUniformBufferMemories[i] = VK_NULL_HANDLE;
811 particleUniformBuffers.clear();
812 particleUniformBufferMemories.clear();
813 particleUniformBufferMapped.clear();
816 void cleanupParticleSwapchainResources() {
817 cleanupParticlePipeline();
818 if (particleDescriptorPool != VK_NULL_HANDLE) {
819 vkDestroyDescriptorPool(
device, particleDescriptorPool,
nullptr);
820 particleDescriptorPool = VK_NULL_HANDLE;
822 particleDescriptorSets.clear();
823 destroyParticleUniformBuffers();
826 void createParticleUniformBuffers() {
828 const VkDeviceSize bufferSize =
sizeof(PongUniformBufferObject);
830 particleUniformBuffers.resize(imageCount, VK_NULL_HANDLE);
831 particleUniformBufferMemories.resize(imageCount, VK_NULL_HANDLE);
832 particleUniformBufferMapped.resize(imageCount,
nullptr);
834 for (
size_t i = 0; i < imageCount; ++i) {
835 createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, particleUniformBuffers[i], particleUniformBufferMemories[i]);
836 VK_CHECK_RESULT(vkMapMemory(
device, particleUniformBufferMemories[i], 0, bufferSize, 0, &particleUniformBufferMapped[i]));
840 void createParticleDescriptorPool() {
843 std::array<VkDescriptorPoolSize, 2> poolSizes{};
844 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
845 poolSizes[0].descriptorCount = imageCount;
846 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
847 poolSizes[1].descriptorCount = imageCount;
849 VkDescriptorPoolCreateInfo poolInfo{};
850 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
851 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
852 poolInfo.pPoolSizes = poolSizes.data();
853 poolInfo.maxSets = imageCount;
858 void createParticleDescriptorSets() {
860 std::vector<VkDescriptorSetLayout> layouts(imageCount, starDescriptorSetLayout);
862 VkDescriptorSetAllocateInfo allocInfo{};
863 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
864 allocInfo.descriptorPool = particleDescriptorPool;
865 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(imageCount);
866 allocInfo.pSetLayouts = layouts.data();
868 particleDescriptorSets.resize(imageCount, VK_NULL_HANDLE);
871 for (
size_t i = 0; i < imageCount; ++i) {
872 VkDescriptorImageInfo imageInfo{};
873 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
874 imageInfo.imageView = starTextureView;
875 imageInfo.sampler = starSampler;
877 VkDescriptorBufferInfo bufferInfo{};
878 bufferInfo.buffer = particleUniformBuffers[i];
879 bufferInfo.offset = 0;
880 bufferInfo.range =
sizeof(PongUniformBufferObject);
882 std::array<VkWriteDescriptorSet, 2> writes{};
883 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
884 writes[0].dstSet = particleDescriptorSets[i];
885 writes[0].dstBinding = 0;
886 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
887 writes[0].descriptorCount = 1;
888 writes[0].pImageInfo = &imageInfo;
890 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
891 writes[1].dstSet = particleDescriptorSets[i];
892 writes[1].dstBinding = 1;
893 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
894 writes[1].descriptorCount = 1;
895 writes[1].pBufferInfo = &bufferInfo;
897 vkUpdateDescriptorSets(
device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
901 void updateParticleUniform(uint32_t imageIndex, [[maybe_unused]]
const VkExtent2D &extent,
const glm::mat4 &view,
const glm::mat4 &proj,
float timeSeconds) {
902 if (imageIndex >= particleUniformBufferMapped.size() || particleUniformBufferMapped[imageIndex] ==
nullptr) {
906 PongUniformBufferObject ubo{};
907 ubo.
model = glm::mat4(1.0f);
910 ubo.params = glm::vec4(timeSeconds, 0.0f, 0.0f, 0.0f);
911 ubo.color = glm::vec4(1.0f);
912 std::memcpy(particleUniformBufferMapped[imageIndex], &ubo,
sizeof(ubo));
915 void cleanupParticlePipeline() {
916 if (particlePipeline != VK_NULL_HANDLE) {
917 vkDestroyPipeline(
device, particlePipeline,
nullptr);
918 particlePipeline = VK_NULL_HANDLE;
920 if (particlePipelineLayout != VK_NULL_HANDLE) {
921 vkDestroyPipelineLayout(
device, particlePipelineLayout,
nullptr);
922 particlePipelineLayout = VK_NULL_HANDLE;
926 void drawParticles(VkCommandBuffer cmd, uint32_t imageIndex,
const VkExtent2D &extent,
const glm::mat4 &view,
const glm::mat4 &proj) {
927 if (particlePipeline == VK_NULL_HANDLE || activeParticleCount == 0 || imageIndex >= particleDescriptorSets.size()) {
931 updateParticleUniform(imageIndex, extent, view, proj, SDL_GetTicks() * 0.001f);
933 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, particlePipeline);
935 VkViewport viewport{};
938 viewport.width =
static_cast<float>(extent.width);
939 viewport.height =
static_cast<float>(extent.height);
940 viewport.minDepth = 0.0f;
941 viewport.maxDepth = 1.0f;
942 vkCmdSetViewport(cmd, 0, 1, &viewport);
945 scissor.offset = {0, 0};
946 scissor.extent = extent;
947 vkCmdSetScissor(cmd, 0, 1, &scissor);
949 VkBuffer vertexBuffers[] = {particleBuffer};
950 VkDeviceSize offsets[] = {0};
951 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
953 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, particlePipelineLayout, 0, 1, &particleDescriptorSets[imageIndex], 0,
nullptr);
955 vkCmdDraw(cmd, activeParticleCount, 1, 0, 0);
958 float randomFloat(
float minv,
float maxv) {
959 static std::random_device rd;
960 static std::default_random_engine eng(rd());
961 std::uniform_real_distribution<float> dist(minv, maxv);
965 glm::vec3 getStarColor(
float temperature)
const {
970 if (temperature < 3700.0f) {
972 g = temperature / 3700.0f * 0.6f;
974 }
else if (temperature < 5200.0f) {
976 g = 0.6f + (temperature - 3700.0f) / 1500.0f * 0.4f;
977 b = (temperature - 3700.0f) / 1500.0f * 0.3f;
978 }
else if (temperature < 6000.0f) {
981 b = (temperature - 5200.0f) / 800.0f * 0.7f;
982 }
else if (temperature < 7500.0f) {
985 b = 0.7f + (temperature - 6000.0f) / 1500.0f * 0.3f;
987 r = 0.7f - (temperature - 7500.0f) / 10000.0f * 0.4f;
988 g = 0.8f + (temperature - 7500.0f) / 10000.0f * 0.2f;
992 return glm::vec3(r, g, b);
995 float magnitudeToSize(
float magnitude)
const {
return glm::clamp(15.0f - magnitude * 2.0f, 1.0f, 25.0f); }
997 float magnitudeToAlpha(
float magnitude)
const {
998 const float alpha = (6.5f - magnitude) / 6.5f;
999 return glm::clamp(alpha - lightPollution, 0.0f, 1.0f);
1002 void initStarfield(
int numStarsParam) {
1003 if (starfieldInitialized) {
1007 numStars = numStarsParam;
1008 stars.resize(
static_cast<size_t>(numStars));
1010 constexpr float pi = 3.14159265358979323846f;
1011 for (
int i = 0; i < numStars; ++i) {
1012 auto &star = stars[
static_cast<size_t>(i)];
1014 const float theta = randomFloat(0.0f, 2.0f * pi);
1015 const float phi = std::acos(randomFloat(-1.0f, 1.0f));
1016 const float radius = randomFloat(50.0f, 200.0f);
1018 star.x = radius * std::sin(phi) * std::cos(theta);
1019 star.y = radius * std::sin(phi) * std::sin(theta);
1020 star.z = radius * std::cos(phi);
1022 star.vx = randomFloat(-0.001f, 0.001f);
1023 star.vy = randomFloat(-0.001f, 0.001f);
1024 star.vz = randomFloat(-0.001f, 0.001f);
1026 const float r = randomFloat(0.0f, 1.0f);
1028 star.magnitude = randomFloat(-1.0f, 2.0f);
1030 }
else if (r < 0.3f) {
1031 star.magnitude = randomFloat(2.0f, 4.0f);
1034 star.magnitude = randomFloat(4.0f, 6.5f);
1038 if (star.starType == 1) {
1039 star.temperature = randomFloat(3000.0f, 5000.0f);
1040 }
else if (star.starType == 0) {
1041 star.temperature = randomFloat(4000.0f, 8000.0f);
1043 star.temperature = randomFloat(2500.0f, 4000.0f);
1046 star.twinkle = randomFloat(0.5f, 3.0f);
1047 star.size = magnitudeToSize(star.magnitude);
1048 star.isConstellation = (star.magnitude < 3.0f) && (randomFloat(0.0f, 1.0f) < 0.3f);
1051 createStarTexture();
1052 createStarVertexBuffer();
1053 createStarSwapchainResources();
1055 lastStarUpdateTime = SDL_GetTicks();
1056 starfieldInitialized =
true;
1059 void cleanupStarResources() {
1060 if (starVertexBufferMapped !=
nullptr && starVertexBufferMemory != VK_NULL_HANDLE) {
1061 vkUnmapMemory(
device, starVertexBufferMemory);
1062 starVertexBufferMapped =
nullptr;
1064 if (starVertexBuffer != VK_NULL_HANDLE) {
1065 vkDestroyBuffer(
device, starVertexBuffer,
nullptr);
1066 starVertexBuffer = VK_NULL_HANDLE;
1068 if (starVertexBufferMemory != VK_NULL_HANDLE) {
1069 vkFreeMemory(
device, starVertexBufferMemory,
nullptr);
1070 starVertexBufferMemory = VK_NULL_HANDLE;
1073 if (starSampler != VK_NULL_HANDLE) {
1074 vkDestroySampler(
device, starSampler,
nullptr);
1075 starSampler = VK_NULL_HANDLE;
1077 if (starTextureView != VK_NULL_HANDLE) {
1078 vkDestroyImageView(
device, starTextureView,
nullptr);
1079 starTextureView = VK_NULL_HANDLE;
1081 if (starTexture != VK_NULL_HANDLE) {
1082 vkDestroyImage(
device, starTexture,
nullptr);
1083 starTexture = VK_NULL_HANDLE;
1085 if (starTextureMemory != VK_NULL_HANDLE) {
1086 vkFreeMemory(
device, starTextureMemory,
nullptr);
1087 starTextureMemory = VK_NULL_HANDLE;
1091 void cleanupStarSwapchainResources() {
1092 cleanupParticleSwapchainResources();
1094 if (starPipeline != VK_NULL_HANDLE) {
1095 vkDestroyPipeline(
device, starPipeline,
nullptr);
1096 starPipeline = VK_NULL_HANDLE;
1098 if (starPipelineLayout != VK_NULL_HANDLE) {
1099 vkDestroyPipelineLayout(
device, starPipelineLayout,
nullptr);
1100 starPipelineLayout = VK_NULL_HANDLE;
1102 if (starDescriptorPool != VK_NULL_HANDLE) {
1103 vkDestroyDescriptorPool(
device, starDescriptorPool,
nullptr);
1104 starDescriptorPool = VK_NULL_HANDLE;
1106 if (starDescriptorSetLayout != VK_NULL_HANDLE) {
1107 vkDestroyDescriptorSetLayout(
device, starDescriptorSetLayout,
nullptr);
1108 starDescriptorSetLayout = VK_NULL_HANDLE;
1111 destroyStarUniformBuffers();
1112 starDescriptorSets.clear();
1115 void createStarSwapchainResources() {
1116 if (!starfieldInitialized && stars.empty()) {
1119 createStarDescriptorSetLayout();
1120 createStarUniformBuffers();
1121 createStarDescriptorPool();
1122 createStarDescriptorSets();
1123 createStarPipeline();
1124 createParticleUniformBuffers();
1125 createParticleDescriptorPool();
1126 createParticleDescriptorSets();
1127 createParticlePipeline();
1130 void createStarTexture() {
1131 SDL_Surface *starImg =
mxvk::LoadPNG((dataRoot +
"/star.png").c_str());
1132 if (starImg ==
nullptr) {
1133 throw mxvk::Exception(
"Failed to load star.png texture");
1136 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(starImg->w) *
static_cast<VkDeviceSize
>(starImg->h) * 4U;
1138 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1139 VkDeviceMemory stagingBufferMemory = VK_NULL_HANDLE;
1140 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
1142 void *data =
nullptr;
1144 std::memcpy(data, starImg->pixels,
static_cast<size_t>(imageSize));
1145 vkUnmapMemory(
device, stagingBufferMemory);
1147 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);
1149 transitionImageLayout(starTexture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1150 copyBufferToImage(stagingBuffer, starTexture,
static_cast<uint32_t
>(starImg->w),
static_cast<uint32_t
>(starImg->h));
1151 transitionImageLayout(starTexture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1153 vkDestroyBuffer(
device, stagingBuffer,
nullptr);
1154 vkFreeMemory(
device, stagingBufferMemory,
nullptr);
1156 starTextureView = createImageView(starTexture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1158 VkSamplerCreateInfo samplerInfo{};
1159 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1160 samplerInfo.magFilter = VK_FILTER_LINEAR;
1161 samplerInfo.minFilter = VK_FILTER_LINEAR;
1162 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1163 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1164 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1165 samplerInfo.anisotropyEnable = VK_FALSE;
1166 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1167 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1168 samplerInfo.compareEnable = VK_FALSE;
1169 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1170 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1173 SDL_DestroySurface(starImg);
1176 void createStarVertexBuffer() {
1177 const VkDeviceSize bufferSize =
sizeof(StarVertex) *
static_cast<VkDeviceSize
>(numStars);
1178 createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, starVertexBuffer, starVertexBufferMemory);
1180 VK_CHECK_RESULT(vkMapMemory(
device, starVertexBufferMemory, 0, bufferSize, 0, &starVertexBufferMapped));
1183 void createStarDescriptorSetLayout() {
1184 VkDescriptorSetLayoutBinding samplerBinding{};
1185 samplerBinding.binding = 0;
1186 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1187 samplerBinding.descriptorCount = 1;
1188 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
1190 VkDescriptorSetLayoutBinding uboBinding{};
1191 uboBinding.binding = 1;
1192 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1193 uboBinding.descriptorCount = 1;
1194 uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1196 std::array<VkDescriptorSetLayoutBinding, 2> bindings = {samplerBinding, uboBinding};
1197 VkDescriptorSetLayoutCreateInfo layoutInfo{};
1198 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1199 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
1200 layoutInfo.pBindings = bindings.data();
1201 VK_CHECK_RESULT(vkCreateDescriptorSetLayout(
device, &layoutInfo,
nullptr, &starDescriptorSetLayout));
1204 void createStarUniformBuffers() {
1206 const VkDeviceSize bufferSize =
sizeof(PongUniformBufferObject);
1208 starUniformBuffers.resize(imageCount, VK_NULL_HANDLE);
1209 starUniformBufferMemories.resize(imageCount, VK_NULL_HANDLE);
1210 starUniformBufferMapped.resize(imageCount,
nullptr);
1212 for (
size_t i = 0; i < imageCount; ++i) {
1213 createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, starUniformBuffers[i], starUniformBufferMemories[i]);
1214 VK_CHECK_RESULT(vkMapMemory(
device, starUniformBufferMemories[i], 0, bufferSize, 0, &starUniformBufferMapped[i]));
1218 void destroyStarUniformBuffers() {
1219 for (
size_t i = 0; i < starUniformBuffers.size(); ++i) {
1220 if (starUniformBufferMapped[i] !=
nullptr && starUniformBufferMemories[i] != VK_NULL_HANDLE) {
1221 vkUnmapMemory(
device, starUniformBufferMemories[i]);
1222 starUniformBufferMapped[i] =
nullptr;
1224 if (starUniformBuffers[i] != VK_NULL_HANDLE) {
1225 vkDestroyBuffer(
device, starUniformBuffers[i],
nullptr);
1226 starUniformBuffers[i] = VK_NULL_HANDLE;
1228 if (starUniformBufferMemories[i] != VK_NULL_HANDLE) {
1229 vkFreeMemory(
device, starUniformBufferMemories[i],
nullptr);
1230 starUniformBufferMemories[i] = VK_NULL_HANDLE;
1233 starUniformBuffers.clear();
1234 starUniformBufferMemories.clear();
1235 starUniformBufferMapped.clear();
1238 void createStarDescriptorPool() {
1241 std::array<VkDescriptorPoolSize, 2> poolSizes{};
1242 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1243 poolSizes[0].descriptorCount = imageCount;
1244 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1245 poolSizes[1].descriptorCount = imageCount;
1247 VkDescriptorPoolCreateInfo poolInfo{};
1248 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1249 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
1250 poolInfo.pPoolSizes = poolSizes.data();
1251 poolInfo.maxSets = imageCount;
1256 void createStarDescriptorSets() {
1258 std::vector<VkDescriptorSetLayout> layouts(imageCount, starDescriptorSetLayout);
1260 VkDescriptorSetAllocateInfo allocInfo{};
1261 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1262 allocInfo.descriptorPool = starDescriptorPool;
1263 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(imageCount);
1264 allocInfo.pSetLayouts = layouts.data();
1266 starDescriptorSets.resize(imageCount, VK_NULL_HANDLE);
1269 for (
size_t i = 0; i < imageCount; ++i) {
1270 VkDescriptorImageInfo imageInfo{};
1271 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1272 imageInfo.imageView = starTextureView;
1273 imageInfo.sampler = starSampler;
1275 VkDescriptorBufferInfo bufferInfo{};
1276 bufferInfo.buffer = starUniformBuffers[i];
1277 bufferInfo.offset = 0;
1278 bufferInfo.range =
sizeof(PongUniformBufferObject);
1280 std::array<VkWriteDescriptorSet, 2> writes{};
1281 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1282 writes[0].dstSet = starDescriptorSets[i];
1283 writes[0].dstBinding = 0;
1284 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1285 writes[0].descriptorCount = 1;
1286 writes[0].pImageInfo = &imageInfo;
1288 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1289 writes[1].dstSet = starDescriptorSets[i];
1290 writes[1].dstBinding = 1;
1291 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1292 writes[1].descriptorCount = 1;
1293 writes[1].pBufferInfo = &bufferInfo;
1295 vkUpdateDescriptorSets(
device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
1299 void createStarPipeline() {
1300 const std::vector<char> vertShaderCode =
loadSpv(dataRoot +
"/star_vert.spv");
1301 const std::vector<char> fragShaderCode =
loadSpv(dataRoot +
"/star_frag.spv");
1304 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
1309 VkPipelineShaderStageCreateInfo vertStage{};
1310 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1311 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1312 vertStage.module = vertShaderModule;
1313 vertStage.pName =
"main";
1315 VkPipelineShaderStageCreateInfo fragStage{};
1316 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1317 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1318 fragStage.module = fragShaderModule;
1319 fragStage.pName =
"main";
1321 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
1323 VkVertexInputBindingDescription bindingDescription{};
1324 bindingDescription.binding = 0;
1325 bindingDescription.stride =
sizeof(StarVertex);
1326 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1328 std::array<VkVertexInputAttributeDescription, 3> attributes{};
1329 attributes[0].binding = 0;
1330 attributes[0].location = 0;
1331 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
1332 attributes[0].offset = offsetof(StarVertex, pos);
1334 attributes[1].binding = 0;
1335 attributes[1].location = 1;
1336 attributes[1].format = VK_FORMAT_R32_SFLOAT;
1337 attributes[1].offset = offsetof(StarVertex, size);
1339 attributes[2].binding = 0;
1340 attributes[2].location = 2;
1341 attributes[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1342 attributes[2].offset = offsetof(StarVertex, color);
1344 VkPipelineVertexInputStateCreateInfo vertexInput{};
1345 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1346 vertexInput.vertexBindingDescriptionCount = 1;
1347 vertexInput.pVertexBindingDescriptions = &bindingDescription;
1348 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
1349 vertexInput.pVertexAttributeDescriptions = attributes.data();
1351 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1352 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1353 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
1354 inputAssembly.primitiveRestartEnable = VK_FALSE;
1356 VkPipelineViewportStateCreateInfo viewportState{};
1357 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1358 viewportState.viewportCount = 1;
1359 viewportState.scissorCount = 1;
1361 std::array<VkDynamicState, 2> dynamicStates = {
1362 VK_DYNAMIC_STATE_VIEWPORT,
1363 VK_DYNAMIC_STATE_SCISSOR,
1365 VkPipelineDynamicStateCreateInfo dynamicInfo{};
1366 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1367 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1368 dynamicInfo.pDynamicStates = dynamicStates.data();
1370 VkPipelineRasterizationStateCreateInfo rasterizer{};
1371 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1372 rasterizer.depthClampEnable = VK_FALSE;
1373 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1374 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1375 rasterizer.lineWidth = 1.0f;
1376 rasterizer.cullMode = VK_CULL_MODE_NONE;
1377 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1378 rasterizer.depthBiasEnable = VK_FALSE;
1380 VkPipelineMultisampleStateCreateInfo multisampling{};
1381 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1382 multisampling.sampleShadingEnable = VK_FALSE;
1383 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1385 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1386 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1387 depthStencil.depthTestEnable = VK_FALSE;
1388 depthStencil.depthWriteEnable = VK_FALSE;
1389 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1390 depthStencil.depthBoundsTestEnable = VK_FALSE;
1391 depthStencil.stencilTestEnable = VK_FALSE;
1393 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
1394 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1395 colorBlendAttachment.blendEnable = VK_TRUE;
1396 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1397 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1398 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
1399 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1400 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
1401 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
1403 VkPipelineColorBlendStateCreateInfo colorBlending{};
1404 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1405 colorBlending.logicOpEnable = VK_FALSE;
1406 colorBlending.attachmentCount = 1;
1407 colorBlending.pAttachments = &colorBlendAttachment;
1409 VkPipelineLayoutCreateInfo layoutInfo{};
1410 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1411 layoutInfo.setLayoutCount = 1;
1412 layoutInfo.pSetLayouts = &starDescriptorSetLayout;
1413 layoutInfo.pushConstantRangeCount = 0;
1420 VkPipelineRenderingCreateInfo renderingInfo{};
1421 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1422 renderingInfo.colorAttachmentCount = 1;
1423 renderingInfo.pColorAttachmentFormats = &colorFormat;
1424 renderingInfo.depthAttachmentFormat = depthFormat;
1425 renderingInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
1427 VkGraphicsPipelineCreateInfo pipelineInfo{};
1428 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1429 pipelineInfo.pNext = &renderingInfo;
1430 pipelineInfo.stageCount =
static_cast<uint32_t
>(shaderStages.size());
1431 pipelineInfo.pStages = shaderStages.data();
1432 pipelineInfo.pVertexInputState = &vertexInput;
1433 pipelineInfo.pInputAssemblyState = &inputAssembly;
1434 pipelineInfo.pViewportState = &viewportState;
1435 pipelineInfo.pRasterizationState = &rasterizer;
1436 pipelineInfo.pMultisampleState = &multisampling;
1437 pipelineInfo.pDepthStencilState = &depthStencil;
1438 pipelineInfo.pColorBlendState = &colorBlending;
1439 pipelineInfo.pDynamicState = &dynamicInfo;
1440 pipelineInfo.layout = starPipelineLayout;
1441 pipelineInfo.renderPass = VK_NULL_HANDLE;
1442 pipelineInfo.subpass = 0;
1443 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
1444 pipelineInfo.basePipelineIndex = -1;
1446 VK_CHECK_RESULT(vkCreateGraphicsPipelines(
device, VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &starPipeline));
1448 if (fragShaderModule != VK_NULL_HANDLE) {
1449 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
1451 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
1455 if (fragShaderModule != VK_NULL_HANDLE) {
1456 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
1458 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
1461 void updateStarfield(
float deltaTime) {
1462 if (!starfieldInitialized || starVertexBufferMapped ==
nullptr) {
1465 if (deltaTime > 0.1f) {
1469 const float time = SDL_GetTicks() * 0.001f;
1470 auto *vertices =
static_cast<StarVertex *
>(starVertexBufferMapped);
1472 for (
int i = 0; i < numStars; ++i) {
1473 auto &star = stars[
static_cast<size_t>(i)];
1475 star.x += star.vx * deltaTime;
1476 star.y += star.vy * deltaTime;
1477 star.z += star.vz * deltaTime;
1479 vertices[i].pos[0] = star.x;
1480 vertices[i].pos[1] = star.y;
1481 vertices[i].pos[2] = star.z;
1483 float twinkleFactor = 1.0f;
1484 if (atmosphericTwinkle > 0.0f) {
1485 twinkleFactor = 0.7f + 0.3f * std::sin(time * star.twinkle) * atmosphericTwinkle;
1488 float size = star.size * twinkleFactor;
1489 if (star.isConstellation) {
1492 vertices[i].size = size;
1494 const glm::vec3 starColor = getStarColor(star.temperature);
1495 const float alpha = magnitudeToAlpha(star.magnitude) * twinkleFactor;
1497 vertices[i].color[0] = starColor.r;
1498 vertices[i].color[1] = starColor.g;
1499 vertices[i].color[2] = starColor.b;
1500 vertices[i].color[3] = alpha;
1504 void updateStarUniform(uint32_t imageIndex, [[maybe_unused]]
const VkExtent2D &extent,
const glm::mat4 &view,
const glm::mat4 &proj,
float timeSeconds) {
1505 if (imageIndex >= starUniformBufferMapped.size() || starUniformBufferMapped[imageIndex] ==
nullptr) {
1509 PongUniformBufferObject ubo{};
1510 ubo.model = glm::mat4(1.0f);
1513 ubo.params = glm::vec4(timeSeconds, 0.0f, 0.0f, 0.0f);
1514 ubo.color = glm::vec4(1.0f);
1515 std::memcpy(starUniformBufferMapped[imageIndex], &ubo,
sizeof(ubo));
1518 void drawStarfield(VkCommandBuffer cmd, uint32_t imageIndex,
const VkExtent2D &extent) {
1519 if (!starfieldInitialized || starPipeline == VK_NULL_HANDLE || imageIndex >= starDescriptorSets.size()) {
1523 const Uint32 currentTime = SDL_GetTicks();
1524 const float deltaTime =
static_cast<float>(currentTime - lastStarUpdateTime) / 1000.0f;
1525 lastStarUpdateTime = currentTime;
1526 updateStarfield(deltaTime);
1528 const glm::mat4 view = buildViewMatrix();
1529 glm::mat4 proj = glm::perspective(glm::radians(60.0f),
static_cast<float>(extent.width) /
static_cast<float>(extent.height), 0.1f, 1000.0f);
1530 proj[1][1] *= -1.0f;
1532 updateStarUniform(imageIndex, extent, view, proj, SDL_GetTicks() * 0.001f);
1534 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, starPipeline);
1536 VkViewport viewport{};
1539 viewport.width =
static_cast<float>(extent.width);
1540 viewport.height =
static_cast<float>(extent.height);
1541 viewport.minDepth = 0.0f;
1542 viewport.maxDepth = 1.0f;
1543 vkCmdSetViewport(cmd, 0, 1, &viewport);
1546 scissor.offset = {0, 0};
1547 scissor.extent = extent;
1548 vkCmdSetScissor(cmd, 0, 1, &scissor);
1550 VkBuffer vertexBuffers[] = {starVertexBuffer};
1551 VkDeviceSize offsets[] = {0};
1552 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
1554 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, starPipelineLayout, 0, 1, &starDescriptorSets[imageIndex], 0,
nullptr);
1556 vkCmdDraw(cmd,
static_cast<uint32_t
>(numStars), 1, 0, 0);
1559 void createParticlePipeline() {
1560 const std::vector<char> vertShaderCode =
loadSpv(dataRoot +
"/particle_vert.spv");
1561 const std::vector<char> fragShaderCode =
loadSpv(dataRoot +
"/particle_frag.spv");
1564 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
1569 VkPipelineShaderStageCreateInfo vertStage{};
1570 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1571 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1572 vertStage.module = vertShaderModule;
1573 vertStage.pName =
"main";
1575 VkPipelineShaderStageCreateInfo fragStage{};
1576 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1577 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1578 fragStage.module = fragShaderModule;
1579 fragStage.pName =
"main";
1581 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
1583 VkVertexInputBindingDescription bindingDescription{};
1584 bindingDescription.binding = 0;
1585 bindingDescription.stride =
sizeof(ParticleVertex);
1586 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1588 std::array<VkVertexInputAttributeDescription, 2> attributes{};
1589 attributes[0].binding = 0;
1590 attributes[0].location = 0;
1591 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
1592 attributes[0].offset = offsetof(ParticleVertex, position);
1594 attributes[1].binding = 0;
1595 attributes[1].location = 1;
1596 attributes[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1597 attributes[1].offset = offsetof(ParticleVertex, color);
1599 VkPipelineVertexInputStateCreateInfo vertexInput{};
1600 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1601 vertexInput.vertexBindingDescriptionCount = 1;
1602 vertexInput.pVertexBindingDescriptions = &bindingDescription;
1603 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
1604 vertexInput.pVertexAttributeDescriptions = attributes.data();
1606 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1607 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1608 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
1609 inputAssembly.primitiveRestartEnable = VK_FALSE;
1611 VkPipelineViewportStateCreateInfo viewportState{};
1612 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1613 viewportState.viewportCount = 1;
1614 viewportState.scissorCount = 1;
1616 std::array<VkDynamicState, 2> dynamicStates = {
1617 VK_DYNAMIC_STATE_VIEWPORT,
1618 VK_DYNAMIC_STATE_SCISSOR,
1620 VkPipelineDynamicStateCreateInfo dynamicInfo{};
1621 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1622 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1623 dynamicInfo.pDynamicStates = dynamicStates.data();
1625 VkPipelineRasterizationStateCreateInfo rasterizer{};
1626 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1627 rasterizer.depthClampEnable = VK_FALSE;
1628 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1629 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1630 rasterizer.lineWidth = 1.0f;
1631 rasterizer.cullMode = VK_CULL_MODE_NONE;
1632 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1633 rasterizer.depthBiasEnable = VK_FALSE;
1635 VkPipelineMultisampleStateCreateInfo multisampling{};
1636 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1637 multisampling.sampleShadingEnable = VK_FALSE;
1638 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1640 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1641 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1642 depthStencil.depthTestEnable = VK_FALSE;
1643 depthStencil.depthWriteEnable = VK_FALSE;
1644 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1645 depthStencil.depthBoundsTestEnable = VK_FALSE;
1646 depthStencil.stencilTestEnable = VK_FALSE;
1648 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
1649 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1650 colorBlendAttachment.blendEnable = VK_TRUE;
1651 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1652 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
1653 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
1654 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1655 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
1656 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
1658 VkPipelineColorBlendStateCreateInfo colorBlending{};
1659 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1660 colorBlending.logicOpEnable = VK_FALSE;
1661 colorBlending.attachmentCount = 1;
1662 colorBlending.pAttachments = &colorBlendAttachment;
1664 VkPipelineLayoutCreateInfo layoutInfo{};
1665 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1666 layoutInfo.setLayoutCount = 1;
1667 layoutInfo.pSetLayouts = &starDescriptorSetLayout;
1668 layoutInfo.pushConstantRangeCount = 0;
1675 VkPipelineRenderingCreateInfo renderingInfo{};
1676 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1677 renderingInfo.colorAttachmentCount = 1;
1678 renderingInfo.pColorAttachmentFormats = &colorFormat;
1679 renderingInfo.depthAttachmentFormat = depthFormat;
1680 renderingInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
1682 VkGraphicsPipelineCreateInfo pipelineInfo{};
1683 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1684 pipelineInfo.pNext = &renderingInfo;
1685 pipelineInfo.stageCount =
static_cast<uint32_t
>(shaderStages.size());
1686 pipelineInfo.pStages = shaderStages.data();
1687 pipelineInfo.pVertexInputState = &vertexInput;
1688 pipelineInfo.pInputAssemblyState = &inputAssembly;
1689 pipelineInfo.pViewportState = &viewportState;
1690 pipelineInfo.pRasterizationState = &rasterizer;
1691 pipelineInfo.pMultisampleState = &multisampling;
1692 pipelineInfo.pDepthStencilState = &depthStencil;
1693 pipelineInfo.pColorBlendState = &colorBlending;
1694 pipelineInfo.pDynamicState = &dynamicInfo;
1695 pipelineInfo.layout = particlePipelineLayout;
1696 pipelineInfo.renderPass = VK_NULL_HANDLE;
1697 pipelineInfo.subpass = 0;
1698 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
1699 pipelineInfo.basePipelineIndex = -1;
1701 VK_CHECK_RESULT(vkCreateGraphicsPipelines(
device, VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &particlePipeline));
1703 if (fragShaderModule != VK_NULL_HANDLE) {
1704 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
1706 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
1710 if (fragShaderModule != VK_NULL_HANDLE) {
1711 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
1713 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
1716 bool openGamepad(SDL_JoystickID
id) {
1717 if (gamepad !=
nullptr && gamepadId ==
id) {
1721 gamepad = SDL_OpenGamepad(
id);
1722 if (gamepad ==
nullptr) {
1729 void closeGamepad() {
1730 if (gamepad !=
nullptr) {
1731 SDL_CloseGamepad(gamepad);
1737 void tryOpenFirstGamepad() {
1738 if (gamepad !=
nullptr) {
1742 SDL_JoystickID *ids = SDL_GetGamepads(&count);
1743 if (ids ==
nullptr || count <= 0) {
1744 if (ids !=
nullptr) {
1749 openGamepad(ids[0]);
1753 void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
const {
1754 VkBuffer newBuffer = VK_NULL_HANDLE;
1755 VkDeviceMemory newMemory = VK_NULL_HANDLE;
1757 VkBufferCreateInfo bufferInfo{};
1758 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1759 bufferInfo.size = size;
1760 bufferInfo.usage = usage;
1761 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1766 VkMemoryRequirements memRequirements{};
1767 vkGetBufferMemoryRequirements(
device, newBuffer, &memRequirements);
1769 VkMemoryAllocateInfo allocInfo{};
1770 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1771 allocInfo.allocationSize = memRequirements.size;
1772 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1776 if (newBuffer != VK_NULL_HANDLE) {
1777 vkDestroyBuffer(
device, newBuffer,
nullptr);
1779 if (newMemory != VK_NULL_HANDLE) {
1780 vkFreeMemory(
device, newMemory,
nullptr);
1785 if (buffer != VK_NULL_HANDLE) {
1786 vkDestroyBuffer(
device, buffer,
nullptr);
1788 if (bufferMemory != VK_NULL_HANDLE) {
1789 vkFreeMemory(
device, bufferMemory,
nullptr);
1792 bufferMemory = newMemory;
1795 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
1796 VkPhysicalDeviceMemoryProperties memProperties{};
1799 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
1800 if ((typeFilter & (1U << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1805 throw mxvk::Exception(
"Failed to find suitable memory type");
1808 [[nodiscard]] VkCommandBuffer beginSingleTimeCommands()
const {
1809 VkCommandBufferAllocateInfo allocInfo{};
1810 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1811 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1813 allocInfo.commandBufferCount = 1;
1815 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
1818 VkCommandBufferBeginInfo beginInfo{};
1819 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1820 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1823 return commandBuffer;
1826 void endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
1829 VkSubmitInfo submitInfo{};
1830 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1831 submitInfo.commandBufferCount = 1;
1832 submitInfo.pCommandBuffers = &commandBuffer;
1840 void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &memory)
const {
1841 VkImage newImage = VK_NULL_HANDLE;
1842 VkDeviceMemory newMemory = VK_NULL_HANDLE;
1844 VkImageCreateInfo imageInfo{};
1845 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1846 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1847 imageInfo.extent.width = width;
1848 imageInfo.extent.height = height;
1849 imageInfo.extent.depth = 1;
1850 imageInfo.mipLevels = 1;
1851 imageInfo.arrayLayers = 1;
1852 imageInfo.format = format;
1853 imageInfo.tiling = tiling;
1854 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1855 imageInfo.usage = usage;
1856 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1857 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1862 VkMemoryRequirements memRequirements{};
1863 vkGetImageMemoryRequirements(
device, newImage, &memRequirements);
1865 VkMemoryAllocateInfo allocInfo{};
1866 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1867 allocInfo.allocationSize = memRequirements.size;
1868 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1872 if (newImage != VK_NULL_HANDLE) {
1873 vkDestroyImage(
device, newImage,
nullptr);
1875 if (newMemory != VK_NULL_HANDLE) {
1876 vkFreeMemory(
device, newMemory,
nullptr);
1881 if (image != VK_NULL_HANDLE) {
1882 vkDestroyImage(
device, image,
nullptr);
1884 if (memory != VK_NULL_HANDLE) {
1885 vkFreeMemory(
device, memory,
nullptr);
1891 [[nodiscard]] VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect)
const {
1892 VkImageViewCreateInfo viewInfo{};
1893 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1894 viewInfo.image = image;
1895 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1896 viewInfo.format = format;
1897 viewInfo.subresourceRange.aspectMask = aspect;
1898 viewInfo.subresourceRange.baseMipLevel = 0;
1899 viewInfo.subresourceRange.levelCount = 1;
1900 viewInfo.subresourceRange.baseArrayLayer = 0;
1901 viewInfo.subresourceRange.layerCount = 1;
1903 VkImageView imageView = VK_NULL_HANDLE;
1908 void transitionImageLayout(VkImage image, VkFormat, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
1909 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1911 VkImageMemoryBarrier barrier{};
1912 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1913 barrier.oldLayout = oldLayout;
1914 barrier.newLayout = newLayout;
1915 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1916 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1917 barrier.image = image;
1918 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1919 barrier.subresourceRange.baseMipLevel = 0;
1920 barrier.subresourceRange.levelCount = 1;
1921 barrier.subresourceRange.baseArrayLayer = 0;
1922 barrier.subresourceRange.layerCount = 1;
1924 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1925 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1927 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1928 barrier.srcAccessMask = 0;
1929 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1930 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1931 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1932 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1933 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1934 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1935 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1936 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1938 throw mxvk::Exception(
"Unsupported image layout transition");
1941 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1943 endSingleTimeCommands(commandBuffer);
1946 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
1947 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1949 VkBufferImageCopy region{};
1950 region.bufferOffset = 0;
1951 region.bufferRowLength = 0;
1952 region.bufferImageHeight = 0;
1953 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1954 region.imageSubresource.mipLevel = 0;
1955 region.imageSubresource.baseArrayLayer = 0;
1956 region.imageSubresource.layerCount = 1;
1957 region.imageOffset = {0, 0, 0};
1958 region.imageExtent = {width, height, 1};
1960 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
1962 endSingleTimeCommands(commandBuffer);