MXVK Vulkan Framework 0.35.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/glm.hpp>
22#include <glm/gtc/quaternion.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) : position(pos), size(sz) {}
89
90 void update(float deltaTime) {
91 if (!isRotating) {
92 return;
93 }
94 rotationAngle += rotationSpeed * deltaTime;
95 if (rotationAngle >= 360.0f) {
96 rotationAngle = 0.0f;
97 isRotating = false;
98 }
99 }
100
101 void startRotation(float speed) {
102 if (!isRotating) {
103 rotationSpeed = speed;
104 isRotating = true;
105 }
106 }
107
108 [[nodiscard]] glm::mat4 modelMatrix() const {
109 glm::mat4 model(1.0f);
110 model = glm::translate(model, position);
111 model = glm::rotate(model, glm::radians(rotationAngle), glm::vec3(0.0f, 1.0f, 0.0f));
112 model = glm::scale(model, size);
113 return model;
114 }
115 };
116
117 class Ball {
118 public:
119 glm::vec3 position;
120 glm::vec3 velocity;
121 float radius = 0.05f;
122 float speed = 1.0f;
123 bool hitPaddle1 = false;
124 bool hitPaddle2 = false;
125 bool hitWall = false;
126 glm::vec3 lastImpactPos{0.0f};
127 glm::quat rollingOrientation{1.0f, 0.0f, 0.0f, 0.0f};
128
129 explicit Ball(const glm::vec3 &pos, const glm::vec3 &vel, float r) : position(pos), velocity(vel), radius(r), speed(glm::length(vel)) {}
130
131 void resetBall() {
132 position = glm::vec3(0.0f, 0.0f, 0.0f);
133 rollingOrientation = glm::quat{1.0f, 0.0f, 0.0f, 0.0f};
134 const float angle = glm::radians(static_cast<float>(std::rand() % 120 - 60));
135 speed = 1.0f;
136
137 float vx = std::cos(angle);
138 float vy = std::sin(angle);
139
140 if (std::abs(vx) < 0.5f) {
141 vx = (vx < 0.0f) ? -0.5f : 0.5f;
142 }
143
144 vx *= (std::rand() % 2 == 0) ? 1.0f : -1.0f;
145 velocity = glm::normalize(glm::vec3(vx, vy, 0.0f)) * speed;
146 }
147
148 [[nodiscard]] glm::mat4 modelMatrix() const {
149 glm::mat4 model(1.0f);
150 model = glm::translate(model, position);
151 model *= glm::mat4_cast(rollingOrientation);
152 model = glm::scale(model, glm::vec3(radius));
153 return model;
154 }
155
156 void update(float deltaTime, Paddle &paddle1, Paddle &paddle2, int &score1, int &score2) {
157 hitPaddle1 = false;
158 hitPaddle2 = false;
159 hitWall = false;
160
161 const glm::vec3 movement = velocity * deltaTime;
162 updateRollingOrientation(movement);
163 position += movement;
164
165 if (position.y + radius > 1.0f) {
166 position.y = 1.0f - radius;
167 velocity.y = -velocity.y;
168 hitWall = true;
169 lastImpactPos = glm::vec3(position.x, 1.0f, 0.0f);
170 } else if (position.y - radius < -1.0f) {
171 position.y = -1.0f + radius;
172 velocity.y = -velocity.y;
173 hitWall = true;
174 lastImpactPos = glm::vec3(position.x, -1.0f, 0.0f);
175 }
176
177 handlePaddleCollision(paddle1);
178 handlePaddleCollision(paddle2);
179
180 if (position.x - radius < -1.8f) {
181 ++score2;
182 resetBall();
183 return;
184 }
185 if (position.x + radius > 1.8f) {
186 ++score1;
187 resetBall();
188 }
189 }
190
191 private:
192 static float clampf(float value, float minv, float maxv) { return std::max(minv, std::min(value, maxv)); }
193
194 void updateRollingOrientation(const glm::vec3 &movement) {
195 const float distance = glm::length(movement);
196 if (distance <= 0.000001f || radius <= 0.000001f) {
197 return;
198 }
199
200 const glm::vec3 movementDirection = movement / distance;
201 const glm::vec3 playfieldNormal(0.0f, 0.0f, 1.0f);
202 const glm::vec3 rollAxis = glm::normalize(glm::cross(movementDirection, playfieldNormal));
203 const float rollRadians = distance / radius;
204 rollingOrientation = glm::normalize(glm::angleAxis(rollRadians, rollAxis) * rollingOrientation);
205 }
206
207 void handlePaddleCollision(Paddle &paddle) {
208 const float paddleLeft = paddle.position.x - paddle.size.x / 2.0f;
209 const float paddleRight = paddle.position.x + paddle.size.x / 2.0f;
210 const float paddleTop = paddle.position.y + paddle.size.y / 2.0f;
211 const float paddleBottom = paddle.position.y - paddle.size.y / 2.0f;
212
213 const float closestX = clampf(position.x, paddleLeft, paddleRight);
214 const float closestY = clampf(position.y, paddleBottom, paddleTop);
215
216 const float distanceX = position.x - closestX;
217 const float distanceY = position.y - closestY;
218 const float distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
219
220 if (distanceSquared >= (radius * radius)) {
221 return;
222 }
223
224 float distance = std::sqrt(distanceSquared);
225 if (distance <= 0.0001f) {
226 distance = 0.0001f;
227 }
228
229 const float nx = distanceX / distance;
230 const float ny = distanceY / distance;
231 const glm::vec3 normal(nx, ny, 0.0f);
232
233 velocity = glm::reflect(velocity, normal);
234 position += normal * (radius - distance);
235
236 const float impactY = position.y - paddle.position.y;
237 velocity.y += impactY * 5.0f;
238
239 const float maxVerticalComponent = speed * 0.75f;
240 if (std::abs(velocity.y) > maxVerticalComponent) {
241 velocity.y = (velocity.y > 0.0f) ? maxVerticalComponent : -maxVerticalComponent;
242 }
243
244 velocity = glm::normalize(velocity) * speed;
245 paddle.startRotation(360.0f);
246
247 if (paddle.position.x < 0.0f) {
248 hitPaddle1 = true;
249 lastImpactPos = glm::vec3(paddleRight, position.y, 0.0f);
250 } else {
251 hitPaddle2 = true;
252 lastImpactPos = glm::vec3(paddleLeft, position.y, 0.0f);
253 }
254 }
255 };
256
257 class PongWindow final : public mxvk::VK_Window {
258 public:
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);
262 initModels();
263 initAudio();
264 createParticleBuffer();
265 initStarfield(30000);
266 ball.resetBall();
267 tryOpenFirstGamepad();
268 }
269
270 ~PongWindow() override {
271 if (device != VK_NULL_HANDLE) {
272 vkDeviceWaitIdle(device);
273 }
274 closeGamepad();
275 cleanupStarSwapchainResources();
276 cleanupParticleResources();
277 cleanupStarResources();
278 paddleModel1.cleanup(this);
279 paddleModel2.cleanup(this);
280 ballModel.cleanup(this);
281 }
282
283 void onSwapchainAboutToRecreate() override { cleanupStarSwapchainResources(); }
284
285 void onSwapchainRecreated() override {
286 paddleModel1.resize(this);
287 paddleModel2.resize(this);
288 ballModel.resize(this);
289 createStarSwapchainResources();
290 }
291
292 void event(SDL_Event &e) override {
293 if (e.type == SDL_EVENT_QUIT) {
294 exit();
295 return;
296 }
297
298 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
299 openGamepad(e.gdevice.which);
300 return;
301 }
302
303 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
304 if (gamepad != nullptr && e.gdevice.which == gamepadId) {
305 closeGamepad();
306 tryOpenFirstGamepad();
307 }
308 return;
309 }
310
311 if (e.type == SDL_EVENT_KEY_DOWN) {
312 switch (e.key.key) {
313 case SDLK_SPACE:
314 wireframe = !wireframe;
315 break;
316 case SDLK_RETURN:
317 cameraZ = 5.0f;
318 gridRotation = 0.0f;
319 gridYRotation = 0.0f;
320 break;
321 case SDLK_ESCAPE:
322 exit();
323 break;
324 case SDLK_R:
325 resetGame();
326 break;
327 default:
328 break;
329 }
330 }
331
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);
337 }
338 return;
339 }
340
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;
344 }
345 return;
346 }
347
348 if (e.type == SDL_EVENT_MOUSE_MOTION) {
349 if (mouseDragging) {
350 const int deltaX = static_cast<int>(e.motion.x) - lastMouseX;
351 const int deltaY = static_cast<int>(e.motion.y) - lastMouseY;
352
353 gridYRotation += static_cast<float>(deltaX) * mouseSensitivity;
354 gridRotation += static_cast<float>(deltaY) * mouseSensitivity;
355
356 gridRotation = std::clamp(gridRotation, -89.0f, 89.0f);
357
358 lastMouseX = static_cast<int>(e.motion.x);
359 lastMouseY = static_cast<int>(e.motion.y);
360 } else {
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);
365 }
366 return;
367 }
368
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);
373 return;
374 }
375
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);
380 }
381 }
382
383 void proc() override {
384 const auto currentTime = std::chrono::steady_clock::now();
385 float deltaTime = std::chrono::duration<float>(currentTime - lastFrameTime).count();
386 lastFrameTime = currentTime;
387
388 if (deltaTime > 0.1f) {
389 deltaTime = 0.1f;
390 }
391
392 const VkExtent2D extent = getSwapchainExtent();
393 if (extent.width > 0U) {
394 fallbackWidth = static_cast<int>(extent.width);
395 }
396 if (extent.height > 0U) {
397 fallbackHeight = static_cast<int>(extent.height);
398 }
399
400 updateFromKeyboard(deltaTime);
401 updateFromGamepad(deltaTime);
402
403 updateAI(deltaTime);
404
405 paddle1.update(deltaTime);
406 paddle2.update(deltaTime);
407 ball.update(deltaTime, paddle1, paddle2, score1, score2);
408
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();
412 }
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();
416 }
417
418 updateParticles(deltaTime);
419 printHud(deltaTime);
420 }
421
422 void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override {
423 if (!ensureRenderResources()) {
424 return;
425 }
426
427 const VkExtent2D extent = getSwapchainExtent();
428 if (extent.width == 0U || extent.height == 0U) {
429 return;
430 }
431
432 drawStarfield(cmd, imageIndex, extent);
433
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);
436 proj[1][1] *= -1.0f;
437
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);
441
442 drawParticles(cmd, imageIndex, extent, view, proj);
443 }
444
445 private:
446 static constexpr int maxParticles = 500;
447
448 std::string assetRoot;
449 std::string dataRoot;
450 std::string shaderRoot;
451
452 Paddle paddle1;
453 Paddle paddle2;
454 Ball ball;
455
456 mxvk::VKAbstractModel paddleModel1{};
457 mxvk::VKAbstractModel paddleModel2{};
458 mxvk::VKAbstractModel ballModel{};
459
460#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
461 std::unique_ptr<mxvk::VK_Mixer> mixer{};
462 int paddleHitSound = -1;
463#endif
464
465 int score1 = 0;
466 int score2 = 0;
467
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;
473
474 bool wireframe = false;
475 bool mouseDragging = false;
476 int lastMouseX = 0;
477 int lastMouseY = 0;
478
479 SDL_Gamepad *gamepad = nullptr;
480 SDL_JoystickID gamepadId = 0;
481 static constexpr float controllerDeadzone = 8000.0f;
482 static constexpr float controllerMax = 32767.0f;
483
484 int fallbackWidth = 1280;
485 int fallbackHeight = 720;
486
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;
491
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{};
504
505 std::vector<Star> stars{};
506 int numStars = 0;
507 bool starfieldInitialized = false;
508 Uint32 lastStarUpdateTime = 0;
509 float atmosphericTwinkle = 1.0f;
510 float lightPollution = 0.1f;
511
512 VkImage starTexture = VK_NULL_HANDLE;
513 VkDeviceMemory starTextureMemory = VK_NULL_HANDLE;
514 VkImageView starTextureView = VK_NULL_HANDLE;
515 VkSampler starSampler = VK_NULL_HANDLE;
516
517 VkBuffer starVertexBuffer = VK_NULL_HANDLE;
518 VkDeviceMemory starVertexBufferMemory = VK_NULL_HANDLE;
519 void *starVertexBufferMapped = nullptr;
520
521 VkDescriptorSetLayout starDescriptorSetLayout = VK_NULL_HANDLE;
522 VkDescriptorPool starDescriptorPool = VK_NULL_HANDLE;
523 std::vector<VkDescriptorSet> starDescriptorSets{};
524
525 std::vector<VkBuffer> starUniformBuffers{};
526 std::vector<VkDeviceMemory> starUniformBufferMemories{};
527 std::vector<void *> starUniformBufferMapped{};
528
529 VkPipeline starPipeline = VK_NULL_HANDLE;
530 VkPipelineLayout starPipelineLayout = VK_NULL_HANDLE;
531
532 void initModels() {
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";
538
539 paddleModel1.load(this, paddleModelPath, paddleManifest, dataRoot, 1.0f);
540 paddleModel1.setShaders(this, shaderVert, shaderFrag);
541
542 paddleModel2.load(this, paddleModelPath, paddleManifest, dataRoot, 1.0f);
543 paddleModel2.setShaders(this, shaderVert, shaderFrag);
544
545 ballModel.load(this, ballModelPath, "", dataRoot, 0.1f);
546 ballModel.setShaders(this, shaderVert, shaderFrag);
547 }
548
549 void initAudio() {
550#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
551 try {
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());
556 mixer.reset();
557 paddleHitSound = -1;
558 }
559#endif
560 }
561
562 void playPaddleHitSound() {
563#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
564 if (mixer != nullptr && paddleHitSound >= 0) {
565 mixer->playWav(paddleHitSound, 0, 0);
566 }
567#endif
568 }
569
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));
575 return view;
576 }
577
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{};
580 ubo.model = world;
581 ubo.view = view;
582 ubo.proj = proj;
583 ubo.fx = glm::vec4(color, 1.0f);
584 model.updateUBO(imageIndex, ubo);
585 model.render(cmd, imageIndex, wireframe);
586 }
587
588 void resetGame() {
589 score1 = 0;
590 score2 = 0;
591 paddle1.position.y = 0.0f;
592 paddle2.position.y = 0.0f;
593 ball.resetBall();
594 }
595
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);
599 }
600
601 [[nodiscard]] static float normalizeAxisWithDeadzone(float axisValue) {
602 const float magnitude = std::abs(axisValue);
603 if (magnitude <= controllerDeadzone) {
604 return 0.0f;
605 }
606
607 const float normalized = (magnitude - controllerDeadzone) / (controllerMax - controllerDeadzone);
608 return std::copysign(std::clamp(normalized, 0.0f, 1.0f), axisValue);
609 }
610
611 void updateFromKeyboard(float deltaTime) {
612 const bool *keyState = SDL_GetKeyboardState(nullptr);
613 if (keyState == nullptr) {
614 return;
615 }
616
617 if (keyState[SDL_SCANCODE_A]) {
618 gridRotation -= rotationSpeed * deltaTime;
619 }
620 if (keyState[SDL_SCANCODE_D]) {
621 gridRotation += rotationSpeed * deltaTime;
622 }
623 if (keyState[SDL_SCANCODE_S]) {
624 gridYRotation -= rotationSpeed * deltaTime;
625 }
626 if (keyState[SDL_SCANCODE_W]) {
627 gridYRotation += rotationSpeed * deltaTime;
628 }
629 if (keyState[SDL_SCANCODE_Q]) {
630 gridRotation = 0.0f;
631 gridYRotation = 0.0f;
632 }
633 if (keyState[SDL_SCANCODE_PAGEUP]) {
634 cameraZ -= 3.0f * deltaTime;
635 }
636 if (keyState[SDL_SCANCODE_PAGEDOWN]) {
637 cameraZ += 3.0f * deltaTime;
638 }
639 cameraZ = std::clamp(cameraZ, 1.0f, 20.0f);
640
641 constexpr float speed = 2.0f;
642 if (keyState[SDL_SCANCODE_UP]) {
643 paddle1.position.y += speed * deltaTime;
644 }
645 if (keyState[SDL_SCANCODE_DOWN]) {
646 paddle1.position.y -= speed * deltaTime;
647 }
648 clampPaddle(paddle1);
649 }
650
651 void updateFromGamepad(float deltaTime) {
652 if (gamepad == nullptr) {
653 return;
654 }
655
656 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_BACK)) {
657 exit();
658 return;
659 }
660
661 constexpr float paddleMoveSpeed = 2.0f;
662
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);
666
667 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP)) {
668 paddle1.position.y += 2.0f * deltaTime;
669 }
670 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN)) {
671 paddle1.position.y -= 2.0f * deltaTime;
672 }
673 clampPaddle(paddle1);
674
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;
679
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;
684 }
685 if (rightTrigger > controllerDeadzone) {
686 cameraZ -= (rightTrigger / controllerMax) * 3.0f * deltaTime;
687 }
688
689 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) {
690 cameraZ += 3.0f * deltaTime;
691 }
692 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) {
693 cameraZ -= 3.0f * deltaTime;
694 }
695
696 cameraZ = std::clamp(cameraZ, 1.0f, 20.0f);
697 }
698
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;
703 }
704 if (ball.position.y < paddle2.position.y - paddle2.size.y / 4.0f) {
705 paddle2.position.y -= paddleSpeed * deltaTime;
706 }
707 clampPaddle(paddle2);
708 }
709
710 void printHud(float deltaTime) {
711 fpsAccumulator += deltaTime;
712 ++fpsFrameCounter;
713 if (fpsAccumulator >= 0.2f) {
714 fpsValue = static_cast<float>(fpsFrameCounter) / fpsAccumulator;
715 fpsAccumulator = 0.0f;
716 fpsFrameCounter = 0;
717 }
718
719 const SDL_Color white{255, 255, 255, 255};
720 const SDL_Color yellow{255, 255, 0, 255};
721
722 printText("Vulkan Pong", 50, 50, white);
723 printText(std::format("Player 1: {} : Player 2: {}", score1, score2), 50, 80, yellow);
724
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";
729
730 printText(fpsStream.str() + " | Mode: " + polygonMode, 50, 110, white);
731 printText(controllerStatus, 50, 140, white);
732 }
733
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) {
736 Particle p;
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);
739 p.life = 0.6f;
740 p.color = paddleColor;
741 particles.push_back(p);
742 }
743 }
744
745 void updateParticles(float deltaTime) {
746 if (mappedParticleData == nullptr) {
747 activeParticleCount = 0;
748 return;
749 }
750
751 const glm::vec3 gravity(0.0f, -2.0f, 0.0f);
752 activeParticleCount = 0;
753 auto *particleBufferData = static_cast<ParticleVertex *>(mappedParticleData);
754
755 particles.erase(std::remove_if(particles.begin(), particles.end(), [](const Particle &p) { return p.life <= 0.0f; }), particles.end());
756
757 for (auto &p : particles) {
758 if (p.life <= 0.0f || activeParticleCount >= static_cast<uint32_t>(maxParticles)) {
759 continue;
760 }
761
762 p.velocity += gravity * deltaTime;
763 p.position += p.velocity * deltaTime;
764 p.life -= deltaTime * 1.5f;
765 p.color.a = p.life;
766
767 particleBufferData[activeParticleCount].position = p.position;
768 particleBufferData[activeParticleCount].color = p.color;
769 ++activeParticleCount;
770 }
771 }
772
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);
776
777 VK_CHECK_RESULT(vkMapMemory(device, particleBufferMemory, 0, bufferSize, 0, &mappedParticleData));
778 }
779
780 void cleanupParticleResources() {
781 cleanupParticleSwapchainResources();
782 if (mappedParticleData != nullptr && particleBufferMemory != VK_NULL_HANDLE) {
783 vkUnmapMemory(device, particleBufferMemory);
784 mappedParticleData = nullptr;
785 }
786 if (particleBuffer != VK_NULL_HANDLE) {
787 vkDestroyBuffer(device, particleBuffer, nullptr);
788 particleBuffer = VK_NULL_HANDLE;
789 }
790 if (particleBufferMemory != VK_NULL_HANDLE) {
791 vkFreeMemory(device, particleBufferMemory, nullptr);
792 particleBufferMemory = VK_NULL_HANDLE;
793 }
794 }
795
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;
801 }
802 if (particleUniformBuffers[i] != VK_NULL_HANDLE) {
803 vkDestroyBuffer(device, particleUniformBuffers[i], nullptr);
804 particleUniformBuffers[i] = VK_NULL_HANDLE;
805 }
806 if (particleUniformBufferMemories[i] != VK_NULL_HANDLE) {
807 vkFreeMemory(device, particleUniformBufferMemories[i], nullptr);
808 particleUniformBufferMemories[i] = VK_NULL_HANDLE;
809 }
810 }
811 particleUniformBuffers.clear();
812 particleUniformBufferMemories.clear();
813 particleUniformBufferMapped.clear();
814 }
815
816 void cleanupParticleSwapchainResources() {
817 cleanupParticlePipeline();
818 if (particleDescriptorPool != VK_NULL_HANDLE) {
819 vkDestroyDescriptorPool(device, particleDescriptorPool, nullptr);
820 particleDescriptorPool = VK_NULL_HANDLE;
821 }
822 particleDescriptorSets.clear();
823 destroyParticleUniformBuffers();
824 }
825
826 void createParticleUniformBuffers() {
827 const size_t imageCount = getSwapchainImageCount();
828 const VkDeviceSize bufferSize = sizeof(PongUniformBufferObject);
829
830 particleUniformBuffers.resize(imageCount, VK_NULL_HANDLE);
831 particleUniformBufferMemories.resize(imageCount, VK_NULL_HANDLE);
832 particleUniformBufferMapped.resize(imageCount, nullptr);
833
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]));
837 }
838 }
839
840 void createParticleDescriptorPool() {
841 const uint32_t imageCount = static_cast<uint32_t>(getSwapchainImageCount());
842
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;
848
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;
854
855 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo, nullptr, &particleDescriptorPool));
856 }
857
858 void createParticleDescriptorSets() {
859 const size_t imageCount = getSwapchainImageCount();
860 std::vector<VkDescriptorSetLayout> layouts(imageCount, starDescriptorSetLayout);
861
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();
867
868 particleDescriptorSets.resize(imageCount, VK_NULL_HANDLE);
869 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, particleDescriptorSets.data()));
870
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;
876
877 VkDescriptorBufferInfo bufferInfo{};
878 bufferInfo.buffer = particleUniformBuffers[i];
879 bufferInfo.offset = 0;
880 bufferInfo.range = sizeof(PongUniformBufferObject);
881
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;
889
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;
896
897 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
898 }
899 }
900
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) {
903 return;
904 }
905
906 PongUniformBufferObject ubo{};
907 ubo.model = glm::mat4(1.0f);
908 ubo.view = view;
909 ubo.proj = proj;
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));
913 }
914
915 void cleanupParticlePipeline() {
916 if (particlePipeline != VK_NULL_HANDLE) {
917 vkDestroyPipeline(device, particlePipeline, nullptr);
918 particlePipeline = VK_NULL_HANDLE;
919 }
920 if (particlePipelineLayout != VK_NULL_HANDLE) {
921 vkDestroyPipelineLayout(device, particlePipelineLayout, nullptr);
922 particlePipelineLayout = VK_NULL_HANDLE;
923 }
924 }
925
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()) {
928 return;
929 }
930
931 updateParticleUniform(imageIndex, extent, view, proj, SDL_GetTicks() * 0.001f);
932
933 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, particlePipeline);
934
935 VkViewport viewport{};
936 viewport.x = 0.0f;
937 viewport.y = 0.0f;
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);
943
944 VkRect2D scissor{};
945 scissor.offset = {0, 0};
946 scissor.extent = extent;
947 vkCmdSetScissor(cmd, 0, 1, &scissor);
948
949 VkBuffer vertexBuffers[] = {particleBuffer};
950 VkDeviceSize offsets[] = {0};
951 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
952
953 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, particlePipelineLayout, 0, 1, &particleDescriptorSets[imageIndex], 0, nullptr);
954
955 vkCmdDraw(cmd, activeParticleCount, 1, 0, 0);
956 }
957
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);
962 return dist(eng);
963 }
964
965 glm::vec3 getStarColor(float temperature) const {
966 float r = 1.0f;
967 float g = 1.0f;
968 float b = 1.0f;
969
970 if (temperature < 3700.0f) {
971 r = 1.0f;
972 g = temperature / 3700.0f * 0.6f;
973 b = 0.0f;
974 } else if (temperature < 5200.0f) {
975 r = 1.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) {
979 r = 1.0f;
980 g = 1.0f;
981 b = (temperature - 5200.0f) / 800.0f * 0.7f;
982 } else if (temperature < 7500.0f) {
983 r = 1.0f;
984 g = 1.0f;
985 b = 0.7f + (temperature - 6000.0f) / 1500.0f * 0.3f;
986 } else {
987 r = 0.7f - (temperature - 7500.0f) / 10000.0f * 0.4f;
988 g = 0.8f + (temperature - 7500.0f) / 10000.0f * 0.2f;
989 b = 1.0f;
990 }
991
992 return glm::vec3(r, g, b);
993 }
994
995 float magnitudeToSize(float magnitude) const { return glm::clamp(15.0f - magnitude * 2.0f, 1.0f, 25.0f); }
996
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);
1000 }
1001
1002 void initStarfield(int numStarsParam) {
1003 if (starfieldInitialized) {
1004 return;
1005 }
1006
1007 numStars = numStarsParam;
1008 stars.resize(static_cast<size_t>(numStars));
1009
1010 constexpr float pi = 3.14159265358979323846f;
1011 for (int i = 0; i < numStars; ++i) {
1012 auto &star = stars[static_cast<size_t>(i)];
1013
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);
1017
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);
1021
1022 star.vx = randomFloat(-0.001f, 0.001f);
1023 star.vy = randomFloat(-0.001f, 0.001f);
1024 star.vz = randomFloat(-0.001f, 0.001f);
1025
1026 const float r = randomFloat(0.0f, 1.0f);
1027 if (r < 0.05f) {
1028 star.magnitude = randomFloat(-1.0f, 2.0f);
1029 star.starType = 1;
1030 } else if (r < 0.3f) {
1031 star.magnitude = randomFloat(2.0f, 4.0f);
1032 star.starType = 0;
1033 } else {
1034 star.magnitude = randomFloat(4.0f, 6.5f);
1035 star.starType = 2;
1036 }
1037
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);
1042 } else {
1043 star.temperature = randomFloat(2500.0f, 4000.0f);
1044 }
1045
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);
1049 }
1050
1051 createStarTexture();
1052 createStarVertexBuffer();
1053 createStarSwapchainResources();
1054
1055 lastStarUpdateTime = SDL_GetTicks();
1056 starfieldInitialized = true;
1057 }
1058
1059 void cleanupStarResources() {
1060 if (starVertexBufferMapped != nullptr && starVertexBufferMemory != VK_NULL_HANDLE) {
1061 vkUnmapMemory(device, starVertexBufferMemory);
1062 starVertexBufferMapped = nullptr;
1063 }
1064 if (starVertexBuffer != VK_NULL_HANDLE) {
1065 vkDestroyBuffer(device, starVertexBuffer, nullptr);
1066 starVertexBuffer = VK_NULL_HANDLE;
1067 }
1068 if (starVertexBufferMemory != VK_NULL_HANDLE) {
1069 vkFreeMemory(device, starVertexBufferMemory, nullptr);
1070 starVertexBufferMemory = VK_NULL_HANDLE;
1071 }
1072
1073 if (starSampler != VK_NULL_HANDLE) {
1074 vkDestroySampler(device, starSampler, nullptr);
1075 starSampler = VK_NULL_HANDLE;
1076 }
1077 if (starTextureView != VK_NULL_HANDLE) {
1078 vkDestroyImageView(device, starTextureView, nullptr);
1079 starTextureView = VK_NULL_HANDLE;
1080 }
1081 if (starTexture != VK_NULL_HANDLE) {
1082 vkDestroyImage(device, starTexture, nullptr);
1083 starTexture = VK_NULL_HANDLE;
1084 }
1085 if (starTextureMemory != VK_NULL_HANDLE) {
1086 vkFreeMemory(device, starTextureMemory, nullptr);
1087 starTextureMemory = VK_NULL_HANDLE;
1088 }
1089 }
1090
1091 void cleanupStarSwapchainResources() {
1092 cleanupParticleSwapchainResources();
1093
1094 if (starPipeline != VK_NULL_HANDLE) {
1095 vkDestroyPipeline(device, starPipeline, nullptr);
1096 starPipeline = VK_NULL_HANDLE;
1097 }
1098 if (starPipelineLayout != VK_NULL_HANDLE) {
1099 vkDestroyPipelineLayout(device, starPipelineLayout, nullptr);
1100 starPipelineLayout = VK_NULL_HANDLE;
1101 }
1102 if (starDescriptorPool != VK_NULL_HANDLE) {
1103 vkDestroyDescriptorPool(device, starDescriptorPool, nullptr);
1104 starDescriptorPool = VK_NULL_HANDLE;
1105 }
1106 if (starDescriptorSetLayout != VK_NULL_HANDLE) {
1107 vkDestroyDescriptorSetLayout(device, starDescriptorSetLayout, nullptr);
1108 starDescriptorSetLayout = VK_NULL_HANDLE;
1109 }
1110
1111 destroyStarUniformBuffers();
1112 starDescriptorSets.clear();
1113 }
1114
1115 void createStarSwapchainResources() {
1116 if (!starfieldInitialized && stars.empty()) {
1117 return;
1118 }
1119 createStarDescriptorSetLayout();
1120 createStarUniformBuffers();
1121 createStarDescriptorPool();
1122 createStarDescriptorSets();
1123 createStarPipeline();
1124 createParticleUniformBuffers();
1125 createParticleDescriptorPool();
1126 createParticleDescriptorSets();
1127 createParticlePipeline();
1128 }
1129
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");
1134 }
1135
1136 const VkDeviceSize imageSize = static_cast<VkDeviceSize>(starImg->w) * static_cast<VkDeviceSize>(starImg->h) * 4U;
1137
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);
1141
1142 void *data = nullptr;
1143 VK_CHECK_RESULT(vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data));
1144 std::memcpy(data, starImg->pixels, static_cast<size_t>(imageSize));
1145 vkUnmapMemory(device, stagingBufferMemory);
1146
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);
1148
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);
1152
1153 vkDestroyBuffer(device, stagingBuffer, nullptr);
1154 vkFreeMemory(device, stagingBufferMemory, nullptr);
1155
1156 starTextureView = createImageView(starTexture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1157
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;
1171 VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &starSampler));
1172
1173 SDL_DestroySurface(starImg);
1174 }
1175
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);
1179
1180 VK_CHECK_RESULT(vkMapMemory(device, starVertexBufferMemory, 0, bufferSize, 0, &starVertexBufferMapped));
1181 }
1182
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;
1189
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;
1195
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));
1202 }
1203
1204 void createStarUniformBuffers() {
1205 const size_t imageCount = getSwapchainImageCount();
1206 const VkDeviceSize bufferSize = sizeof(PongUniformBufferObject);
1207
1208 starUniformBuffers.resize(imageCount, VK_NULL_HANDLE);
1209 starUniformBufferMemories.resize(imageCount, VK_NULL_HANDLE);
1210 starUniformBufferMapped.resize(imageCount, nullptr);
1211
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]));
1215 }
1216 }
1217
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;
1223 }
1224 if (starUniformBuffers[i] != VK_NULL_HANDLE) {
1225 vkDestroyBuffer(device, starUniformBuffers[i], nullptr);
1226 starUniformBuffers[i] = VK_NULL_HANDLE;
1227 }
1228 if (starUniformBufferMemories[i] != VK_NULL_HANDLE) {
1229 vkFreeMemory(device, starUniformBufferMemories[i], nullptr);
1230 starUniformBufferMemories[i] = VK_NULL_HANDLE;
1231 }
1232 }
1233 starUniformBuffers.clear();
1234 starUniformBufferMemories.clear();
1235 starUniformBufferMapped.clear();
1236 }
1237
1238 void createStarDescriptorPool() {
1239 const uint32_t imageCount = static_cast<uint32_t>(getSwapchainImageCount());
1240
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;
1246
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;
1252
1253 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo, nullptr, &starDescriptorPool));
1254 }
1255
1256 void createStarDescriptorSets() {
1257 const size_t imageCount = getSwapchainImageCount();
1258 std::vector<VkDescriptorSetLayout> layouts(imageCount, starDescriptorSetLayout);
1259
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();
1265
1266 starDescriptorSets.resize(imageCount, VK_NULL_HANDLE);
1267 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, starDescriptorSets.data()));
1268
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;
1274
1275 VkDescriptorBufferInfo bufferInfo{};
1276 bufferInfo.buffer = starUniformBuffers[i];
1277 bufferInfo.offset = 0;
1278 bufferInfo.range = sizeof(PongUniformBufferObject);
1279
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;
1287
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;
1294
1295 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
1296 }
1297 }
1298
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");
1302
1303 VkShaderModule vertShaderModule = createShaderModule(device, vertShaderCode);
1304 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
1305
1306 try {
1307 fragShaderModule = createShaderModule(device, fragShaderCode);
1308
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";
1314
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";
1320
1321 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
1322
1323 VkVertexInputBindingDescription bindingDescription{};
1324 bindingDescription.binding = 0;
1325 bindingDescription.stride = sizeof(StarVertex);
1326 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1327
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);
1333
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);
1338
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);
1343
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();
1350
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;
1355
1356 VkPipelineViewportStateCreateInfo viewportState{};
1357 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1358 viewportState.viewportCount = 1;
1359 viewportState.scissorCount = 1;
1360
1361 std::array<VkDynamicState, 2> dynamicStates = {
1362 VK_DYNAMIC_STATE_VIEWPORT,
1363 VK_DYNAMIC_STATE_SCISSOR,
1364 };
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();
1369
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;
1379
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;
1384
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;
1392
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;
1402
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;
1408
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;
1414
1415 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &layoutInfo, nullptr, &starPipelineLayout));
1416
1417 VkFormat colorFormat = getSwapchainFormat();
1418 VkFormat depthFormat = getDepthFormat();
1419
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;
1426
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;
1445
1446 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &starPipeline));
1447 } catch (...) {
1448 if (fragShaderModule != VK_NULL_HANDLE) {
1449 vkDestroyShaderModule(device, fragShaderModule, nullptr);
1450 }
1451 vkDestroyShaderModule(device, vertShaderModule, nullptr);
1452 throw;
1453 }
1454
1455 if (fragShaderModule != VK_NULL_HANDLE) {
1456 vkDestroyShaderModule(device, fragShaderModule, nullptr);
1457 }
1458 vkDestroyShaderModule(device, vertShaderModule, nullptr);
1459 }
1460
1461 void updateStarfield(float deltaTime) {
1462 if (!starfieldInitialized || starVertexBufferMapped == nullptr) {
1463 return;
1464 }
1465 if (deltaTime > 0.1f) {
1466 deltaTime = 0.1f;
1467 }
1468
1469 const float time = SDL_GetTicks() * 0.001f;
1470 auto *vertices = static_cast<StarVertex *>(starVertexBufferMapped);
1471
1472 for (int i = 0; i < numStars; ++i) {
1473 auto &star = stars[static_cast<size_t>(i)];
1474
1475 star.x += star.vx * deltaTime;
1476 star.y += star.vy * deltaTime;
1477 star.z += star.vz * deltaTime;
1478
1479 vertices[i].pos[0] = star.x;
1480 vertices[i].pos[1] = star.y;
1481 vertices[i].pos[2] = star.z;
1482
1483 float twinkleFactor = 1.0f;
1484 if (atmosphericTwinkle > 0.0f) {
1485 twinkleFactor = 0.7f + 0.3f * std::sin(time * star.twinkle) * atmosphericTwinkle;
1486 }
1487
1488 float size = star.size * twinkleFactor;
1489 if (star.isConstellation) {
1490 size *= 1.2f;
1491 }
1492 vertices[i].size = size;
1493
1494 const glm::vec3 starColor = getStarColor(star.temperature);
1495 const float alpha = magnitudeToAlpha(star.magnitude) * twinkleFactor;
1496
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;
1501 }
1502 }
1503
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) {
1506 return;
1507 }
1508
1509 PongUniformBufferObject ubo{};
1510 ubo.model = glm::mat4(1.0f);
1511 ubo.view = view;
1512 ubo.proj = proj;
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));
1516 }
1517
1518 void drawStarfield(VkCommandBuffer cmd, uint32_t imageIndex, const VkExtent2D &extent) {
1519 if (!starfieldInitialized || starPipeline == VK_NULL_HANDLE || imageIndex >= starDescriptorSets.size()) {
1520 return;
1521 }
1522
1523 const Uint32 currentTime = SDL_GetTicks();
1524 const float deltaTime = static_cast<float>(currentTime - lastStarUpdateTime) / 1000.0f;
1525 lastStarUpdateTime = currentTime;
1526 updateStarfield(deltaTime);
1527
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;
1531
1532 updateStarUniform(imageIndex, extent, view, proj, SDL_GetTicks() * 0.001f);
1533
1534 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, starPipeline);
1535
1536 VkViewport viewport{};
1537 viewport.x = 0.0f;
1538 viewport.y = 0.0f;
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);
1544
1545 VkRect2D scissor{};
1546 scissor.offset = {0, 0};
1547 scissor.extent = extent;
1548 vkCmdSetScissor(cmd, 0, 1, &scissor);
1549
1550 VkBuffer vertexBuffers[] = {starVertexBuffer};
1551 VkDeviceSize offsets[] = {0};
1552 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
1553
1554 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, starPipelineLayout, 0, 1, &starDescriptorSets[imageIndex], 0, nullptr);
1555
1556 vkCmdDraw(cmd, static_cast<uint32_t>(numStars), 1, 0, 0);
1557 }
1558
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");
1562
1563 VkShaderModule vertShaderModule = createShaderModule(device, vertShaderCode);
1564 VkShaderModule fragShaderModule = VK_NULL_HANDLE;
1565
1566 try {
1567 fragShaderModule = createShaderModule(device, fragShaderCode);
1568
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";
1574
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";
1580
1581 std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {vertStage, fragStage};
1582
1583 VkVertexInputBindingDescription bindingDescription{};
1584 bindingDescription.binding = 0;
1585 bindingDescription.stride = sizeof(ParticleVertex);
1586 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1587
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);
1593
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);
1598
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();
1605
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;
1610
1611 VkPipelineViewportStateCreateInfo viewportState{};
1612 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1613 viewportState.viewportCount = 1;
1614 viewportState.scissorCount = 1;
1615
1616 std::array<VkDynamicState, 2> dynamicStates = {
1617 VK_DYNAMIC_STATE_VIEWPORT,
1618 VK_DYNAMIC_STATE_SCISSOR,
1619 };
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();
1624
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;
1634
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;
1639
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;
1647
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;
1657
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;
1663
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;
1669
1670 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &layoutInfo, nullptr, &particlePipelineLayout));
1671
1672 VkFormat colorFormat = getSwapchainFormat();
1673 VkFormat depthFormat = getDepthFormat();
1674
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;
1681
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;
1700
1701 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &particlePipeline));
1702 } catch (...) {
1703 if (fragShaderModule != VK_NULL_HANDLE) {
1704 vkDestroyShaderModule(device, fragShaderModule, nullptr);
1705 }
1706 vkDestroyShaderModule(device, vertShaderModule, nullptr);
1707 throw;
1708 }
1709
1710 if (fragShaderModule != VK_NULL_HANDLE) {
1711 vkDestroyShaderModule(device, fragShaderModule, nullptr);
1712 }
1713 vkDestroyShaderModule(device, vertShaderModule, nullptr);
1714 }
1715
1716 bool openGamepad(SDL_JoystickID id) {
1717 if (gamepad != nullptr && gamepadId == id) {
1718 return true;
1719 }
1720 closeGamepad();
1721 gamepad = SDL_OpenGamepad(id);
1722 if (gamepad == nullptr) {
1723 return false;
1724 }
1725 gamepadId = id;
1726 return true;
1727 }
1728
1729 void closeGamepad() {
1730 if (gamepad != nullptr) {
1731 SDL_CloseGamepad(gamepad);
1732 gamepad = nullptr;
1733 gamepadId = 0;
1734 }
1735 }
1736
1737 void tryOpenFirstGamepad() {
1738 if (gamepad != nullptr) {
1739 return;
1740 }
1741 int count = 0;
1742 SDL_JoystickID *ids = SDL_GetGamepads(&count);
1743 if (ids == nullptr || count <= 0) {
1744 if (ids != nullptr) {
1745 SDL_free(ids);
1746 }
1747 return;
1748 }
1749 openGamepad(ids[0]);
1750 SDL_free(ids);
1751 }
1752
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;
1756
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;
1762
1763 try {
1764 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &newBuffer));
1765
1766 VkMemoryRequirements memRequirements{};
1767 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
1768
1769 VkMemoryAllocateInfo allocInfo{};
1770 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1771 allocInfo.allocationSize = memRequirements.size;
1772 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1773 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &newMemory));
1774 VK_CHECK_RESULT(vkBindBufferMemory(device, newBuffer, newMemory, 0));
1775 } catch (...) {
1776 if (newBuffer != VK_NULL_HANDLE) {
1777 vkDestroyBuffer(device, newBuffer, nullptr);
1778 }
1779 if (newMemory != VK_NULL_HANDLE) {
1780 vkFreeMemory(device, newMemory, nullptr);
1781 }
1782 throw;
1783 }
1784
1785 if (buffer != VK_NULL_HANDLE) {
1786 vkDestroyBuffer(device, buffer, nullptr);
1787 }
1788 if (bufferMemory != VK_NULL_HANDLE) {
1789 vkFreeMemory(device, bufferMemory, nullptr);
1790 }
1791 buffer = newBuffer;
1792 bufferMemory = newMemory;
1793 }
1794
1795 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const {
1796 VkPhysicalDeviceMemoryProperties memProperties{};
1797 vkGetPhysicalDeviceMemoryProperties(physical_device, &memProperties);
1798
1799 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
1800 if ((typeFilter & (1U << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1801 return i;
1802 }
1803 }
1804
1805 throw mxvk::Exception("Failed to find suitable memory type");
1806 }
1807
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;
1812 allocInfo.commandPool = command_pool;
1813 allocInfo.commandBufferCount = 1;
1814
1815 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
1816 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
1817
1818 VkCommandBufferBeginInfo beginInfo{};
1819 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1820 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1821 VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &beginInfo));
1822
1823 return commandBuffer;
1824 }
1825
1826 void endSingleTimeCommands(VkCommandBuffer commandBuffer) const {
1827 VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
1828
1829 VkSubmitInfo submitInfo{};
1830 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1831 submitInfo.commandBufferCount = 1;
1832 submitInfo.pCommandBuffers = &commandBuffer;
1833
1834 VK_CHECK_RESULT(vkQueueSubmit(graphics_queue, 1, &submitInfo, VK_NULL_HANDLE));
1835 VK_CHECK_RESULT(vkQueueWaitIdle(graphics_queue));
1836
1837 vkFreeCommandBuffers(device, command_pool, 1, &commandBuffer);
1838 }
1839
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;
1843
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;
1858
1859 try {
1860 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &newImage));
1861
1862 VkMemoryRequirements memRequirements{};
1863 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
1864
1865 VkMemoryAllocateInfo allocInfo{};
1866 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1867 allocInfo.allocationSize = memRequirements.size;
1868 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1869 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &newMemory));
1870 VK_CHECK_RESULT(vkBindImageMemory(device, newImage, newMemory, 0));
1871 } catch (...) {
1872 if (newImage != VK_NULL_HANDLE) {
1873 vkDestroyImage(device, newImage, nullptr);
1874 }
1875 if (newMemory != VK_NULL_HANDLE) {
1876 vkFreeMemory(device, newMemory, nullptr);
1877 }
1878 throw;
1879 }
1880
1881 if (image != VK_NULL_HANDLE) {
1882 vkDestroyImage(device, image, nullptr);
1883 }
1884 if (memory != VK_NULL_HANDLE) {
1885 vkFreeMemory(device, memory, nullptr);
1886 }
1887 image = newImage;
1888 memory = newMemory;
1889 }
1890
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;
1902
1903 VkImageView imageView = VK_NULL_HANDLE;
1904 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &imageView));
1905 return imageView;
1906 }
1907
1908 void transitionImageLayout(VkImage image, VkFormat, VkImageLayout oldLayout, VkImageLayout newLayout) const {
1909 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1910
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;
1923
1924 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1925 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1926
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;
1937 } else {
1938 throw mxvk::Exception("Unsupported image layout transition");
1939 }
1940
1941 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
1942
1943 endSingleTimeCommands(commandBuffer);
1944 }
1945
1946 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) const {
1947 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1948
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};
1959
1960 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
1961
1962 endSingleTimeCommands(commandBuffer);
1963 }
1964 };
1965
1966} // namespace
1967
1968int main(int argc, char **argv) {
1969 try {
1970 Arguments args = proc_args(argc, argv);
1971 PongWindow window(args.path, args.width, args.height, args.fullscreen, args.enable_vsync);
1972 window.loop();
1973 } catch (mxvk::Exception &e) {
1974 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
1975 return EXIT_FAILURE;
1976 } catch (ArgException<std::string> &e) {
1977 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
1978 return EXIT_FAILURE;
1979 } catch (const std::exception &e) {
1980 std::cerr << std::format("std::exception: {}\n", e.what());
1981 return EXIT_FAILURE;
1982 }
1983
1984 return EXIT_SUCCESS;
1985}
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:854
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:169
Ball(const glm::vec3 &pos, const glm::vec3 &vel, float r)
Definition pong.cpp:129
glm::mat4 modelMatrix() const
Definition pong.cpp:148
void update(float deltaTime, Paddle &paddle1, Paddle &paddle2, int &score1, int &score2)
Definition pong.cpp:156
void update(float deltaTime)
Definition pong.cpp:90
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:259
void onSwapchainAboutToRecreate() override
Called right before swapchain-dependent resources are recreated.
Definition pong.cpp:283
void proc() override
Execute one processing/update step.
Definition pong.cpp:383
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override
Optional hook for derived classes to record extra draw commands.
Definition pong.cpp:422
void event(SDL_Event &e) override
Handle one SDL event.
Definition pong.cpp:292
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
Definition pong.cpp:285
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:39
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:222
VkDevice device
Definition mxvk.hpp:606
static VkShaderModule createShaderModule(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
Definition mxvk.cpp:154
size_t getSwapchainImageCount() const noexcept
Get the number of swapchain images currently allocated.
Definition mxvk.hpp:239
void exit()
Request loop termination.
Definition mxvk.cpp:1378
VkCommandPool command_pool
Definition mxvk.hpp:627
bool ensureRenderResources()
Ensure deferred render resources are initialized.
Definition mxvk.cpp:1787
VK_Window()=default
Construct an empty window object.
VkPhysicalDevice physical_device
Definition mxvk.hpp:605
VkFormat getDepthFormat() const noexcept
Get the depth format used for dynamic rendering attachments.
Definition mxvk.hpp:236
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
Definition mxvk.cpp:3872
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:3919
static std::vector< char > loadSpv(const std::string &path)
Load a SPIR-V file from disk.
Definition mxvk.cpp:152
VkQueue graphics_queue
Definition mxvk.hpp:609
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
Definition mxvk.hpp:213
#define MXVK_VALIDATION
Definition mxvk.hpp:28
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
int main()
Definition main.py:165
Definition model.py:1
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition mxvk_png.cpp:89
#define pong_ASSET_DIR
Definition pong.cpp:34
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:718
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:724
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:738
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:721
std::string path
Asset root; proc_args() defaults it to the executable directory.
Definition argz.hpp:723
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:720