MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
pong.cpp
Go to the documentation of this file.
1#include <SDL3/SDL.h>
2
3#include <algorithm>
4#include <array>
5#include <chrono>
6#include <cmath>
7#include <cstdlib>
8#include <cstring>
9#include <ctime>
10#include <format>
11#include <iomanip>
12#include <iostream>
13#include <memory>
14#include <random>
15#include <sstream>
16#include <string>
17#include <vector>
18
19#include <glm/ext/matrix_clip_space.hpp>
20#include <glm/ext/matrix_transform.hpp>
21#include <glm/gtc/quaternion.hpp>
22#include <glm/glm.hpp>
23
24#include "mxvk/argz.hpp"
25#include "mxvk/mxvk.hpp"
28#include "mxvk/mxvk_png.hpp"
29#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
30#include "mxvk/mxvk_sound.hpp"
31#endif
32
33#ifndef pong_ASSET_DIR
34#define pong_ASSET_DIR "."
35#endif
36
37namespace {
38
40 alignas(16) glm::mat4 model{1.0f};
41 alignas(16) glm::mat4 view{1.0f};
42 alignas(16) glm::mat4 proj{1.0f};
43 alignas(16) glm::vec4 params{0.0f, 0.0f, 0.0f, 0.0f};
44 alignas(16) glm::vec4 color{1.0f, 1.0f, 1.0f, 1.0f};
45 };
46
47 struct StarVertex {
48 float pos[3];
49 float size;
50 float color[4];
51 };
52
53 struct Star {
54 float x = 0.0f;
55 float y = 0.0f;
56 float z = 0.0f;
57 float vx = 0.0f;
58 float vy = 0.0f;
59 float vz = 0.0f;
60 float magnitude = 0.0f;
61 float temperature = 0.0f;
62 float twinkle = 0.0f;
63 float size = 0.0f;
64 int starType = 0;
65 bool isConstellation = false;
66 };
67
68 struct Particle {
69 glm::vec3 position{0.0f};
70 glm::vec3 velocity{0.0f};
71 float life = 0.0f;
72 glm::vec4 color{1.0f};
73 };
74
76 glm::vec3 position{0.0f};
77 glm::vec4 color{1.0f};
78 };
79
80 class Paddle {
81 public:
82 glm::vec3 position;
83 glm::vec3 size;
84 float rotationAngle = 0.0f;
85 float rotationSpeed = 0.0f;
86 bool isRotating = false;
87
88 Paddle(const glm::vec3 &pos, const glm::vec3 &sz)
89 : position(pos), size(sz) {}
90
91 void update(float deltaTime) {
92 if (!isRotating) {
93 return;
94 }
95 rotationAngle += rotationSpeed * deltaTime;
96 if (rotationAngle >= 360.0f) {
97 rotationAngle = 0.0f;
98 isRotating = false;
99 }
100 }
101
102 void startRotation(float speed) {
103 if (!isRotating) {
104 rotationSpeed = speed;
105 isRotating = true;
106 }
107 }
108
109 [[nodiscard]] glm::mat4 modelMatrix() const {
110 glm::mat4 model(1.0f);
111 model = glm::translate(model, position);
112 model = glm::rotate(model, glm::radians(rotationAngle), glm::vec3(0.0f, 1.0f, 0.0f));
113 model = glm::scale(model, size);
114 return model;
115 }
116 };
117
118 class Ball {
119 public:
120 glm::vec3 position;
121 glm::vec3 velocity;
122 float radius = 0.05f;
123 float speed = 1.0f;
124 bool hitPaddle1 = false;
125 bool hitPaddle2 = false;
126 bool hitWall = false;
127 glm::vec3 lastImpactPos{0.0f};
128 glm::quat rollingOrientation{1.0f, 0.0f, 0.0f, 0.0f};
129
130 explicit Ball(const glm::vec3 &pos, const glm::vec3 &vel, float r)
131 : position(pos), velocity(vel), radius(r), speed(glm::length(vel)) {}
132
133 void resetBall() {
134 position = glm::vec3(0.0f, 0.0f, 0.0f);
135 rollingOrientation = glm::quat{1.0f, 0.0f, 0.0f, 0.0f};
136 const float angle = glm::radians(static_cast<float>(std::rand() % 120 - 60));
137 speed = 1.0f;
138
139 float vx = std::cos(angle);
140 float vy = std::sin(angle);
141
142 if (std::abs(vx) < 0.5f) {
143 vx = (vx < 0.0f) ? -0.5f : 0.5f;
144 }
145
146 vx *= (std::rand() % 2 == 0) ? 1.0f : -1.0f;
147 velocity = glm::normalize(glm::vec3(vx, vy, 0.0f)) * speed;
148 }
149
150 [[nodiscard]] glm::mat4 modelMatrix() const {
151 glm::mat4 model(1.0f);
152 model = glm::translate(model, position);
153 model *= glm::mat4_cast(rollingOrientation);
154 model = glm::scale(model, glm::vec3(radius));
155 return model;
156 }
157
158 void update(float deltaTime, Paddle &paddle1, Paddle &paddle2, int &score1, int &score2) {
159 hitPaddle1 = false;
160 hitPaddle2 = false;
161 hitWall = false;
162
163 const glm::vec3 movement = velocity * deltaTime;
164 updateRollingOrientation(movement);
165 position += movement;
166
167 if (position.y + radius > 1.0f) {
168 position.y = 1.0f - radius;
169 velocity.y = -velocity.y;
170 hitWall = true;
171 lastImpactPos = glm::vec3(position.x, 1.0f, 0.0f);
172 } else if (position.y - radius < -1.0f) {
173 position.y = -1.0f + radius;
174 velocity.y = -velocity.y;
175 hitWall = true;
176 lastImpactPos = glm::vec3(position.x, -1.0f, 0.0f);
177 }
178
179 handlePaddleCollision(paddle1);
180 handlePaddleCollision(paddle2);
181
182 if (position.x - radius < -1.8f) {
183 ++score2;
184 resetBall();
185 return;
186 }
187 if (position.x + radius > 1.8f) {
188 ++score1;
189 resetBall();
190 }
191 }
192
193 private:
194 static float clampf(float value, float minv, float maxv) {
195 return std::max(minv, std::min(value, maxv));
196 }
197
198 void updateRollingOrientation(const glm::vec3 &movement) {
199 const float distance = glm::length(movement);
200 if (distance <= 0.000001f || radius <= 0.000001f) {
201 return;
202 }
203
204 const glm::vec3 movementDirection = movement / distance;
205 const glm::vec3 playfieldNormal(0.0f, 0.0f, 1.0f);
206 const glm::vec3 rollAxis = glm::normalize(glm::cross(movementDirection, playfieldNormal));
207 const float rollRadians = distance / radius;
208 rollingOrientation = glm::normalize(glm::angleAxis(rollRadians, rollAxis) * rollingOrientation);
209 }
210
211 void handlePaddleCollision(Paddle &paddle) {
212 const float paddleLeft = paddle.position.x - paddle.size.x / 2.0f;
213 const float paddleRight = paddle.position.x + paddle.size.x / 2.0f;
214 const float paddleTop = paddle.position.y + paddle.size.y / 2.0f;
215 const float paddleBottom = paddle.position.y - paddle.size.y / 2.0f;
216
217 const float closestX = clampf(position.x, paddleLeft, paddleRight);
218 const float closestY = clampf(position.y, paddleBottom, paddleTop);
219
220 const float distanceX = position.x - closestX;
221 const float distanceY = position.y - closestY;
222 const float distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
223
224 if (distanceSquared >= (radius * radius)) {
225 return;
226 }
227
228 float distance = std::sqrt(distanceSquared);
229 if (distance <= 0.0001f) {
230 distance = 0.0001f;
231 }
232
233 const float nx = distanceX / distance;
234 const float ny = distanceY / distance;
235 const glm::vec3 normal(nx, ny, 0.0f);
236
237 velocity = glm::reflect(velocity, normal);
238 position += normal * (radius - distance);
239
240 const float impactY = position.y - paddle.position.y;
241 velocity.y += impactY * 5.0f;
242
243 const float maxVerticalComponent = speed * 0.75f;
244 if (std::abs(velocity.y) > maxVerticalComponent) {
245 velocity.y = (velocity.y > 0.0f) ? maxVerticalComponent : -maxVerticalComponent;
246 }
247
248 velocity = glm::normalize(velocity) * speed;
249 paddle.startRotation(360.0f);
250
251 if (paddle.position.x < 0.0f) {
252 hitPaddle1 = true;
253 lastImpactPos = glm::vec3(paddleRight, position.y, 0.0f);
254 } else {
255 hitPaddle2 = true;
256 lastImpactPos = glm::vec3(paddleLeft, position.y, 0.0f);
257 }
258 }
259 };
260
261 class PongWindow final : public mxvk::VK_Window {
262 public:
263 PongWindow(const std::string &path, int width, int height, bool fullscreen, bool enable_vsync)
264 : mxvk::VK_Window("-[ MXVK Pong ]-", width, height, fullscreen, MXVK_VALIDATION, 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);
275 initModels();
276 initAudio();
277 createParticleBuffer();
278 initStarfield(30000);
279 ball.resetBall();
280 tryOpenFirstGamepad();
281 }
282
283 ~PongWindow() override {
284 if (device != VK_NULL_HANDLE) {
285 vkDeviceWaitIdle(device);
286 }
287 closeGamepad();
288 cleanupStarSwapchainResources();
289 cleanupParticleResources();
290 cleanupStarResources();
291 paddleModel1.cleanup(this);
292 paddleModel2.cleanup(this);
293 ballModel.cleanup(this);
294 }
295
297 cleanupStarSwapchainResources();
298 }
299
300 void onSwapchainRecreated() override {
301 paddleModel1.resize(this);
302 paddleModel2.resize(this);
303 ballModel.resize(this);
304 createStarSwapchainResources();
305 }
306
307 void event(SDL_Event &e) override {
308 if (e.type == SDL_EVENT_QUIT) {
309 exit();
310 return;
311 }
312
313 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
314 openGamepad(e.gdevice.which);
315 return;
316 }
317
318 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
319 if (gamepad != nullptr && e.gdevice.which == gamepadId) {
320 closeGamepad();
321 tryOpenFirstGamepad();
322 }
323 return;
324 }
325
326 if (e.type == SDL_EVENT_KEY_DOWN) {
327 switch (e.key.key) {
328 case SDLK_SPACE:
329 wireframe = !wireframe;
330 break;
331 case SDLK_RETURN:
332 cameraZ = 5.0f;
333 gridRotation = 0.0f;
334 gridYRotation = 0.0f;
335 break;
336 case SDLK_ESCAPE:
337 exit();
338 break;
339 case SDLK_R:
340 resetGame();
341 break;
342 default:
343 break;
344 }
345 }
346
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);
352 }
353 return;
354 }
355
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;
359 }
360 return;
361 }
362
363 if (e.type == SDL_EVENT_MOUSE_MOTION) {
364 if (mouseDragging) {
365 const int deltaX = static_cast<int>(e.motion.x) - lastMouseX;
366 const int deltaY = static_cast<int>(e.motion.y) - lastMouseY;
367
368 gridYRotation += static_cast<float>(deltaX) * mouseSensitivity;
369 gridRotation += static_cast<float>(deltaY) * mouseSensitivity;
370
371 gridRotation = std::clamp(gridRotation, -89.0f, 89.0f);
372
373 lastMouseX = static_cast<int>(e.motion.x);
374 lastMouseY = static_cast<int>(e.motion.y);
375 } else {
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);
380 }
381 return;
382 }
383
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);
388 return;
389 }
390
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);
395 }
396 }
397
398 void proc() override {
399 const auto currentTime = std::chrono::steady_clock::now();
400 float deltaTime = std::chrono::duration<float>(currentTime - lastFrameTime).count();
401 lastFrameTime = currentTime;
402
403 if (deltaTime > 0.1f) {
404 deltaTime = 0.1f;
405 }
406
407 const VkExtent2D extent = getSwapchainExtent();
408 if (extent.width > 0U) {
409 fallbackWidth = static_cast<int>(extent.width);
410 }
411 if (extent.height > 0U) {
412 fallbackHeight = static_cast<int>(extent.height);
413 }
414
415 updateFromKeyboard(deltaTime);
416 updateFromGamepad(deltaTime);
417
418 updateAI(deltaTime);
419
420 paddle1.update(deltaTime);
421 paddle2.update(deltaTime);
422 ball.update(deltaTime, paddle1, paddle2, score1, score2);
423
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();
427 }
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();
431 }
432
433 updateParticles(deltaTime);
434 printHud(deltaTime);
435 }
436
437 void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override {
438 if (!ensureRenderResources()) {
439 return;
440 }
441
442 const VkExtent2D extent = getSwapchainExtent();
443 if (extent.width == 0U || extent.height == 0U) {
444 return;
445 }
446
447 drawStarfield(cmd, imageIndex, extent);
448
449 const glm::mat4 view = buildViewMatrix();
450 glm::mat4 proj = glm::perspective(
451 glm::radians(35.0f),
452 static_cast<float>(extent.width) / static_cast<float>(extent.height),
453 0.1f,
454 100.0f);
455 proj[1][1] *= -1.0f;
456
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);
460
461 drawParticles(cmd, imageIndex, extent, view, proj);
462 }
463
464 private:
465 static constexpr int maxParticles = 500;
466
467 std::string assetRoot;
468 std::string dataRoot;
469 std::string shaderRoot;
470
471 Paddle paddle1;
472 Paddle paddle2;
473 Ball ball;
474
475 mxvk::VKAbstractModel paddleModel1{};
476 mxvk::VKAbstractModel paddleModel2{};
477 mxvk::VKAbstractModel ballModel{};
478
479#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
480 std::unique_ptr<mxvk::VK_Mixer> mixer{};
481 int paddleHitSound = -1;
482#endif
483
484 int score1 = 0;
485 int score2 = 0;
486
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;
492
493 bool wireframe = false;
494 bool mouseDragging = false;
495 int lastMouseX = 0;
496 int lastMouseY = 0;
497
498 SDL_Gamepad *gamepad = nullptr;
499 SDL_JoystickID gamepadId = 0;
500 static constexpr float controllerDeadzone = 8000.0f;
501 static constexpr float controllerMax = 32767.0f;
502
503 int fallbackWidth = 1280;
504 int fallbackHeight = 720;
505
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;
510
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{};
523
524 std::vector<Star> stars{};
525 int numStars = 0;
526 bool starfieldInitialized = false;
527 Uint32 lastStarUpdateTime = 0;
528 float atmosphericTwinkle = 1.0f;
529 float lightPollution = 0.1f;
530
531 VkImage starTexture = VK_NULL_HANDLE;
532 VkDeviceMemory starTextureMemory = VK_NULL_HANDLE;
533 VkImageView starTextureView = VK_NULL_HANDLE;
534 VkSampler starSampler = VK_NULL_HANDLE;
535
536 VkBuffer starVertexBuffer = VK_NULL_HANDLE;
537 VkDeviceMemory starVertexBufferMemory = VK_NULL_HANDLE;
538 void *starVertexBufferMapped = nullptr;
539
540 VkDescriptorSetLayout starDescriptorSetLayout = VK_NULL_HANDLE;
541 VkDescriptorPool starDescriptorPool = VK_NULL_HANDLE;
542 std::vector<VkDescriptorSet> starDescriptorSets{};
543
544 std::vector<VkBuffer> starUniformBuffers{};
545 std::vector<VkDeviceMemory> starUniformBufferMemories{};
546 std::vector<void *> starUniformBufferMapped{};
547
548 VkPipeline starPipeline = VK_NULL_HANDLE;
549 VkPipelineLayout starPipelineLayout = VK_NULL_HANDLE;
550
551 void initModels() {
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";
557
558 paddleModel1.load(this, paddleModelPath, paddleManifest, dataRoot, 1.0f);
559 paddleModel1.setShaders(this, shaderVert, shaderFrag);
560
561 paddleModel2.load(this, paddleModelPath, paddleManifest, dataRoot, 1.0f);
562 paddleModel2.setShaders(this, shaderVert, shaderFrag);
563
564 ballModel.load(this, ballModelPath, "", dataRoot, 0.1f);
565 ballModel.setShaders(this, shaderVert, shaderFrag);
566 }
567
568 void initAudio() {
569#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
570 try {
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());
575 mixer.reset();
576 paddleHitSound = -1;
577 }
578#endif
579 }
580
581 void playPaddleHitSound() {
582#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
583 if (mixer != nullptr && paddleHitSound >= 0) {
584 mixer->playWav(paddleHitSound, 0, 0);
585 }
586#endif
587 }
588
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));
594 return view;
595 }
596
597 void drawModel(VkCommandBuffer cmd,
598 uint32_t imageIndex,
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{};
605 ubo.model = world;
606 ubo.view = view;
607 ubo.proj = proj;
608 ubo.fx = glm::vec4(color, 1.0f);
609 model.updateUBO(imageIndex, ubo);
610 model.render(cmd, imageIndex, wireframe);
611 }
612
613 void resetGame() {
614 score1 = 0;
615 score2 = 0;
616 paddle1.position.y = 0.0f;
617 paddle2.position.y = 0.0f;
618 ball.resetBall();
619 }
620
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);
624 }
625
626 [[nodiscard]] static float normalizeAxisWithDeadzone(float axisValue) {
627 const float magnitude = std::abs(axisValue);
628 if (magnitude <= controllerDeadzone) {
629 return 0.0f;
630 }
631
632 const float normalized = (magnitude - controllerDeadzone) / (controllerMax - controllerDeadzone);
633 return std::copysign(std::clamp(normalized, 0.0f, 1.0f), axisValue);
634 }
635
636 void updateFromKeyboard(float deltaTime) {
637 const bool *keyState = SDL_GetKeyboardState(nullptr);
638 if (keyState == nullptr) {
639 return;
640 }
641
642 if (keyState[SDL_SCANCODE_A]) {
643 gridRotation -= rotationSpeed * deltaTime;
644 }
645 if (keyState[SDL_SCANCODE_D]) {
646 gridRotation += rotationSpeed * deltaTime;
647 }
648 if (keyState[SDL_SCANCODE_S]) {
649 gridYRotation -= rotationSpeed * deltaTime;
650 }
651 if (keyState[SDL_SCANCODE_W]) {
652 gridYRotation += rotationSpeed * deltaTime;
653 }
654 if (keyState[SDL_SCANCODE_Q]) {
655 gridRotation = 0.0f;
656 gridYRotation = 0.0f;
657 }
658 if (keyState[SDL_SCANCODE_PAGEUP]) {
659 cameraZ -= 3.0f * deltaTime;
660 }
661 if (keyState[SDL_SCANCODE_PAGEDOWN]) {
662 cameraZ += 3.0f * deltaTime;
663 }
664 cameraZ = std::clamp(cameraZ, 1.0f, 20.0f);
665
666 constexpr float speed = 2.0f;
667 if (keyState[SDL_SCANCODE_UP]) {
668 paddle1.position.y += speed * deltaTime;
669 }
670 if (keyState[SDL_SCANCODE_DOWN]) {
671 paddle1.position.y -= speed * deltaTime;
672 }
673 clampPaddle(paddle1);
674 }
675
676 void updateFromGamepad(float deltaTime) {
677 if (gamepad == nullptr) {
678 return;
679 }
680
681 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_BACK)) {
682 exit();
683 return;
684 }
685
686 constexpr float paddleMoveSpeed = 2.0f;
687
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);
691
692 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP)) {
693 paddle1.position.y += 2.0f * deltaTime;
694 }
695 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN)) {
696 paddle1.position.y -= 2.0f * deltaTime;
697 }
698 clampPaddle(paddle1);
699
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;
704
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;
709 }
710 if (rightTrigger > controllerDeadzone) {
711 cameraZ -= (rightTrigger / controllerMax) * 3.0f * deltaTime;
712 }
713
714 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) {
715 cameraZ += 3.0f * deltaTime;
716 }
717 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) {
718 cameraZ -= 3.0f * deltaTime;
719 }
720
721 cameraZ = std::clamp(cameraZ, 1.0f, 20.0f);
722 }
723
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;
728 }
729 if (ball.position.y < paddle2.position.y - paddle2.size.y / 4.0f) {
730 paddle2.position.y -= paddleSpeed * deltaTime;
731 }
732 clampPaddle(paddle2);
733 }
734
735 void printHud(float deltaTime) {
736 fpsAccumulator += deltaTime;
737 ++fpsFrameCounter;
738 if (fpsAccumulator >= 0.2f) {
739 fpsValue = static_cast<float>(fpsFrameCounter) / fpsAccumulator;
740 fpsAccumulator = 0.0f;
741 fpsFrameCounter = 0;
742 }
743
744 const SDL_Color white{255, 255, 255, 255};
745 const SDL_Color yellow{255, 255, 0, 255};
746
747 printText("Vulkan Pong", 50, 50, white);
748 printText(std::format("Player 1: {} : Player 2: {}", score1, score2), 50, 80, yellow);
749
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";
754
755 printText(fpsStream.str() + " | Mode: " + polygonMode, 50, 110, white);
756 printText(controllerStatus, 50, 140, white);
757 }
758
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) {
761 Particle p;
762 p.position = impactPos;
763 p.velocity = normal * static_cast<float>((std::rand() % 50) / 10.0f + 0.5f) +
764 glm::vec3(0.0f,
765 static_cast<float>((std::rand() % 60) - 30) / 30.0f,
766 static_cast<float>((std::rand() % 40) - 20) / 40.0f);
767 p.life = 0.6f;
768 p.color = paddleColor;
769 particles.push_back(p);
770 }
771 }
772
773 void updateParticles(float deltaTime) {
774 if (mappedParticleData == nullptr) {
775 activeParticleCount = 0;
776 return;
777 }
778
779 const glm::vec3 gravity(0.0f, -2.0f, 0.0f);
780 activeParticleCount = 0;
781 auto *particleBufferData = static_cast<ParticleVertex *>(mappedParticleData);
782
783 particles.erase(
784 std::remove_if(particles.begin(), particles.end(), [](const Particle &p) { return p.life <= 0.0f; }),
785 particles.end());
786
787 for (auto &p : particles) {
788 if (p.life <= 0.0f || activeParticleCount >= static_cast<uint32_t>(maxParticles)) {
789 continue;
790 }
791
792 p.velocity += gravity * deltaTime;
793 p.position += p.velocity * deltaTime;
794 p.life -= deltaTime * 1.5f;
795 p.color.a = p.life;
796
797 particleBufferData[activeParticleCount].position = p.position;
798 particleBufferData[activeParticleCount].color = p.color;
799 ++activeParticleCount;
800 }
801 }
802
803 void createParticleBuffer() {
804 const VkDeviceSize bufferSize = sizeof(ParticleVertex) * maxParticles;
805 createBuffer(
806 bufferSize,
807 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
808 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
809 particleBuffer,
810 particleBufferMemory);
811
812 VK_CHECK_RESULT(vkMapMemory(device, particleBufferMemory, 0, bufferSize, 0, &mappedParticleData));
813 }
814
815 void cleanupParticleResources() {
816 cleanupParticleSwapchainResources();
817 if (mappedParticleData != nullptr && particleBufferMemory != VK_NULL_HANDLE) {
818 vkUnmapMemory(device, particleBufferMemory);
819 mappedParticleData = nullptr;
820 }
821 if (particleBuffer != VK_NULL_HANDLE) {
822 vkDestroyBuffer(device, particleBuffer, nullptr);
823 particleBuffer = VK_NULL_HANDLE;
824 }
825 if (particleBufferMemory != VK_NULL_HANDLE) {
826 vkFreeMemory(device, particleBufferMemory, nullptr);
827 particleBufferMemory = VK_NULL_HANDLE;
828 }
829 }
830
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;
836 }
837 if (particleUniformBuffers[i] != VK_NULL_HANDLE) {
838 vkDestroyBuffer(device, particleUniformBuffers[i], nullptr);
839 particleUniformBuffers[i] = VK_NULL_HANDLE;
840 }
841 if (particleUniformBufferMemories[i] != VK_NULL_HANDLE) {
842 vkFreeMemory(device, particleUniformBufferMemories[i], nullptr);
843 particleUniformBufferMemories[i] = VK_NULL_HANDLE;
844 }
845 }
846 particleUniformBuffers.clear();
847 particleUniformBufferMemories.clear();
848 particleUniformBufferMapped.clear();
849 }
850
851 void cleanupParticleSwapchainResources() {
852 cleanupParticlePipeline();
853 if (particleDescriptorPool != VK_NULL_HANDLE) {
854 vkDestroyDescriptorPool(device, particleDescriptorPool, nullptr);
855 particleDescriptorPool = VK_NULL_HANDLE;
856 }
857 particleDescriptorSets.clear();
858 destroyParticleUniformBuffers();
859 }
860
861 void createParticleUniformBuffers() {
862 const size_t imageCount = getSwapchainImageCount();
863 const VkDeviceSize bufferSize = sizeof(PongUniformBufferObject);
864
865 particleUniformBuffers.resize(imageCount, VK_NULL_HANDLE);
866 particleUniformBufferMemories.resize(imageCount, VK_NULL_HANDLE);
867 particleUniformBufferMapped.resize(imageCount, nullptr);
868
869 for (size_t i = 0; i < imageCount; ++i) {
870 createBuffer(
871 bufferSize,
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]));
877 }
878 }
879
880 void createParticleDescriptorPool() {
881 const uint32_t imageCount = static_cast<uint32_t>(getSwapchainImageCount());
882
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;
888
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;
894
895 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo, nullptr, &particleDescriptorPool));
896 }
897
898 void createParticleDescriptorSets() {
899 const size_t imageCount = getSwapchainImageCount();
900 std::vector<VkDescriptorSetLayout> layouts(imageCount, starDescriptorSetLayout);
901
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();
907
908 particleDescriptorSets.resize(imageCount, VK_NULL_HANDLE);
909 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, particleDescriptorSets.data()));
910
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;
916
917 VkDescriptorBufferInfo bufferInfo{};
918 bufferInfo.buffer = particleUniformBuffers[i];
919 bufferInfo.offset = 0;
920 bufferInfo.range = sizeof(PongUniformBufferObject);
921
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;
929
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;
936
937 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
938 }
939 }
940
941 void updateParticleUniform(uint32_t imageIndex,
942 [[maybe_unused]] const VkExtent2D &extent,
943 const glm::mat4 &view,
944 const glm::mat4 &proj,
945 float timeSeconds) {
946 if (imageIndex >= particleUniformBufferMapped.size() || particleUniformBufferMapped[imageIndex] == nullptr) {
947 return;
948 }
949
950 PongUniformBufferObject ubo{};
951 ubo.model = glm::mat4(1.0f);
952 ubo.view = view;
953 ubo.proj = proj;
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));
957 }
958
959 void cleanupParticlePipeline() {
960 if (particlePipeline != VK_NULL_HANDLE) {
961 vkDestroyPipeline(device, particlePipeline, nullptr);
962 particlePipeline = VK_NULL_HANDLE;
963 }
964 if (particlePipelineLayout != VK_NULL_HANDLE) {
965 vkDestroyPipelineLayout(device, particlePipelineLayout, nullptr);
966 particlePipelineLayout = VK_NULL_HANDLE;
967 }
968 }
969
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()) {
972 return;
973 }
974
975 updateParticleUniform(imageIndex, extent, view, proj, SDL_GetTicks() * 0.001f);
976
977 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, particlePipeline);
978
979 VkViewport viewport{};
980 viewport.x = 0.0f;
981 viewport.y = 0.0f;
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);
987
988 VkRect2D scissor{};
989 scissor.offset = {0, 0};
990 scissor.extent = extent;
991 vkCmdSetScissor(cmd, 0, 1, &scissor);
992
993 VkBuffer vertexBuffers[] = {particleBuffer};
994 VkDeviceSize offsets[] = {0};
995 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
996
997 vkCmdBindDescriptorSets(
998 cmd,
999 VK_PIPELINE_BIND_POINT_GRAPHICS,
1000 particlePipelineLayout,
1001 0,
1002 1,
1003 &particleDescriptorSets[imageIndex],
1004 0,
1005 nullptr);
1006
1007 vkCmdDraw(cmd, activeParticleCount, 1, 0, 0);
1008 }
1009
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);
1014 return dist(eng);
1015 }
1016
1017 glm::vec3 getStarColor(float temperature) const {
1018 float r = 1.0f;
1019 float g = 1.0f;
1020 float b = 1.0f;
1021
1022 if (temperature < 3700.0f) {
1023 r = 1.0f;
1024 g = temperature / 3700.0f * 0.6f;
1025 b = 0.0f;
1026 } else if (temperature < 5200.0f) {
1027 r = 1.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) {
1031 r = 1.0f;
1032 g = 1.0f;
1033 b = (temperature - 5200.0f) / 800.0f * 0.7f;
1034 } else if (temperature < 7500.0f) {
1035 r = 1.0f;
1036 g = 1.0f;
1037 b = 0.7f + (temperature - 6000.0f) / 1500.0f * 0.3f;
1038 } else {
1039 r = 0.7f - (temperature - 7500.0f) / 10000.0f * 0.4f;
1040 g = 0.8f + (temperature - 7500.0f) / 10000.0f * 0.2f;
1041 b = 1.0f;
1042 }
1043
1044 return glm::vec3(r, g, b);
1045 }
1046
1047 float magnitudeToSize(float magnitude) const {
1048 return glm::clamp(15.0f - magnitude * 2.0f, 1.0f, 25.0f);
1049 }
1050
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);
1054 }
1055
1056 void initStarfield(int numStarsParam) {
1057 if (starfieldInitialized) {
1058 return;
1059 }
1060
1061 numStars = numStarsParam;
1062 stars.resize(static_cast<size_t>(numStars));
1063
1064 constexpr float pi = 3.14159265358979323846f;
1065 for (int i = 0; i < numStars; ++i) {
1066 auto &star = stars[static_cast<size_t>(i)];
1067
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);
1071
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);
1075
1076 star.vx = randomFloat(-0.001f, 0.001f);
1077 star.vy = randomFloat(-0.001f, 0.001f);
1078 star.vz = randomFloat(-0.001f, 0.001f);
1079
1080 const float r = randomFloat(0.0f, 1.0f);
1081 if (r < 0.05f) {
1082 star.magnitude = randomFloat(-1.0f, 2.0f);
1083 star.starType = 1;
1084 } else if (r < 0.3f) {
1085 star.magnitude = randomFloat(2.0f, 4.0f);
1086 star.starType = 0;
1087 } else {
1088 star.magnitude = randomFloat(4.0f, 6.5f);
1089 star.starType = 2;
1090 }
1091
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);
1096 } else {
1097 star.temperature = randomFloat(2500.0f, 4000.0f);
1098 }
1099
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);
1103 }
1104
1105 createStarTexture();
1106 createStarVertexBuffer();
1107 createStarSwapchainResources();
1108
1109 lastStarUpdateTime = SDL_GetTicks();
1110 starfieldInitialized = true;
1111 }
1112
1113 void cleanupStarResources() {
1114 if (starVertexBufferMapped != nullptr && starVertexBufferMemory != VK_NULL_HANDLE) {
1115 vkUnmapMemory(device, starVertexBufferMemory);
1116 starVertexBufferMapped = nullptr;
1117 }
1118 if (starVertexBuffer != VK_NULL_HANDLE) {
1119 vkDestroyBuffer(device, starVertexBuffer, nullptr);
1120 starVertexBuffer = VK_NULL_HANDLE;
1121 }
1122 if (starVertexBufferMemory != VK_NULL_HANDLE) {
1123 vkFreeMemory(device, starVertexBufferMemory, nullptr);
1124 starVertexBufferMemory = VK_NULL_HANDLE;
1125 }
1126
1127 if (starSampler != VK_NULL_HANDLE) {
1128 vkDestroySampler(device, starSampler, nullptr);
1129 starSampler = VK_NULL_HANDLE;
1130 }
1131 if (starTextureView != VK_NULL_HANDLE) {
1132 vkDestroyImageView(device, starTextureView, nullptr);
1133 starTextureView = VK_NULL_HANDLE;
1134 }
1135 if (starTexture != VK_NULL_HANDLE) {
1136 vkDestroyImage(device, starTexture, nullptr);
1137 starTexture = VK_NULL_HANDLE;
1138 }
1139 if (starTextureMemory != VK_NULL_HANDLE) {
1140 vkFreeMemory(device, starTextureMemory, nullptr);
1141 starTextureMemory = VK_NULL_HANDLE;
1142 }
1143 }
1144
1145 void cleanupStarSwapchainResources() {
1146 cleanupParticleSwapchainResources();
1147
1148 if (starPipeline != VK_NULL_HANDLE) {
1149 vkDestroyPipeline(device, starPipeline, nullptr);
1150 starPipeline = VK_NULL_HANDLE;
1151 }
1152 if (starPipelineLayout != VK_NULL_HANDLE) {
1153 vkDestroyPipelineLayout(device, starPipelineLayout, nullptr);
1154 starPipelineLayout = VK_NULL_HANDLE;
1155 }
1156 if (starDescriptorPool != VK_NULL_HANDLE) {
1157 vkDestroyDescriptorPool(device, starDescriptorPool, nullptr);
1158 starDescriptorPool = VK_NULL_HANDLE;
1159 }
1160 if (starDescriptorSetLayout != VK_NULL_HANDLE) {
1161 vkDestroyDescriptorSetLayout(device, starDescriptorSetLayout, nullptr);
1162 starDescriptorSetLayout = VK_NULL_HANDLE;
1163 }
1164
1165 destroyStarUniformBuffers();
1166 starDescriptorSets.clear();
1167 }
1168
1169 void createStarSwapchainResources() {
1170 if (!starfieldInitialized && stars.empty()) {
1171 return;
1172 }
1173 createStarDescriptorSetLayout();
1174 createStarUniformBuffers();
1175 createStarDescriptorPool();
1176 createStarDescriptorSets();
1177 createStarPipeline();
1178 createParticleUniformBuffers();
1179 createParticleDescriptorPool();
1180 createParticleDescriptorSets();
1181 createParticlePipeline();
1182 }
1183
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");
1188 }
1189
1190 const VkDeviceSize imageSize = static_cast<VkDeviceSize>(starImg->w) * static_cast<VkDeviceSize>(starImg->h) * 4U;
1191
1192 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1193 VkDeviceMemory stagingBufferMemory = VK_NULL_HANDLE;
1194 createBuffer(
1195 imageSize,
1196 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1197 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1198 stagingBuffer,
1199 stagingBufferMemory);
1200
1201 void *data = nullptr;
1202 VK_CHECK_RESULT(vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data));
1203 std::memcpy(data, starImg->pixels, static_cast<size_t>(imageSize));
1204 vkUnmapMemory(device, stagingBufferMemory);
1205
1206 createImage(
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,
1213 starTexture,
1214 starTextureMemory);
1215
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);
1219
1220 vkDestroyBuffer(device, stagingBuffer, nullptr);
1221 vkFreeMemory(device, stagingBufferMemory, nullptr);
1222
1223 starTextureView = createImageView(starTexture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1224
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;
1238 VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &starSampler));
1239
1240 SDL_DestroySurface(starImg);
1241 }
1242
1243 void createStarVertexBuffer() {
1244 const VkDeviceSize bufferSize = sizeof(StarVertex) * static_cast<VkDeviceSize>(numStars);
1245 createBuffer(
1246 bufferSize,
1247 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
1248 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1249 starVertexBuffer,
1250 starVertexBufferMemory);
1251
1252 VK_CHECK_RESULT(vkMapMemory(device, starVertexBufferMemory, 0, bufferSize, 0, &starVertexBufferMapped));
1253 }
1254
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;
1261
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;
1267
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));
1274 }
1275
1276 void createStarUniformBuffers() {
1277 const size_t imageCount = getSwapchainImageCount();
1278 const VkDeviceSize bufferSize = sizeof(PongUniformBufferObject);
1279
1280 starUniformBuffers.resize(imageCount, VK_NULL_HANDLE);
1281 starUniformBufferMemories.resize(imageCount, VK_NULL_HANDLE);
1282 starUniformBufferMapped.resize(imageCount, nullptr);
1283
1284 for (size_t i = 0; i < imageCount; ++i) {
1285 createBuffer(
1286 bufferSize,
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]));
1292 }
1293 }
1294
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;
1300 }
1301 if (starUniformBuffers[i] != VK_NULL_HANDLE) {
1302 vkDestroyBuffer(device, starUniformBuffers[i], nullptr);
1303 starUniformBuffers[i] = VK_NULL_HANDLE;
1304 }
1305 if (starUniformBufferMemories[i] != VK_NULL_HANDLE) {
1306 vkFreeMemory(device, starUniformBufferMemories[i], nullptr);
1307 starUniformBufferMemories[i] = VK_NULL_HANDLE;
1308 }
1309 }
1310 starUniformBuffers.clear();
1311 starUniformBufferMemories.clear();
1312 starUniformBufferMapped.clear();
1313 }
1314
1315 void createStarDescriptorPool() {
1316 const uint32_t imageCount = static_cast<uint32_t>(getSwapchainImageCount());
1317
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;
1323
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;
1329
1330 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo, nullptr, &starDescriptorPool));
1331 }
1332
1333 void createStarDescriptorSets() {
1334 const size_t imageCount = getSwapchainImageCount();
1335 std::vector<VkDescriptorSetLayout> layouts(imageCount, starDescriptorSetLayout);
1336
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();
1342
1343 starDescriptorSets.resize(imageCount, VK_NULL_HANDLE);
1344 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, starDescriptorSets.data()));
1345
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;
1351
1352 VkDescriptorBufferInfo bufferInfo{};
1353 bufferInfo.buffer = starUniformBuffers[i];
1354 bufferInfo.offset = 0;
1355 bufferInfo.range = sizeof(PongUniformBufferObject);
1356
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;
1364
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;
1371
1372 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
1373 }
1374 }
1375
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");
1379
1380 VkShaderModule vertShaderModule = createShaderModule(device, vertShaderCode);
1381 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
1382
1383 try {
1384 fragShaderModule = createShaderModule(device, fragShaderCode);
1385
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";
1391
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";
1397
1398 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
1399
1400 VkVertexInputBindingDescription bindingDescription{};
1401 bindingDescription.binding = 0;
1402 bindingDescription.stride = sizeof(StarVertex);
1403 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1404
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);
1410
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);
1415
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);
1420
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();
1427
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;
1432
1433 VkPipelineViewportStateCreateInfo viewportState{};
1434 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1435 viewportState.viewportCount = 1;
1436 viewportState.scissorCount = 1;
1437
1438 std::array<VkDynamicState, 2> dynamicStates = {
1439 VK_DYNAMIC_STATE_VIEWPORT,
1440 VK_DYNAMIC_STATE_SCISSOR,
1441 };
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();
1446
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;
1456
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;
1461
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;
1469
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;
1480
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;
1486
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;
1492
1493 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &layoutInfo, nullptr, &starPipelineLayout));
1494
1495 VkFormat colorFormat = getSwapchainFormat();
1496 VkFormat depthFormat = getDepthFormat();
1497
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;
1504
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;
1523
1524 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &starPipeline));
1525 } catch (...) {
1526 if (fragShaderModule != VK_NULL_HANDLE) {
1527 vkDestroyShaderModule(device, fragShaderModule, nullptr);
1528 }
1529 vkDestroyShaderModule(device, vertShaderModule, nullptr);
1530 throw;
1531 }
1532
1533 if (fragShaderModule != VK_NULL_HANDLE) {
1534 vkDestroyShaderModule(device, fragShaderModule, nullptr);
1535 }
1536 vkDestroyShaderModule(device, vertShaderModule, nullptr);
1537 }
1538
1539 void updateStarfield(float deltaTime) {
1540 if (!starfieldInitialized || starVertexBufferMapped == nullptr) {
1541 return;
1542 }
1543 if (deltaTime > 0.1f) {
1544 deltaTime = 0.1f;
1545 }
1546
1547 const float time = SDL_GetTicks() * 0.001f;
1548 auto *vertices = static_cast<StarVertex *>(starVertexBufferMapped);
1549
1550 for (int i = 0; i < numStars; ++i) {
1551 auto &star = stars[static_cast<size_t>(i)];
1552
1553 star.x += star.vx * deltaTime;
1554 star.y += star.vy * deltaTime;
1555 star.z += star.vz * deltaTime;
1556
1557 vertices[i].pos[0] = star.x;
1558 vertices[i].pos[1] = star.y;
1559 vertices[i].pos[2] = star.z;
1560
1561 float twinkleFactor = 1.0f;
1562 if (atmosphericTwinkle > 0.0f) {
1563 twinkleFactor = 0.7f + 0.3f * std::sin(time * star.twinkle) * atmosphericTwinkle;
1564 }
1565
1566 float size = star.size * twinkleFactor;
1567 if (star.isConstellation) {
1568 size *= 1.2f;
1569 }
1570 vertices[i].size = size;
1571
1572 const glm::vec3 starColor = getStarColor(star.temperature);
1573 const float alpha = magnitudeToAlpha(star.magnitude) * twinkleFactor;
1574
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;
1579 }
1580 }
1581
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) {
1588 return;
1589 }
1590
1591 PongUniformBufferObject ubo{};
1592 ubo.model = glm::mat4(1.0f);
1593 ubo.view = view;
1594 ubo.proj = proj;
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));
1598 }
1599
1600 void drawStarfield(VkCommandBuffer cmd, uint32_t imageIndex, const VkExtent2D &extent) {
1601 if (!starfieldInitialized || starPipeline == VK_NULL_HANDLE || imageIndex >= starDescriptorSets.size()) {
1602 return;
1603 }
1604
1605 const Uint32 currentTime = SDL_GetTicks();
1606 const float deltaTime = static_cast<float>(currentTime - lastStarUpdateTime) / 1000.0f;
1607 lastStarUpdateTime = currentTime;
1608 updateStarfield(deltaTime);
1609
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),
1614 0.1f,
1615 1000.0f);
1616 proj[1][1] *= -1.0f;
1617
1618 updateStarUniform(imageIndex, extent, view, proj, SDL_GetTicks() * 0.001f);
1619
1620 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, starPipeline);
1621
1622 VkViewport viewport{};
1623 viewport.x = 0.0f;
1624 viewport.y = 0.0f;
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);
1630
1631 VkRect2D scissor{};
1632 scissor.offset = {0, 0};
1633 scissor.extent = extent;
1634 vkCmdSetScissor(cmd, 0, 1, &scissor);
1635
1636 VkBuffer vertexBuffers[] = {starVertexBuffer};
1637 VkDeviceSize offsets[] = {0};
1638 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
1639
1640 vkCmdBindDescriptorSets(
1641 cmd,
1642 VK_PIPELINE_BIND_POINT_GRAPHICS,
1643 starPipelineLayout,
1644 0,
1645 1,
1646 &starDescriptorSets[imageIndex],
1647 0,
1648 nullptr);
1649
1650 vkCmdDraw(cmd, static_cast<uint32_t>(numStars), 1, 0, 0);
1651 }
1652
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");
1656
1657 VkShaderModule vertShaderModule = createShaderModule(device, vertShaderCode);
1658 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
1659
1660 try {
1661 fragShaderModule = createShaderModule(device, fragShaderCode);
1662
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";
1668
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";
1674
1675 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
1676
1677 VkVertexInputBindingDescription bindingDescription{};
1678 bindingDescription.binding = 0;
1679 bindingDescription.stride = sizeof(ParticleVertex);
1680 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1681
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);
1687
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);
1692
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();
1699
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;
1704
1705 VkPipelineViewportStateCreateInfo viewportState{};
1706 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1707 viewportState.viewportCount = 1;
1708 viewportState.scissorCount = 1;
1709
1710 std::array<VkDynamicState, 2> dynamicStates = {
1711 VK_DYNAMIC_STATE_VIEWPORT,
1712 VK_DYNAMIC_STATE_SCISSOR,
1713 };
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();
1718
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;
1728
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;
1733
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;
1741
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;
1752
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;
1758
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;
1764
1765 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &layoutInfo, nullptr, &particlePipelineLayout));
1766
1767 VkFormat colorFormat = getSwapchainFormat();
1768 VkFormat depthFormat = getDepthFormat();
1769
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;
1776
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;
1795
1796 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &particlePipeline));
1797 } catch (...) {
1798 if (fragShaderModule != VK_NULL_HANDLE) {
1799 vkDestroyShaderModule(device, fragShaderModule, nullptr);
1800 }
1801 vkDestroyShaderModule(device, vertShaderModule, nullptr);
1802 throw;
1803 }
1804
1805 if (fragShaderModule != VK_NULL_HANDLE) {
1806 vkDestroyShaderModule(device, fragShaderModule, nullptr);
1807 }
1808 vkDestroyShaderModule(device, vertShaderModule, nullptr);
1809 }
1810
1811 bool openGamepad(SDL_JoystickID id) {
1812 if (gamepad != nullptr && gamepadId == id) {
1813 return true;
1814 }
1815 closeGamepad();
1816 gamepad = SDL_OpenGamepad(id);
1817 if (gamepad == nullptr) {
1818 return false;
1819 }
1820 gamepadId = id;
1821 return true;
1822 }
1823
1824 void closeGamepad() {
1825 if (gamepad != nullptr) {
1826 SDL_CloseGamepad(gamepad);
1827 gamepad = nullptr;
1828 gamepadId = 0;
1829 }
1830 }
1831
1832 void tryOpenFirstGamepad() {
1833 if (gamepad != nullptr) {
1834 return;
1835 }
1836 int count = 0;
1837 SDL_JoystickID *ids = SDL_GetGamepads(&count);
1838 if (ids == nullptr || count <= 0) {
1839 if (ids != nullptr) {
1840 SDL_free(ids);
1841 }
1842 return;
1843 }
1844 openGamepad(ids[0]);
1845 SDL_free(ids);
1846 }
1847
1848 void createBuffer(VkDeviceSize size,
1849 VkBufferUsageFlags usage,
1850 VkMemoryPropertyFlags properties,
1851 VkBuffer &buffer,
1852 VkDeviceMemory &bufferMemory) const {
1853 VkBuffer newBuffer = VK_NULL_HANDLE;
1854 VkDeviceMemory newMemory = VK_NULL_HANDLE;
1855
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;
1861
1862 try {
1863 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &newBuffer));
1864
1865 VkMemoryRequirements memRequirements{};
1866 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
1867
1868 VkMemoryAllocateInfo allocInfo{};
1869 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1870 allocInfo.allocationSize = memRequirements.size;
1871 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1872 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &newMemory));
1873 VK_CHECK_RESULT(vkBindBufferMemory(device, newBuffer, newMemory, 0));
1874 } catch (...) {
1875 if (newBuffer != VK_NULL_HANDLE) {
1876 vkDestroyBuffer(device, newBuffer, nullptr);
1877 }
1878 if (newMemory != VK_NULL_HANDLE) {
1879 vkFreeMemory(device, newMemory, nullptr);
1880 }
1881 throw;
1882 }
1883
1884 if (buffer != VK_NULL_HANDLE) {
1885 vkDestroyBuffer(device, buffer, nullptr);
1886 }
1887 if (bufferMemory != VK_NULL_HANDLE) {
1888 vkFreeMemory(device, bufferMemory, nullptr);
1889 }
1890 buffer = newBuffer;
1891 bufferMemory = newMemory;
1892 }
1893
1894 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const {
1895 VkPhysicalDeviceMemoryProperties memProperties{};
1896 vkGetPhysicalDeviceMemoryProperties(physical_device, &memProperties);
1897
1898 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
1899 if ((typeFilter & (1U << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1900 return i;
1901 }
1902 }
1903
1904 throw mxvk::Exception("Failed to find suitable memory type");
1905 }
1906
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;
1911 allocInfo.commandPool = command_pool;
1912 allocInfo.commandBufferCount = 1;
1913
1914 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
1915 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
1916
1917 VkCommandBufferBeginInfo beginInfo{};
1918 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1919 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1920 VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &beginInfo));
1921
1922 return commandBuffer;
1923 }
1924
1925 void endSingleTimeCommands(VkCommandBuffer commandBuffer) const {
1926 VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
1927
1928 VkSubmitInfo submitInfo{};
1929 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1930 submitInfo.commandBufferCount = 1;
1931 submitInfo.pCommandBuffers = &commandBuffer;
1932
1933 VK_CHECK_RESULT(vkQueueSubmit(graphics_queue, 1, &submitInfo, VK_NULL_HANDLE));
1934 VK_CHECK_RESULT(vkQueueWaitIdle(graphics_queue));
1935
1936 vkFreeCommandBuffers(device, command_pool, 1, &commandBuffer);
1937 }
1938
1939 void createImage(uint32_t width,
1940 uint32_t height,
1941 VkFormat format,
1942 VkImageTiling tiling,
1943 VkImageUsageFlags usage,
1944 VkMemoryPropertyFlags properties,
1945 VkImage &image,
1946 VkDeviceMemory &memory) const {
1947 VkImage newImage = VK_NULL_HANDLE;
1948 VkDeviceMemory newMemory = VK_NULL_HANDLE;
1949
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;
1964
1965 try {
1966 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &newImage));
1967
1968 VkMemoryRequirements memRequirements{};
1969 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
1970
1971 VkMemoryAllocateInfo allocInfo{};
1972 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1973 allocInfo.allocationSize = memRequirements.size;
1974 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1975 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &newMemory));
1976 VK_CHECK_RESULT(vkBindImageMemory(device, newImage, newMemory, 0));
1977 } catch (...) {
1978 if (newImage != VK_NULL_HANDLE) {
1979 vkDestroyImage(device, newImage, nullptr);
1980 }
1981 if (newMemory != VK_NULL_HANDLE) {
1982 vkFreeMemory(device, newMemory, nullptr);
1983 }
1984 throw;
1985 }
1986
1987 if (image != VK_NULL_HANDLE) {
1988 vkDestroyImage(device, image, nullptr);
1989 }
1990 if (memory != VK_NULL_HANDLE) {
1991 vkFreeMemory(device, memory, nullptr);
1992 }
1993 image = newImage;
1994 memory = newMemory;
1995 }
1996
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;
2008
2009 VkImageView imageView = VK_NULL_HANDLE;
2010 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &imageView));
2011 return imageView;
2012 }
2013
2014 void transitionImageLayout(VkImage image,
2015 VkFormat,
2016 VkImageLayout oldLayout,
2017 VkImageLayout newLayout) const {
2018 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2019
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;
2032
2033 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2034 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2035
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;
2046 } else {
2047 throw mxvk::Exception("Unsupported image layout transition");
2048 }
2049
2050 vkCmdPipelineBarrier(
2051 commandBuffer,
2052 sourceStage,
2053 destinationStage,
2054 0,
2055 0,
2056 nullptr,
2057 0,
2058 nullptr,
2059 1,
2060 &barrier);
2061
2062 endSingleTimeCommands(commandBuffer);
2063 }
2064
2065 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) const {
2066 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2067
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};
2078
2079 vkCmdCopyBufferToImage(
2080 commandBuffer,
2081 buffer,
2082 image,
2083 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
2084 1,
2085 &region);
2086
2087 endSingleTimeCommands(commandBuffer);
2088 }
2089 };
2090
2091} // namespace
2092
2093int main(int argc, char **argv) {
2094 try {
2095 Arguments args = proc_args(argc, argv);
2096 PongWindow window(args.path, args.width, args.height, args.fullscreen, args.enable_vsync);
2097 window.loop();
2098 } catch (mxvk::Exception &e) {
2099 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
2100 return EXIT_FAILURE;
2101 } catch (ArgException<std::string> &e) {
2102 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
2103 return EXIT_FAILURE;
2104 } catch (const std::exception &e) {
2105 std::cerr << std::format("std::exception: {}\n", e.what());
2106 return EXIT_FAILURE;
2107 }
2108
2109 return EXIT_SUCCESS;
2110}
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Definition argz.hpp:872
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:178
Ball(const glm::vec3 &pos, const glm::vec3 &vel, float r)
Definition pong.cpp:130
glm::mat4 modelMatrix() const
Definition pong.cpp:150
void update(float deltaTime, Paddle &paddle1, Paddle &paddle2, int &score1, int &score2)
Definition pong.cpp:158
void update(float deltaTime)
Definition pong.cpp:91
Paddle(const glm::vec3 &pos, const glm::vec3 &sz)
Definition pong.cpp:88
PongWindow(const std::string &path, int width, int height, bool fullscreen, bool enable_vsync)
Definition pong.cpp:263
void onSwapchainAboutToRecreate() override
Called right before swapchain-dependent resources are recreated.
Definition pong.cpp:296
void proc() override
Execute one processing/update step.
Definition pong.cpp:398
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override
Optional hook for derived classes to record extra draw commands.
Definition pong.cpp:437
void event(SDL_Event &e) override
Handle one SDL event.
Definition pong.cpp:307
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
Definition pong.cpp:300
std::string text() const
Convenience wrapper that owns mesh, textures, descriptors, and pipeline state.
void updateUBO(uint32_t imageIndex, const UniformBufferObject &ubo)
Update one per-frame UBO payload.
void render(VkCommandBuffer cmd, uint32_t imageIndex, bool wireframe=false) const
Record draw commands for this model.
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:37
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:186
VkDevice device
Definition mxvk.hpp:485
static VkShaderModule createShaderModule(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
Definition mxvk.cpp:145
size_t getSwapchainImageCount() const noexcept
Get the number of swapchain images currently allocated.
Definition mxvk.hpp:192
void exit()
Request loop termination.
Definition mxvk.cpp:1126
VkCommandPool command_pool
Definition mxvk.hpp:504
bool ensureRenderResources()
Ensure deferred render resources are initialized.
Definition mxvk.cpp:1406
VK_Window()=default
Construct an empty window object.
VkPhysicalDevice physical_device
Definition mxvk.hpp:484
VkFormat getDepthFormat() const noexcept
Get the depth format used for dynamic rendering attachments.
Definition mxvk.hpp:189
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
Definition mxvk.cpp:2991
void printText(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for rendering during the current frame.
Definition mxvk.cpp:3018
static std::vector< char > loadSpv(const std::string &path)
Load a SPIR-V file from disk.
Definition mxvk.cpp:141
VkQueue graphics_queue
Definition mxvk.hpp:488
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
Definition mxvk.hpp:183
int main(void)
Definition main.cpp:7
#define MXVK_VALIDATION
Definition mxvk.hpp:27
High-level model wrapper integrated with MXVK dynamic rendering.
PNG image loading and saving utilities via SDL3.
SDL3_mixer audio subsystem wrapper.
#define VK_CHECK_RESULT(f)
float clampf(float value, float low, float high)
Definition breakout.cpp:75
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition mxvk_png.cpp:103
#define pong_ASSET_DIR
Definition pong.cpp:34
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:730
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:736
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:750
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:733
std::string path
Asset root; proc_args() defaults it to the executable directory.
Definition argz.hpp:735
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:732