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