MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
rain.cpp
Go to the documentation of this file.
1#include "rain.hpp"
2
3#include <algorithm>
4#include <charconv>
5#include <cmath>
6#include <cstring>
7#include <mutex>
8#include <optional>
9#include <utility>
10
11namespace matrix {
12 namespace {
13 constexpr int default_surface_width = 1280;
14 constexpr int default_surface_height = 720;
15
16 std::mutex ttf_mutex;
17 std::size_t ttf_refcount = 0;
18
19 bool acquire_ttf() {
20 std::lock_guard<std::mutex> lock(ttf_mutex);
21 if (ttf_refcount == 0 && !TTF_Init()) {
22 return false;
23 }
25 return true;
26 }
27
28 void release_ttf() {
29 std::lock_guard<std::mutex> lock(ttf_mutex);
30 if (ttf_refcount == 0) {
31 return;
32 }
34 if (ttf_refcount == 0) {
35 TTF_Quit();
36 }
37 }
38
39 std::string trim_copy(const std::string &value) {
40 const auto begin = value.find_first_not_of(" \t\r\n");
41 if (begin == std::string::npos) {
42 return {};
43 }
44 const auto end = value.find_last_not_of(" \t\r\n");
45 return value.substr(begin, end - begin + 1);
46 }
47
48 std::optional<Uint8> parse_u8_component(const std::string &value, int base) {
49 int parsed = 0;
50 const std::string trimmed = trim_copy(value);
51 const char *begin = trimmed.data();
52 const char *end = trimmed.data() + trimmed.size();
53 const std::from_chars_result result = std::from_chars(begin, end, parsed, base);
54 if (result.ec != std::errc{} || result.ptr != end || parsed < 0 || parsed > 255) {
55 return std::nullopt;
56 }
57 return static_cast<Uint8>(parsed);
58 }
59
60 std::optional<SDL_Color> parse_color_spec(const std::string &spec) {
61 const std::string value = trim_copy(spec);
62 if (value.empty()) {
63 return std::nullopt;
64 }
65
66 if (value.front() == '#') {
67 if (value.size() != 7) {
68 return std::nullopt;
69 }
70 const auto r = parse_u8_component(value.substr(1, 2), 16);
71 const auto g = parse_u8_component(value.substr(3, 2), 16);
72 const auto b = parse_u8_component(value.substr(5, 2), 16);
73 if (!r || !g || !b) {
74 return std::nullopt;
75 }
76 return SDL_Color{*r, *g, *b, 255};
77 }
78
79 const std::size_t first = value.find(',');
80 if (first == std::string::npos) {
81 return std::nullopt;
82 }
83 const std::size_t second = value.find(',', first + 1);
84 if (second == std::string::npos || value.find(',', second + 1) != std::string::npos) {
85 return std::nullopt;
86 }
87
88 const auto r = parse_u8_component(value.substr(0, first), 10);
89 const auto g = parse_u8_component(value.substr(first + 1, second - first - 1), 10);
90 const auto b = parse_u8_component(value.substr(second + 1), 10);
91 if (!r || !g || !b) {
92 return std::nullopt;
93 }
94 return SDL_Color{*r, *g, *b, 255};
95 }
96
97 std::function<SDL_Color(int)> make_tinted_level_color(SDL_Color base_color) {
98 return [base_color](int level) {
99 constexpr int max_level = 7;
100 const float t = std::clamp(static_cast<float>(level) / static_cast<float>(max_level), 0.0f, 1.0f);
101 const Uint8 r = static_cast<Uint8>(std::lerp(0.0f, static_cast<float>(base_color.r), std::pow(t, 1.0f)));
102 const Uint8 g = static_cast<Uint8>(std::lerp(0.0f, static_cast<float>(base_color.g), std::pow(t, 1.0f)));
103 const Uint8 b = static_cast<Uint8>(std::lerp(0.0f, static_cast<float>(base_color.b), std::pow(t, 1.0f)));
104 const Uint8 a = static_cast<Uint8>(std::lerp(95.0f, 235.0f, t));
105 return SDL_Color{r, g, b, a};
106 };
107 }
108
109 std::function<SDL_Color()> make_tinted_head_color(SDL_Color base_color) {
110 return [base_color]() { return SDL_Color{base_color.r, base_color.g, base_color.b, 255}; };
111 }
112 } // namespace
113
114 void Rain::SurfaceDeleter::operator()(SDL_Surface *surface) const {
115 if (surface != nullptr) {
116 SDL_DestroySurface(surface);
117 }
118 }
119
120 void Rain::TtfDeleter::operator()(TTF_Font *font) const {
121 if (font != nullptr) {
122 TTF_CloseFont(font);
123 }
124 }
125
126 SDL_Color Rain::matrix_color(int level) {
127 constexpr int max_level = 7;
128 const float t = std::clamp(static_cast<float>(level) / static_cast<float>(max_level), 0.0f, 1.0f);
129 const Uint8 r = static_cast<Uint8>(std::lerp(0.0f, 48.0f, std::pow(t, 2.9f)));
130 const Uint8 g = static_cast<Uint8>(std::lerp(42.0f, 205.0f, std::pow(t, 0.82f)));
131 const Uint8 b = static_cast<Uint8>(std::lerp(0.0f, 24.0f, std::pow(t, 3.1f)));
132 const Uint8 a = static_cast<Uint8>(std::lerp(95.0f, 235.0f, t));
133 return SDL_Color{r, g, b, a};
134 }
135
136 SDL_Color Rain::head_color() { return SDL_Color{208, 255, 200, 255}; }
137
138 std::vector<Uint32> Rain::full_symbol_set() { return {0xFF66, 0xFF67, 0xFF68, 0xFF69, 0xFF6A, 0xFF6B, 0xFF6C, 0xFF6D, 0xFF6E, 0xFF6F, 0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF76, 0xFF77, 0xFF78, 0xFF79, 0xFF7A, 0xFF7B, 0xFF7C, 0xFF7D, 0xFF7E, 0xFF7F, 0xFF80, 0xFF81, 0xFF82, 0xFF83, 0xFF84, 0xFF85, 0xFF86, 0xFF87, 0xFF88, 0xFF89, 0xFF8A, 0xFF8B, 0xFF8C, 0xFF8D, 0xFF8E, 0xFF8F, 0xFF90, 0xFF91, 0xFF92, 0xFF93, 0xFF94, 0xFF95, 0xFF96, 0xFF97, 0xFF98, 0xFF99, 0xFF9A, 0xFF9B, 0xFF9C, 0xFF9D}; }
139
140 std::vector<Uint32> Rain::binary_symbol_set() { return {U'0', U'1'}; }
141
142 RainConfig make_matrix_rain_config(const std::string &asset_root, bool binary_glyph_mode) {
143 RainConfig config;
144 config.font_path = asset_root + "/data/NotoSansCJK-Bold.ttc";
145 config.symbols = binary_glyph_mode ? Rain::binary_symbol_set() : Rain::full_symbol_set();
148 return config;
149 }
150
151 Rain::Rain(mxvk::VK_Window &window, RainConfig rain_config) : Rain(std::move(rain_config), SurfaceMode::window_driven) { resize(window); }
152
153 Rain::Rain(RainConfig rain_config) : Rain(std::move(rain_config), SurfaceMode::explicit_default) {}
154
155 Rain::Rain(RainConfig rain_config, SurfaceMode mode) : config(std::move(rain_config)), rng(std::random_device{}()) {
156 if (config.font_path.empty()) {
157 throw mxvk::Exception("Matrix rain requires a font_path");
158 }
159 if (config.symbols.empty()) {
160 throw mxvk::Exception("Matrix rain requires at least one symbol");
161 }
162 if (!config.level_color) {
163 config.level_color = &Rain::matrix_color;
164 }
165 if (!config.head_color) {
166 config.head_color = &Rain::head_color;
167 }
168 if (!config.color.empty()) {
169 const std::optional<SDL_Color> parsed_color = parse_color_spec(config.color);
170 if (!parsed_color) {
171 throw mxvk::Exception("Invalid matrix rain color: " + config.color + " (expected #RRGGBB or R,G,B)");
172 }
173 config.level_color = make_tinted_level_color(*parsed_color);
174 config.head_color = make_tinted_head_color(*parsed_color);
175 }
176 if (mode == SurfaceMode::explicit_default && config.surface_width <= 0 && config.surface_height <= 0) {
177 config.surface_width = default_surface_width;
178 config.surface_height = default_surface_height;
179 }
180 if (!acquire_ttf()) {
181 throw mxvk::Exception("Failed to initialize SDL_ttf: " + std::string(SDL_GetError()));
182 }
183 ttf_acquired = true;
184
185 font.reset(TTF_OpenFont(config.font_path.c_str(), config.font_size));
186 if (!font) {
187 release_ttf();
188 ttf_acquired = false;
189 throw mxvk::Exception("Failed to load matrix font: " + config.font_path + ": " + std::string(SDL_GetError()));
190 }
191
192 TTF_SetFontHinting(font.get(), TTF_HINTING_LIGHT);
193 load_glyphs();
194 if (config.surface_width > 0 && config.surface_height > 0) {
195 resize(config.surface_width, config.surface_height);
196 }
197 last_frame = std::chrono::steady_clock::now();
198 }
199
200 Rain::Rain(mxvk::VK_Window &window, const std::string &asset_root_path, const bool binary_glyph_mode) : Rain(window, make_matrix_rain_config(asset_root_path, binary_glyph_mode)) {}
201
202 Rain::Rain(const std::string &asset_root_path, const bool binary_glyph_mode) : Rain(make_matrix_rain_config(asset_root_path, binary_glyph_mode), SurfaceMode::explicit_default) {}
203
205 glyphs.clear();
206 font.reset();
207 background.reset();
208 canvas.reset();
209 if (ttf_acquired) {
210 release_ttf();
211 }
212 }
213
214 void Rain::event(SDL_Event &e) {
215 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE) {
216 randomize_streams();
217 clear_canvas();
218 }
219 }
220
221 void Rain::resize(mxvk::VK_Window &window) { rebuild_for_extent(window); }
222
223 void Rain::resize(int width, int height) {
224 if (width <= 0 || height <= 0 || (canvas != nullptr && canvas->w == width && canvas->h == height)) {
225 return;
226 }
227
228 canvas.reset(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
229 if (canvas == nullptr) {
230 throw mxvk::Exception("Failed to create matrix canvas: " + std::string(SDL_GetError()));
231 }
232 clear_canvas();
233
234 columns = std::max(1, (width + cell_w - 1) / cell_w);
235 rows = std::max(1, (height + cell_h - 1) / cell_h);
236 streams.assign(columns, {});
237 randomize_streams();
238 }
239
240 void Rain::update(float dt) {
241 dt = std::clamp(dt, 0.0f, 1.0f / 15.0f);
242 if (canvas == nullptr || glyphs.empty()) {
243 return;
244 }
245 update_rain(dt);
246 }
247
249 if (rain_sprite == nullptr || canvas == nullptr) {
250 return;
251 }
252 rain_sprite->updateTexture(canvas->pixels, canvas->w, canvas->h, canvas->pitch);
253 }
254
256 if (canvas == nullptr) {
257 return;
258 }
259 render(canvas->w, canvas->h);
260 }
261
262 void Rain::render(int width, int height) {
263 sync_texture();
264 if (rain_sprite == nullptr || canvas == nullptr) {
265 return;
266 }
267 rain_sprite->drawSpriteRect(0, 0, width, height);
268 }
269
270 void Rain::set_opacity(float value) { opacity = std::clamp(value, 0.0f, 1.0f); }
271
272 void Rain::reset() {
273 clear_canvas();
274 randomize_streams();
275 opacity = 1.0f;
276 last_frame = std::chrono::steady_clock::now();
277 }
278
280 const auto now = std::chrono::steady_clock::now();
281 float dt = std::chrono::duration<float>(now - last_frame).count();
282 last_frame = now;
283 resize(window);
284 update(dt);
285 render();
286 }
287
288 void Rain::update_and_render(mxvk::VK_Window &window, int render_width, int render_height) {
289 const auto now = std::chrono::steady_clock::now();
290 float dt = std::chrono::duration<float>(now - last_frame).count();
291 last_frame = now;
292 resize(window);
293 update(dt);
294 render(render_width, render_height);
295 }
296
298
299 mxvk::VK_Sprite *Rain::sprite() const { return rain_sprite; }
300
301 SDL_Surface *Rain::surface() const { return canvas.get(); }
302
303 const void *Rain::pixels() const { return canvas != nullptr ? canvas->pixels : nullptr; }
304
305 int Rain::width() const { return canvas != nullptr ? canvas->w : 0; }
306
307 int Rain::height() const { return canvas != nullptr ? canvas->h : 0; }
308
309 int Rain::pitch() const { return canvas != nullptr ? canvas->pitch : 0; }
310
311 void Rain::load_glyphs() {
312 const std::vector<Uint32> &symbols = config.symbols;
313 int max_glyph_w = 0;
314 int max_glyph_h = 0;
315
316 glyphs.reserve(symbols.size());
317 for (const Uint32 codepoint : symbols) {
318 Glyph glyph;
319 glyph.codepoint = codepoint;
320 glyph.levels.reserve(config.glyph_levels + 1);
321 for (int level = 0; level <= config.glyph_levels; ++level) {
322 SDL_Surface *rendered = TTF_RenderGlyph_Blended(font.get(), codepoint, config.level_color(level));
323 if (rendered == nullptr) {
324 continue;
325 }
326 SurfacePtr converted(SDL_ConvertSurface(rendered, SDL_PIXELFORMAT_RGBA32));
327 SDL_DestroySurface(rendered);
328 if (converted != nullptr) {
329 SDL_SetSurfaceBlendMode(converted.get(), SDL_BLENDMODE_BLEND);
330 max_glyph_w = std::max(max_glyph_w, converted->w);
331 max_glyph_h = std::max(max_glyph_h, converted->h);
332 glyph.levels.emplace_back(std::move(converted));
333 }
334 }
335 SDL_Surface *rendered_head = TTF_RenderGlyph_Blended(font.get(), codepoint, config.head_color());
336 if (rendered_head != nullptr) {
337 SurfacePtr converted(SDL_ConvertSurface(rendered_head, SDL_PIXELFORMAT_RGBA32));
338 SDL_DestroySurface(rendered_head);
339 if (converted != nullptr) {
340 SDL_SetSurfaceBlendMode(converted.get(), SDL_BLENDMODE_BLEND);
341 max_glyph_w = std::max(max_glyph_w, converted->w);
342 max_glyph_h = std::max(max_glyph_h, converted->h);
343 glyph.levels.emplace_back(std::move(converted));
344 }
345 }
346 if (glyph.levels.size() == static_cast<std::size_t>(config.glyph_levels + 2)) {
347 glyphs.emplace_back(std::move(glyph));
348 }
349 }
350
351 if (glyphs.empty()) {
352 throw mxvk::Exception("Matrix demo could not render any glyphs from the bundled font");
353 }
354
355 cell_w = std::max(config.min_cell_width, max_glyph_w + config.cell_padding_width);
356 cell_h = std::max(config.min_cell_height, max_glyph_h + config.cell_padding_height);
357 }
358
359 void Rain::rebuild_for_extent(mxvk::VK_Window &window) {
360 const VkExtent2D extent = window.getSwapchainExtent();
361 const int window_width = static_cast<int>(extent.width);
362 const int window_height = static_cast<int>(extent.height);
363 if (window_width <= 0 || window_height <= 0) {
364 return;
365 }
366
367 const int target_width = (config.surface_width > 0) ? config.surface_width : std::max(1, static_cast<int>(std::lround(static_cast<float>(window_width) * config.surface_scale)));
368 const int target_height = (config.surface_height > 0) ? config.surface_height : std::max(1, static_cast<int>(std::lround(static_cast<float>(window_height) * config.surface_scale)));
369
370 const bool resized = (canvas == nullptr || canvas->w != target_width || canvas->h != target_height);
371 if (resized) {
372 resize(target_width, target_height);
373 }
374
375 if (rain_sprite == nullptr) {
376 rain_sprite = window.createSprite(canvas.get());
377 }
378
379 if (resized || streams.empty()) {
380 columns = std::max(1, (target_width + cell_w - 1) / cell_w);
381 rows = std::max(1, (target_height + cell_h - 1) / cell_h);
382 streams.assign(columns, {});
383 randomize_streams();
384 }
385 }
386
387 void Rain::randomize_streams() {
388 if (streams.empty() || glyphs.empty()) {
389 return;
390 }
391
392 std::uniform_real_distribution<float> head_dist(-static_cast<float>(rows) * config.initial_head_margin, 0.0f);
393 std::uniform_real_distribution<float> speed_dist(config.initial_speed_min, config.initial_speed_max);
394 std::uniform_int_distribution<int> length_dist(config.initial_length_min, config.initial_length_max);
395 std::uniform_int_distribution<int> glyph_dist(0, static_cast<int>(glyphs.size() - 1));
396 std::uniform_int_distribution<int> shimmer_dist(0, 1200);
397
398 for (Stream &stream : streams) {
399 stream.head = head_dist(rng);
400 stream.speed = speed_dist(rng);
401 stream.length = std::min(std::max(config.initial_min_tail_rows, rows), length_dist(rng));
402 stream.glyphOffset = glyph_dist(rng);
403 stream.shimmer = shimmer_dist(rng);
404 }
405 }
406
407 void Rain::update_rain(float dt) {
408 fade_canvas(dt);
409
410 for (int column = 0; column < columns; ++column) {
411 Stream &stream = streams[column];
412 stream.head += stream.speed * dt;
413 if (stream.head - static_cast<float>(stream.length) > static_cast<float>(rows + config.overscan_rows)) {
414 reset_stream(stream);
415 }
416 draw_stream(column, stream);
417 }
418 ++frame_counter;
419 }
420
421 void Rain::reset_stream(Stream &stream) {
422 std::uniform_real_distribution<float> head_dist(-static_cast<float>(rows) * config.reset_head_margin, -1.0f);
423 std::uniform_real_distribution<float> speed_dist(config.reset_speed_min, config.reset_speed_max);
424 std::uniform_int_distribution<int> length_dist(config.reset_length_min, config.reset_length_max);
425 std::uniform_int_distribution<int> glyph_dist(0, static_cast<int>(glyphs.size() - 1));
426 std::uniform_int_distribution<int> shimmer_dist(0, 1200);
427 stream.head = head_dist(rng);
428 stream.speed = speed_dist(rng);
429 stream.length = std::min(std::max(config.reset_min_tail_rows, rows + rows / 4), length_dist(rng));
430 stream.glyphOffset = glyph_dist(rng);
431 stream.shimmer = shimmer_dist(rng);
432 }
433
434 void Rain::fade_canvas(float dt) {
435 const float fade = std::pow(config.fade_base, dt * config.fade_exponent);
436 const float opacity_scale = opacity;
437 if (!SDL_LockSurface(canvas.get())) {
438 return;
439 }
440 auto *pixels = static_cast<Uint8 *>(canvas->pixels);
441 for (int y = 0; y < canvas->h; ++y) {
442 Uint8 *row = pixels + y * canvas->pitch;
443 for (int x = 0; x < canvas->w; ++x) {
444 Uint8 *p = row + x * 4;
445 p[0] = static_cast<Uint8>(static_cast<float>(p[0]) * fade * config.fade_red_scale);
446 p[1] = static_cast<Uint8>(static_cast<float>(p[1]) * fade * config.fade_green_scale);
447 p[2] = static_cast<Uint8>(static_cast<float>(p[2]) * fade * config.fade_blue_scale);
448 p[3] = static_cast<Uint8>(static_cast<float>(p[3]) * fade * opacity_scale);
449 }
450 }
451 SDL_UnlockSurface(canvas.get());
452 }
453
454 void Rain::draw_stream(int column, const Stream &stream) {
455 const int head_row = static_cast<int>(std::floor(stream.head));
456 const int x = column * cell_w;
457 for (int tail = stream.length; tail >= 0; --tail) {
458 const int row = head_row - tail;
459 if (row < -1 || row >= rows) {
460 continue;
461 }
462 const float age = static_cast<float>(tail) / static_cast<float>(std::max(1, stream.length));
463 int level = config.glyph_levels - static_cast<int>(std::round(age * static_cast<float>(config.glyph_levels + 1)));
464 if (tail == 0) {
465 level = config.glyph_levels + 1;
466 } else if (tail <= config.near_head_tail_threshold) {
467 level = config.glyph_levels;
468 }
469 level = std::clamp(level, 0, config.glyph_levels + 1);
470 const int glyph_index = (stream.glyphOffset + row * 17 + column * 11 + stream.shimmer + frame_counter / 3 + tail * 5) % static_cast<int>(glyphs.size());
471 const Glyph &glyph = glyphs[glyph_index < 0 ? glyph_index + static_cast<int>(glyphs.size()) : glyph_index];
472 if (glyph.levels.empty()) {
473 continue;
474 }
475 const SDL_Rect clear_rect{x, row * cell_h, cell_w, cell_h};
476 SDL_FillSurfaceRect(canvas.get(), &clear_rect, SDL_MapSurfaceRGBA(canvas.get(), 0, 0, 0, 0));
477 SDL_Surface *surface = glyph.levels[level].get();
478 Uint8 previous_alpha = 255;
479 SDL_GetSurfaceAlphaMod(surface, &previous_alpha);
480 const Uint8 opacity_alpha = static_cast<Uint8>(std::lround(std::clamp(opacity, 0.0f, 1.0f) * 255.0f));
481
482 const int glyph_x = x + (cell_w - glyph.levels[level]->w) / 2;
483 const int glyph_y = row * cell_h;
484 const int glow_alpha = static_cast<int>(std::lround(((tail == 0) ? config.head_glow_alpha : (tail <= config.near_head_tail_threshold ? config.near_head_glow_alpha : config.trail_glow_alpha)) * opacity));
485
486 SDL_SetSurfaceAlphaMod(surface, static_cast<Uint8>((glow_alpha * opacity_alpha) / 255));
487 SDL_Rect glow_dst{glyph_x - 1, glyph_y, glyph.levels[level]->w, glyph.levels[level]->h};
488 SDL_BlitSurface(surface, nullptr, canvas.get(), &glow_dst);
489 glow_dst.x = glyph_x + 1;
490 glow_dst.y = glyph_y;
491 SDL_BlitSurface(surface, nullptr, canvas.get(), &glow_dst);
492 glow_dst.x = glyph_x;
493 glow_dst.y = glyph_y + 1;
494 SDL_BlitSurface(surface, nullptr, canvas.get(), &glow_dst);
495
496 SDL_SetSurfaceAlphaMod(surface, static_cast<Uint8>((static_cast<int>(previous_alpha) * opacity_alpha) / 255));
497 SDL_Rect dst{glyph_x, glyph_y, glyph.levels[level]->w, glyph.levels[level]->h};
498 SDL_BlitSurface(surface, nullptr, canvas.get(), &dst);
499 SDL_SetSurfaceAlphaMod(surface, previous_alpha);
500 }
501 }
502
503 void Rain::clear_canvas() {
504 if (canvas != nullptr) {
505 SDL_FillSurfaceRect(canvas.get(), nullptr, SDL_MapSurfaceRGBA(canvas.get(), 0, 0, 0, 0));
506 }
507 }
508} // namespace matrix
static SDL_Color matrix_color(int level)
Definition rain.cpp:126
int height() const
Definition rain.cpp:307
static std::vector< Uint32 > full_symbol_set()
Definition rain.cpp:138
void render()
Definition rain.cpp:255
void resize(mxvk::VK_Window &window)
Definition rain.cpp:221
static SDL_Color head_color()
Definition rain.cpp:136
Rain(mxvk::VK_Window &window, RainConfig config)
Definition rain.cpp:151
void on_swapchain_recreated(mxvk::VK_Window &window)
Definition rain.cpp:297
void update(float dt)
Definition rain.cpp:240
const void * pixels() const
Definition rain.cpp:303
void event(SDL_Event &e)
Definition rain.cpp:214
void reset()
Definition rain.cpp:272
static std::vector< Uint32 > binary_symbol_set()
Definition rain.cpp:140
void sync_texture()
Definition rain.cpp:248
int width() const
Definition rain.cpp:305
void set_opacity(float opacity)
Definition rain.cpp:270
SDL_Surface * surface() const
Definition rain.cpp:301
void update_and_render(mxvk::VK_Window &window)
Definition rain.cpp:279
mxvk::VK_Sprite * sprite() const
Definition rain.cpp:299
int pitch() const
Definition rain.cpp:309
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:39
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:222
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
int lround(float value)
Definition knight.py:41
std::optional< Uint8 > parse_u8_component(const std::string &value, int base)
Definition rain.cpp:48
std::string trim_copy(const std::string &value)
Definition rain.cpp:39
std::function< SDL_Color(int)> make_tinted_level_color(SDL_Color base_color)
Definition rain.cpp:97
std::optional< SDL_Color > parse_color_spec(const std::string &spec)
Definition rain.cpp:60
std::function< SDL_Color()> make_tinted_head_color(SDL_Color base_color)
Definition rain.cpp:109
RainConfig make_matrix_rain_config(const std::string &asset_root, bool binary_glyph_mode)
Definition rain.cpp:142
std::function< SDL_Color()> head_color
Definition rain.hpp:53
std::string color
Definition rain.hpp:19
std::string font_path
Definition rain.hpp:18
std::vector< Uint32 > symbols
Definition rain.hpp:21
std::function< SDL_Color(int)> level_color
Definition rain.hpp:52