MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
defender_window.cpp
Go to the documentation of this file.
1#include "defender_window.hpp"
2
4
5#include <algorithm>
6#include <chrono>
7#include <cmath>
8#include <cstdint>
9#include <cstring>
10#include <format>
11#include <memory>
12#include <string>
13#include <utility>
14
15#include <glm/ext/matrix_clip_space.hpp>
16#include <glm/ext/matrix_transform.hpp>
17
18namespace defender {
19
20 DefenderWindow::DefenderWindow(const std::string &path, int width, int height, bool fullscreen, bool enable_vsync)
21 : mxvk::VK_Window("Defender Starfield Demo", width, height, fullscreen, MXVK_VALIDATION, enable_vsync),
22 asset_root((path.empty() || path == ".") ? std::string(DEFENDER_ASSET_DIR) : path) {
23 setClearColor(0.0f, 0.0f, 0.01f, 1.0f);
24 setFont(asset_root + "/data/font.ttf", DEFAULT_FONT_SIZE);
25 hud_font.reset(asset_root + "/data/font.ttf", HUD_FONT_SIZE);
26 countdown_font.reset(asset_root + "/data/font.ttf", COUNTDOWN_FONT_SIZE);
27
28 const std::string model_vert = asset_root + "/data/model.vert.spv";
29 const std::string model_frag = asset_root + "/data/model.frag.spv";
30 ship_model.load(this, asset_root + "/data/starship.obj", asset_root + "/data/starship.mtl", asset_root + "/data", 1.0f);
31 ship_model.setShaders(this, model_vert, model_frag);
32 for (auto &asteroid_model : asteroid_models) {
33 asteroid_model.load(this, asset_root + "/data/asteroid.obj", asset_root + "/data/asteroid.mtl", asset_root + "/data", 1.0f);
34 asteroid_model.setShaders(this, model_vert, model_frag);
35 }
36
37 intro_sprite = createSprite(
38 asset_root + "/data/intro.png",
39 asset_root + "/data/sprite.vert.spv",
40 asset_root + "/data/intro.frag.spv");
41 fade_overlay_sprite = createSprite(
42 1,
43 1,
44 asset_root + "/data/sprite.vert.spv",
45 asset_root + "/data/fade_overlay.frag.spv");
46 const uint32_t black_pixel = 0xFF000000u;
47 fade_overlay_sprite->updateTexture(&black_pixel, 1, 1);
48 attachPostProcessingShader(asset_root + "/data/crt.frag.spv", 0.0f, 3.0f, 0.5f, 0.002f);
50 matrix::RainConfig intro_rain_config = matrix::make_matrix_rain_config(asset_root, false);
51 intro_rain_config.color = "#ff0000";
52 intro_rain_config.surface_width = INTRO_RAIN_TEXTURE_WIDTH;
53 intro_rain_config.surface_height = INTRO_RAIN_TEXTURE_HEIGHT;
54 intro_rain = std::make_unique<matrix::Rain>(*this, std::move(intro_rain_config));
55 reset_intro_screen();
56
57 star_sprite = createSprite3D(asset_root + "/data/star.png");
58 star_sprite->setDepthTestEnabled(false);
59 star_sprite->setDepthWriteEnabled(false);
60 star_sprite->setAlphaDiscardThreshold(0.02f);
61 star_field.init(STAR_COUNT, 16.0f, 112.0f);
62 star_field.setSprite(star_sprite);
63
64 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> terrain_surface(SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA32), SDL_DestroySurface);
65 if (terrain_surface == nullptr) {
66 throw mxvk::Exception("Failed to create defender terrain surface");
67 }
68 const SDL_PixelFormatDetails *terrain_format = SDL_GetPixelFormatDetails(terrain_surface->format);
69 if (terrain_format == nullptr) {
70 throw mxvk::Exception("Failed to query defender terrain surface format");
71 }
72 SDL_FillSurfaceRect(terrain_surface.get(), nullptr, SDL_MapRGBA(terrain_format, nullptr, 255, 255, 255, 255));
73 terrain_sprite = createSprite3D(terrain_surface.get());
74 terrain_sprite->setDepthTestEnabled(false);
75 terrain_sprite->setDepthWriteEnabled(false);
76 terrain_sprite->setAlphaDiscardThreshold(0.01f);
77
78 projectile_sprite = createSprite3D(asset_root + "/data/particle_projectile.png");
79 projectile_sprite->setDepthTestEnabled(true);
80 projectile_sprite->setDepthWriteEnabled(false);
81 projectile_sprite->setAlphaDiscardThreshold(0.05f);
82
83 for (int i = 0; i < UFO_ANIMATION_FRAMES; ++i) {
84 const std::string frame_path = asset_root + std::format("/data/ufo_lights_{:02d}.png", i);
85 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> saucer_surface(space::load_color_keyed_png(frame_path, 8, 24), SDL_DestroySurface);
86 ufo_sprite_bounds[static_cast<std::size_t>(UfoSpriteSet::Classic)][static_cast<std::size_t>(i)] = calculate_alpha_bounds(saucer_surface.get());
87 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Classic)][static_cast<std::size_t>(i)] = createSprite3D(saucer_surface.get());
88 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Classic)][static_cast<std::size_t>(i)]->setDepthTestEnabled(true);
89 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Classic)][static_cast<std::size_t>(i)]->setDepthWriteEnabled(false);
90 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Classic)][static_cast<std::size_t>(i)]->setAlphaDiscardThreshold(0.02f);
91
92 const std::string ufox_frame_path = asset_root + std::format("/data/ufox{}.png", i + 1);
93 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> ufox_surface(load_light_background_png(ufox_frame_path), SDL_DestroySurface);
94 ufo_sprite_bounds[static_cast<std::size_t>(UfoSpriteSet::Ufox)][static_cast<std::size_t>(i)] = calculate_alpha_bounds(ufox_surface.get());
95 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Ufox)][static_cast<std::size_t>(i)] = createSprite3D(ufox_surface.get());
96 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Ufox)][static_cast<std::size_t>(i)]->setDepthTestEnabled(true);
97 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Ufox)][static_cast<std::size_t>(i)]->setDepthWriteEnabled(false);
98 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Ufox)][static_cast<std::size_t>(i)]->setAlphaDiscardThreshold(0.02f);
99
100 const std::string alien_frame_path = asset_root + std::format("/data/alien{}.png", i + 1);
101 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> alien_surface(load_light_background_png(alien_frame_path), SDL_DestroySurface);
102 ufo_sprite_bounds[static_cast<std::size_t>(UfoSpriteSet::Alien)][static_cast<std::size_t>(i)] = calculate_alpha_bounds(alien_surface.get());
103 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Alien)][static_cast<std::size_t>(i)] = createSprite3D(alien_surface.get());
104 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Alien)][static_cast<std::size_t>(i)]->setDepthTestEnabled(true);
105 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Alien)][static_cast<std::size_t>(i)]->setDepthWriteEnabled(false);
106 ufo_sprite_sets[static_cast<std::size_t>(UfoSpriteSet::Alien)][static_cast<std::size_t>(i)]->setAlphaDiscardThreshold(0.02f);
107 }
108
109 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> explosion_surface(space::load_color_keyed_png(asset_root + "/data/particle_explosion.png", 12), SDL_DestroySurface);
110 effect_sprite = createSprite3D(explosion_surface.get());
111 effect_sprite->setDepthTestEnabled(true);
112 effect_sprite->setDepthWriteEnabled(false);
113 effect_sprite->setAlphaDiscardThreshold(0.05f);
114
115 init_ufos();
116 init_asteroids();
117 create_flame_resources();
118
119 ship.position = glm::vec3(0.0f, 0.0f, 0.0f);
120 ship.velocity = glm::vec3(0.0f, 0.0f, 0.0f);
121 ship.current_speed = 0.0f;
122 ship.min_speed = 0.0f;
123 ship.max_speed = 28.0f;
124 ship.rotation = glm::vec3(0.0f, -90.0f, 0.0f);
125
126#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
127 background_music = std::make_unique<mxvk::VK_Mixer>();
128 background_music_track = background_music->loadMusic(asset_root + "/data/background.ogg");
129 crash_sound = background_music->loadWav(asset_root + "/data/crash.wav");
130 shoot_sound = background_music->loadWav(asset_root + "/data/shoot.wav");
131 takeoff_sound = background_music->loadWav(asset_root + "/data/takeoff.wav");
132 asteroid_explosion_sound = background_music->loadWav(asset_root + "/data/asteroid.wav");
133 ufo_explosion_sound = background_music->loadWav(asset_root + "/data/ufo.wav");
134 ensure_background_music_playing();
135#endif
136 configure_console();
137 open_controller();
138 }
139
141 if (device != VK_NULL_HANDLE) {
142 vkDeviceWaitIdle(device);
143 }
144#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
145 if (background_music) {
146 background_music->stopMusic();
147 }
148#endif
149 cleanup_flame_resources();
150 ship_model.cleanup(this);
151 for (auto &asteroid_model : asteroid_models) {
152 asteroid_model.cleanup(this);
153 }
154 if (star_sprite != nullptr) {
155 star_sprite->cleanup();
156 }
157 if (terrain_sprite != nullptr) {
158 terrain_sprite->cleanup();
159 }
160 if (projectile_sprite != nullptr) {
161 projectile_sprite->cleanup();
162 }
163 for (auto &sprite_set : ufo_sprite_sets) {
164 for (mxvk::VK_Sprite3D *sprite : sprite_set) {
165 if (sprite != nullptr) {
166 sprite->cleanup();
167 }
168 }
169 }
170 if (effect_sprite != nullptr) {
171 effect_sprite->cleanup();
172 }
173 intro_rain.reset();
174 }
175
176 void DefenderWindow::event(SDL_Event &e) {
177 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
178 if (!controller.active()) {
179 controller.connectEvent(e);
180 }
181 sync_controller_connection();
182 return;
183 }
184 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
185 controller.connectEvent(e);
186 sync_controller_connection();
187 return;
188 }
189
190 const bool was_console_visible = console.isVisible();
191 const bool is_console_toggle = e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_F3;
192 console.handleEvent(e);
193 if (is_console_toggle) {
194 clear_input_state();
195 log_game(console.isVisible() ? "Console opened." : "Console closed.");
196 return;
197 }
198 if (was_console_visible) {
199 clear_input_state();
200 return;
201 }
202
203 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
204 log_game("Exit requested.");
205 exit();
206 return;
207 }
208 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_F4 && !e.key.repeat) {
209 show_fps_counter = !show_fps_counter;
210 log_game(std::string("FPS counter ") + (show_fps_counter ? "enabled." : "disabled."));
211 return;
212 }
213 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_F8 && !e.key.repeat) {
214 crt_enabled = !crt_enabled;
215 setPostProcessingEnabled(crt_enabled);
216 log_game(std::string("CRT effect ") + (crt_enabled ? "enabled." : "disabled."));
217 return;
218 }
219 if (mode == GameMode::Intro && e.type == SDL_EVENT_KEY_DOWN && (e.key.key == SDLK_SPACE || e.key.key == SDLK_RETURN || e.key.key == SDLK_KP_ENTER)) {
220 intro_fade = 0.01f;
221 log_game("Intro skipped from keyboard.");
222 return;
223 }
224 if (game_over && e.type == SDL_EVENT_KEY_DOWN && (e.key.key == SDLK_RETURN || e.key.key == SDLK_KP_ENTER)) {
225 restart_game();
226 log_game("Game restarted from keyboard.");
227 return;
228 }
229
230 if (e.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
231 if (e.gbutton.button == SDL_GAMEPAD_BUTTON_BACK) {
232 log_game("Exit requested from controller.");
233 exit();
234 return;
235 }
236 if (mode == GameMode::Intro && (e.gbutton.button == SDL_GAMEPAD_BUTTON_SOUTH || e.gbutton.button == SDL_GAMEPAD_BUTTON_START)) {
237 intro_fade = 0.01f;
238 log_game("Intro skipped from controller.");
239 return;
240 }
241 if (game_over && (e.gbutton.button == SDL_GAMEPAD_BUTTON_SOUTH || e.gbutton.button == SDL_GAMEPAD_BUTTON_START)) {
242 restart_game();
243 log_game("Game restarted from controller.");
244 return;
245 }
246 if (mode == GameMode::Playing && !game_over && !ship_respawning) {
247 if (e.gbutton.button == SDL_GAMEPAD_BUTTON_SOUTH) {
248 fire_pressed = true;
249 } else if (e.gbutton.button == SDL_GAMEPAD_BUTTON_WEST) {
250 reverse_pressed = true;
251 }
252 }
253 }
254
255 if (e.type == SDL_EVENT_KEY_DOWN || e.type == SDL_EVENT_KEY_UP) {
256 const bool pressed = e.type == SDL_EVENT_KEY_DOWN;
257 const bool accepts_ship_thrust = mode == GameMode::Playing && !game_over && !ship_respawning;
258 if (e.key.key == SDLK_A) {
259 roll_left_pressed = pressed;
260 } else if (e.key.key == SDLK_S) {
261 roll_right_pressed = pressed;
262 } else if (e.key.key == SDLK_X) {
263 if (accepts_ship_thrust && pressed && !e.key.repeat) {
264 reverse_pressed = true;
265 } else if (!accepts_ship_thrust) {
266 reverse_pressed = false;
267 }
268 } else if (e.key.key == SDLK_Z) {
269 propulsion_pressed = accepts_ship_thrust && pressed;
270 } else if (e.key.key == SDLK_D) {
271 boost_pressed = accepts_ship_thrust && pressed;
272 } else if (e.key.key == SDLK_UP || e.key.key == SDLK_W) {
273 up_pressed = pressed;
274 } else if (e.key.key == SDLK_DOWN) {
275 down_pressed = pressed;
276 } else if (e.key.key == SDLK_SPACE && pressed && !e.key.repeat) {
277 fire_pressed = true;
278 }
279 }
280 }
281
283 if (intro_sprite != nullptr) {
284 intro_sprite->rebuildPipeline();
285 }
286 if (fade_overlay_sprite != nullptr) {
287 fade_overlay_sprite->rebuildPipeline();
288 }
289 if (intro_rain != nullptr) {
290 intro_rain->resize(*this);
291 }
292 ship_model.resize(this);
293 for (auto &asteroid_model : asteroid_models) {
294 asteroid_model.resize(this);
295 }
296 star_field.resize(this);
297 if (terrain_sprite != nullptr) {
298 terrain_sprite->resize(this);
299 }
300 if (projectile_sprite != nullptr) {
301 projectile_sprite->resize(this);
302 }
303 for (auto &sprite_set : ufo_sprite_sets) {
304 for (mxvk::VK_Sprite3D *sprite : sprite_set) {
305 if (sprite != nullptr) {
306 sprite->resize(this);
307 }
308 }
309 }
310 if (effect_sprite != nullptr) {
311 effect_sprite->resize(this);
312 }
313 cleanup_flame_swapchain_resources();
314 create_flame_swapchain_resources();
315 }
316
317 void DefenderWindow::onPrepareFrameRendering([[maybe_unused]] VkCommandBuffer cmd, [[maybe_unused]] uint32_t image_index) {
318 if (mode != GameMode::Intro || intro_rain == nullptr) {
319 return;
320 }
321
322 const VkExtent2D extent = getSwapchainExtent();
323 intro_rain->set_opacity(intro_fade);
324 intro_rain->update_and_render(*this, static_cast<int>(extent.width), static_cast<int>(extent.height));
325 }
326
327 void DefenderWindow::onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) {
328#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
329 ensure_background_music_playing();
330#endif
331 const auto now = std::chrono::steady_clock::now();
332 const float delta_seconds = std::chrono::duration<float>(now - last_frame_time).count();
333 last_frame_time = now;
334 const float dt = std::min(delta_seconds, 0.1f);
335 elapsed_seconds += dt;
336 update_fps_counter(delta_seconds);
337
338 const bool console_visible = console.isVisible();
339 if (!console_visible) {
340 sync_controller_connection();
341 update_controller_input();
342 update_barrel_roll(dt);
343 if (ship_respawning) {
344 update_respawn(dt);
345 } else if (mode == GameMode::Playing && !game_over) {
346 update_ship(dt);
347 update_ufos(dt);
348 update_asteroids(dt);
349 resolve_enemy_overlaps();
350 update_projectiles(dt);
351 check_projectile_ufo_hits();
352 check_projectile_asteroid_hits();
353 check_ship_ufo_collisions();
354 check_ship_asteroid_collisions();
355 update_level_progress();
356 }
357 }
358 update_particles(dt);
359
360 const VkExtent2D extent = getSwapchainExtent();
361 update_playable_world_top(extent);
362 const float aspect = (extent.height > 0U) ? static_cast<float>(extent.width) / static_cast<float>(extent.height) : 1.0f;
363
364 if (mode == GameMode::Intro) {
365 draw_intro(extent);
366 console.draw();
367 return;
368 }
369
370 if (mode == GameMode::IntroFadeIn || mode == GameMode::Countdown) {
371 update_camera_scroll(aspect);
372 update_camera_position(dt);
373 const glm::vec3 camera_target{nearest_world_x(camera_center_x, camera_position.x), CAMERA_HEIGHT, 0.0f};
374
375 const glm::mat4 view = glm::lookAt(camera_position, camera_target, glm::vec3(0.0f, 1.0f, 0.0f));
376 glm::mat4 projection = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 400.0f);
377 projection[1][1] *= -1.0f;
378
379 draw_countdown(cmd, image_index, extent, view, projection);
380 if (mode == GameMode::IntroFadeIn) {
381 draw_intro_fade_in(extent);
382 }
383 console.draw();
384 return;
385 }
386
387 update_camera_scroll(aspect);
388
389 update_camera_position(dt);
390 const glm::vec3 camera_target{nearest_world_x(camera_center_x, camera_position.x), CAMERA_HEIGHT, 0.0f};
391
392 const glm::mat4 view = glm::lookAt(camera_position, camera_target, glm::vec3(0.0f, 1.0f, 0.0f));
393 glm::mat4 projection = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 400.0f);
394 projection[1][1] *= -1.0f;
395
396 VkRect2D gameplay_scissor{};
397 gameplay_scissor.offset = {0, std::min(GAME_VIEWPORT_TOP, static_cast<int>(extent.height))};
398 gameplay_scissor.extent = {extent.width, extent.height - static_cast<uint32_t>(gameplay_scissor.offset.y)};
399 vkCmdSetScissor(cmd, 0, 1, &gameplay_scissor);
400
401 star_field.update(dt, camera_position, elapsed_seconds);
402 star_sprite->updateCamera(image_index, view, projection);
403 star_field.draw();
404 star_sprite->render(cmd, image_index);
405 star_sprite->clearQueue();
406
407 terrain_sprite->updateCamera(image_index, view, projection);
408 draw_terrain();
409 terrain_sprite->render(cmd, image_index);
410 terrain_sprite->clearQueue();
411
412 for (auto &sprite_set : ufo_sprite_sets) {
413 for (mxvk::VK_Sprite3D *sprite : sprite_set) {
414 sprite->updateCamera(image_index, view, projection);
415 }
416 }
417 draw_ufos();
418 for (auto &sprite_set : ufo_sprite_sets) {
419 for (mxvk::VK_Sprite3D *sprite : sprite_set) {
420 sprite->render(cmd, image_index);
421 sprite->clearQueue();
422 }
423 }
424
425 draw_asteroids(cmd, image_index, view, projection);
426
427 projectile_sprite->updateCamera(image_index, view, projection);
428 draw_projectiles();
429 projectile_sprite->render(cmd, image_index);
430 projectile_sprite->clearQueue();
431
433 ubo.model = build_ship_model_matrix();
434 last_ship_model_matrix = ubo.model;
435 ubo.view = view;
436 ubo.proj = projection;
437 if (ship.visible && !ship_respawning) {
438 ship_model.updateUBO(image_index, ubo);
439 ship_model.render(cmd, image_index, false);
440 draw_engine_flame(cmd, extent, view, projection);
441 }
442
443 effect_sprite->updateCamera(image_index, view, projection);
444 draw_particles();
445 effect_sprite->render(cmd, image_index);
446 effect_sprite->clearQueue();
447
448 VkRect2D full_scissor{};
449 full_scissor.extent = extent;
450 vkCmdSetScissor(cmd, 0, 1, &full_scissor);
451
452 if (!console_visible) {
453 draw_hud(extent);
454 }
455 console.draw();
456 }
457
458#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
459 void DefenderWindow::ensure_background_music_playing() {
460 if (!background_music || background_music_track < 0) {
461 return;
462 }
463 if (!background_music->isMusicPlaying(background_music_track)) {
464 if (background_music->playMusic(background_music_track, -1) != 0) {
465 throw mxvk::Exception("Could not start Defender background music");
466 }
467 }
468 }
469
470 void DefenderWindow::play_sound(int sound_id) {
471 if (!background_music || sound_id < 0) {
472 return;
473 }
474 background_music->playWav(sound_id);
475 }
476#endif
477
478} // namespace defender
void onPrepareFrameRendering(VkCommandBuffer cmd, uint32_t image_index) override
Record resource transitions that must happen before dynamic rendering begins.
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override
Optional hook for derived classes to record extra draw commands.
void event(SDL_Event &e) override
Handle one SDL event.
DefenderWindow(const std::string &path, int width, int height, bool fullscreen, bool enable_vsync)
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
bool isMusicPlaying(int id) const
Query whether a loaded music track is currently playing.
int playMusic(int id, int value=0)
Start playing a previously loaded music track.
Depth-tested 3D billboard sprite batch.
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:186
VK_Sprite3D * createSprite3D(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a world-space billboard sprite from a PNG file.
Definition mxvk.cpp:3578
VkDevice device
Definition mxvk.hpp:485
VK_Sprite * createSprite(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a sprite from a PNG file and register it with this window.
Definition mxvk.cpp:3477
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
Definition mxvk.cpp:593
void exit()
Request loop termination.
Definition mxvk.cpp:1126
VK_Sprite * attachPostProcessingShader(const std::string &fragmentShaderPath, float p1=0.0f, float p2=0.0f, float p3=0.0f, float p4=0.0f)
Attach a full-screen post-processing fragment shader.
Definition mxvk.cpp:1154
VK_Window()=default
Construct an empty window object.
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
Definition mxvk.cpp:2991
void setPostProcessingEnabled(bool enabled)
Definition mxvk.hpp:284
void setPostProcessingShaderTimeEnabled(bool enabled)
Keep shader param 1 updated with elapsed render time in seconds.
Definition mxvk.cpp:1248
#define MXVK_VALIDATION
Definition mxvk.hpp:27
float nearest_world_x(float target, float origin)
SpriteAlphaBounds calculate_alpha_bounds(SDL_Surface *surface, std::uint8_t threshold)
SDL_Surface * load_light_background_png(const std::string &path)
constexpr int STAR_COUNT
constexpr int GAME_VIEWPORT_TOP
constexpr int UFO_ANIMATION_FRAMES
constexpr float CAMERA_HEIGHT
RainConfig make_matrix_rain_config(const std::string &asset_root, bool binary_glyph_mode)
Definition rain.cpp:157
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30
SDL_Surface * load_color_keyed_png(const std::string &path, std::uint8_t threshold=12, std::uint8_t softness=48)
Loads a PNG and fades dark color-key pixels to transparency.
std::string color
Definition rain.hpp:19
Default transform UBO payload for model shaders.