MXVK Vulkan Framework 0.24.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]() {
111 return SDL_Color{base_color.r, base_color.g, base_color.b, 255};
112 };
113 }
114 } // namespace
115
116 void Rain::SurfaceDeleter::operator()(SDL_Surface *surface) const {
117 if (surface != nullptr) {
118 SDL_DestroySurface(surface);
119 }
120 }
121
122 void Rain::TtfDeleter::operator()(TTF_Font *font) const {
123 if (font != nullptr) {
124 TTF_CloseFont(font);
125 }
126 }
127
128 SDL_Color Rain::matrix_color(int level) {
129 constexpr int max_level = 7;
130 const float t = std::clamp(static_cast<float>(level) / static_cast<float>(max_level), 0.0f, 1.0f);
131 const Uint8 r = static_cast<Uint8>(std::lerp(0.0f, 48.0f, std::pow(t, 2.9f)));
132 const Uint8 g = static_cast<Uint8>(std::lerp(42.0f, 205.0f, std::pow(t, 0.82f)));
133 const Uint8 b = static_cast<Uint8>(std::lerp(0.0f, 24.0f, std::pow(t, 3.1f)));
134 const Uint8 a = static_cast<Uint8>(std::lerp(95.0f, 235.0f, t));
135 return SDL_Color{r, g, b, a};
136 }
137
138 SDL_Color Rain::head_color() {
139 return SDL_Color{208, 255, 200, 255};
140 }
141
142 std::vector<Uint32> Rain::full_symbol_set() {
143 return {
144 0xFF66, 0xFF67, 0xFF68, 0xFF69, 0xFF6A, 0xFF6B, 0xFF6C, 0xFF6D,
145 0xFF6E, 0xFF6F, 0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF76,
146 0xFF77, 0xFF78, 0xFF79, 0xFF7A, 0xFF7B, 0xFF7C, 0xFF7D, 0xFF7E,
147 0xFF7F, 0xFF80, 0xFF81, 0xFF82, 0xFF83, 0xFF84, 0xFF85, 0xFF86,
148 0xFF87, 0xFF88, 0xFF89, 0xFF8A, 0xFF8B, 0xFF8C, 0xFF8D, 0xFF8E,
149 0xFF8F, 0xFF90, 0xFF91, 0xFF92, 0xFF93, 0xFF94, 0xFF95, 0xFF96,
150 0xFF97, 0xFF98, 0xFF99, 0xFF9A, 0xFF9B, 0xFF9C, 0xFF9D};
151 }
152
153 std::vector<Uint32> Rain::binary_symbol_set() {
154 return {U'0', U'1'};
155 }
156
157 RainConfig make_matrix_rain_config(const std::string &asset_root, bool binary_glyph_mode) {
158 RainConfig config;
159 config.font_path = asset_root + "/data/NotoSansCJK-Bold.ttc";
160 config.symbols = binary_glyph_mode ? Rain::binary_symbol_set() : Rain::full_symbol_set();
163 return config;
164 }
165
167 : Rain(std::move(rain_config), SurfaceMode::window_driven) {
168 resize(window);
169 }
170
172 : Rain(std::move(rain_config), SurfaceMode::explicit_default) {}
173
174 Rain::Rain(RainConfig rain_config, SurfaceMode mode)
175 : config(std::move(rain_config)), rng(std::random_device{}()) {
176 if (config.font_path.empty()) {
177 throw mxvk::Exception("Matrix rain requires a font_path");
178 }
179 if (config.symbols.empty()) {
180 throw mxvk::Exception("Matrix rain requires at least one symbol");
181 }
182 if (!config.level_color) {
183 config.level_color = &Rain::matrix_color;
184 }
185 if (!config.head_color) {
186 config.head_color = &Rain::head_color;
187 }
188 if (!config.color.empty()) {
189 const std::optional<SDL_Color> parsed_color = parse_color_spec(config.color);
190 if (!parsed_color) {
191 throw mxvk::Exception("Invalid matrix rain color: " + config.color + " (expected #RRGGBB or R,G,B)");
192 }
193 config.level_color = make_tinted_level_color(*parsed_color);
194 config.head_color = make_tinted_head_color(*parsed_color);
195 }
196 if (mode == SurfaceMode::explicit_default && config.surface_width <= 0 && config.surface_height <= 0) {
197 config.surface_width = default_surface_width;
198 config.surface_height = default_surface_height;
199 }
200 if (!acquire_ttf()) {
201 throw mxvk::Exception("Failed to initialize SDL_ttf: " + std::string(SDL_GetError()));
202 }
203 ttf_acquired = true;
204
205 font.reset(TTF_OpenFont(config.font_path.c_str(), config.font_size));
206 if (!font) {
207 release_ttf();
208 ttf_acquired = false;
209 throw mxvk::Exception("Failed to load matrix font: " + config.font_path + ": " + std::string(SDL_GetError()));
210 }
211
212 TTF_SetFontHinting(font.get(), TTF_HINTING_LIGHT);
213 load_glyphs();
214 if (config.surface_width > 0 && config.surface_height > 0) {
215 resize(config.surface_width, config.surface_height);
216 }
217 last_frame = std::chrono::steady_clock::now();
218 }
219
220 Rain::Rain(mxvk::VK_Window &window, const std::string &asset_root_path, const bool binary_glyph_mode)
221 : Rain(window, make_matrix_rain_config(asset_root_path, binary_glyph_mode)) {}
222
223 Rain::Rain(const std::string &asset_root_path, const bool binary_glyph_mode)
224 : Rain(make_matrix_rain_config(asset_root_path, binary_glyph_mode), SurfaceMode::explicit_default) {}
225
227 glyphs.clear();
228 font.reset();
229 background.reset();
230 canvas.reset();
231 if (ttf_acquired) {
232 release_ttf();
233 }
234 }
235
236 void Rain::event(SDL_Event &e) {
237 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE) {
238 randomize_streams();
239 clear_canvas();
240 }
241 }
242
244 rebuild_for_extent(window);
245 }
246
247 void Rain::resize(int width, int height) {
248 if (width <= 0 || height <= 0 || (canvas != nullptr && canvas->w == width && canvas->h == height)) {
249 return;
250 }
251
252 canvas.reset(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
253 if (canvas == nullptr) {
254 throw mxvk::Exception("Failed to create matrix canvas: " + std::string(SDL_GetError()));
255 }
256 clear_canvas();
257
258 columns = std::max(1, (width + cell_w - 1) / cell_w);
259 rows = std::max(1, (height + cell_h - 1) / cell_h);
260 streams.assign(columns, {});
261 randomize_streams();
262 }
263
264 void Rain::update(float dt) {
265 dt = std::clamp(dt, 0.0f, 1.0f / 15.0f);
266 if (canvas == nullptr || glyphs.empty()) {
267 return;
268 }
269 update_rain(dt);
270 }
271
273 if (rain_sprite == nullptr || canvas == nullptr) {
274 return;
275 }
276 rain_sprite->updateTexture(canvas->pixels, canvas->w, canvas->h, canvas->pitch);
277 }
278
280 if (canvas == nullptr) {
281 return;
282 }
283 render(canvas->w, canvas->h);
284 }
285
286 void Rain::render(int width, int height) {
287 sync_texture();
288 if (rain_sprite == nullptr || canvas == nullptr) {
289 return;
290 }
291 rain_sprite->drawSpriteRect(0, 0, width, height);
292 }
293
294 void Rain::set_opacity(float value) {
295 opacity = std::clamp(value, 0.0f, 1.0f);
296 }
297
298 void Rain::reset() {
299 clear_canvas();
300 randomize_streams();
301 opacity = 1.0f;
302 last_frame = std::chrono::steady_clock::now();
303 }
304
306 const auto now = std::chrono::steady_clock::now();
307 float dt = std::chrono::duration<float>(now - last_frame).count();
308 last_frame = now;
309 resize(window);
310 update(dt);
311 render();
312 }
313
314 void Rain::update_and_render(mxvk::VK_Window &window, int render_width, int render_height) {
315 const auto now = std::chrono::steady_clock::now();
316 float dt = std::chrono::duration<float>(now - last_frame).count();
317 last_frame = now;
318 resize(window);
319 update(dt);
320 render(render_width, render_height);
321 }
322
324 resize(window);
325 }
326
328 return rain_sprite;
329 }
330
331 SDL_Surface *Rain::surface() const {
332 return canvas.get();
333 }
334
335 const void *Rain::pixels() const {
336 return canvas != nullptr ? canvas->pixels : nullptr;
337 }
338
339 int Rain::width() const {
340 return canvas != nullptr ? canvas->w : 0;
341 }
342
343 int Rain::height() const {
344 return canvas != nullptr ? canvas->h : 0;
345 }
346
347 int Rain::pitch() const {
348 return canvas != nullptr ? canvas->pitch : 0;
349 }
350
351 void Rain::load_glyphs() {
352 const std::vector<Uint32> &symbols = config.symbols;
353 int max_glyph_w = 0;
354 int max_glyph_h = 0;
355
356 glyphs.reserve(symbols.size());
357 for (const Uint32 codepoint : symbols) {
358 Glyph glyph;
359 glyph.codepoint = codepoint;
360 glyph.levels.reserve(config.glyph_levels + 1);
361 for (int level = 0; level <= config.glyph_levels; ++level) {
362 SDL_Surface *rendered = TTF_RenderGlyph_Blended(font.get(), codepoint, config.level_color(level));
363 if (rendered == nullptr) {
364 continue;
365 }
366 SurfacePtr converted(SDL_ConvertSurface(rendered, SDL_PIXELFORMAT_RGBA32));
367 SDL_DestroySurface(rendered);
368 if (converted != nullptr) {
369 SDL_SetSurfaceBlendMode(converted.get(), SDL_BLENDMODE_BLEND);
370 max_glyph_w = std::max(max_glyph_w, converted->w);
371 max_glyph_h = std::max(max_glyph_h, converted->h);
372 glyph.levels.emplace_back(std::move(converted));
373 }
374 }
375 SDL_Surface *rendered_head = TTF_RenderGlyph_Blended(font.get(), codepoint, config.head_color());
376 if (rendered_head != nullptr) {
377 SurfacePtr converted(SDL_ConvertSurface(rendered_head, SDL_PIXELFORMAT_RGBA32));
378 SDL_DestroySurface(rendered_head);
379 if (converted != nullptr) {
380 SDL_SetSurfaceBlendMode(converted.get(), SDL_BLENDMODE_BLEND);
381 max_glyph_w = std::max(max_glyph_w, converted->w);
382 max_glyph_h = std::max(max_glyph_h, converted->h);
383 glyph.levels.emplace_back(std::move(converted));
384 }
385 }
386 if (glyph.levels.size() == static_cast<std::size_t>(config.glyph_levels + 2)) {
387 glyphs.emplace_back(std::move(glyph));
388 }
389 }
390
391 if (glyphs.empty()) {
392 throw mxvk::Exception("Matrix demo could not render any glyphs from the bundled font");
393 }
394
395 cell_w = std::max(config.min_cell_width, max_glyph_w + config.cell_padding_width);
396 cell_h = std::max(config.min_cell_height, max_glyph_h + config.cell_padding_height);
397 }
398
399 void Rain::rebuild_for_extent(mxvk::VK_Window &window) {
400 const VkExtent2D extent = window.getSwapchainExtent();
401 const int window_width = static_cast<int>(extent.width);
402 const int window_height = static_cast<int>(extent.height);
403 if (window_width <= 0 || window_height <= 0) {
404 return;
405 }
406
407 const int target_width = (config.surface_width > 0)
408 ? config.surface_width
409 : std::max(1, static_cast<int>(std::lround(static_cast<float>(window_width) * config.surface_scale)));
410 const int target_height = (config.surface_height > 0)
411 ? config.surface_height
412 : std::max(1, static_cast<int>(std::lround(static_cast<float>(window_height) * config.surface_scale)));
413
414 const bool resized = (canvas == nullptr || canvas->w != target_width || canvas->h != target_height);
415 if (resized) {
416 resize(target_width, target_height);
417 }
418
419 if (rain_sprite == nullptr) {
420 rain_sprite = window.createSprite(canvas.get());
421 }
422
423 if (resized || streams.empty()) {
424 columns = std::max(1, (target_width + cell_w - 1) / cell_w);
425 rows = std::max(1, (target_height + cell_h - 1) / cell_h);
426 streams.assign(columns, {});
427 randomize_streams();
428 }
429 }
430
431 void Rain::randomize_streams() {
432 if (streams.empty() || glyphs.empty()) {
433 return;
434 }
435
436 std::uniform_real_distribution<float> head_dist(-static_cast<float>(rows) * config.initial_head_margin, 0.0f);
437 std::uniform_real_distribution<float> speed_dist(config.initial_speed_min, config.initial_speed_max);
438 std::uniform_int_distribution<int> length_dist(config.initial_length_min, config.initial_length_max);
439 std::uniform_int_distribution<int> glyph_dist(0, static_cast<int>(glyphs.size() - 1));
440 std::uniform_int_distribution<int> shimmer_dist(0, 1200);
441
442 for (Stream &stream : streams) {
443 stream.head = head_dist(rng);
444 stream.speed = speed_dist(rng);
445 stream.length = std::min(std::max(config.initial_min_tail_rows, rows), length_dist(rng));
446 stream.glyphOffset = glyph_dist(rng);
447 stream.shimmer = shimmer_dist(rng);
448 }
449 }
450
451 void Rain::update_rain(float dt) {
452 fade_canvas(dt);
453
454 for (int column = 0; column < columns; ++column) {
455 Stream &stream = streams[column];
456 stream.head += stream.speed * dt;
457 if (stream.head - static_cast<float>(stream.length) > static_cast<float>(rows + config.overscan_rows)) {
458 reset_stream(stream);
459 }
460 draw_stream(column, stream);
461 }
462 ++frame_counter;
463 }
464
465 void Rain::reset_stream(Stream &stream) {
466 std::uniform_real_distribution<float> head_dist(-static_cast<float>(rows) * config.reset_head_margin, -1.0f);
467 std::uniform_real_distribution<float> speed_dist(config.reset_speed_min, config.reset_speed_max);
468 std::uniform_int_distribution<int> length_dist(config.reset_length_min, config.reset_length_max);
469 std::uniform_int_distribution<int> glyph_dist(0, static_cast<int>(glyphs.size() - 1));
470 std::uniform_int_distribution<int> shimmer_dist(0, 1200);
471 stream.head = head_dist(rng);
472 stream.speed = speed_dist(rng);
473 stream.length = std::min(std::max(config.reset_min_tail_rows, rows + rows / 4), length_dist(rng));
474 stream.glyphOffset = glyph_dist(rng);
475 stream.shimmer = shimmer_dist(rng);
476 }
477
478 void Rain::fade_canvas(float dt) {
479 const float fade = std::pow(config.fade_base, dt * config.fade_exponent);
480 const float opacity_scale = opacity;
481 if (!SDL_LockSurface(canvas.get())) {
482 return;
483 }
484 auto *pixels = static_cast<Uint8 *>(canvas->pixels);
485 for (int y = 0; y < canvas->h; ++y) {
486 Uint8 *row = pixels + y * canvas->pitch;
487 for (int x = 0; x < canvas->w; ++x) {
488 Uint8 *p = row + x * 4;
489 p[0] = static_cast<Uint8>(static_cast<float>(p[0]) * fade * config.fade_red_scale);
490 p[1] = static_cast<Uint8>(static_cast<float>(p[1]) * fade * config.fade_green_scale);
491 p[2] = static_cast<Uint8>(static_cast<float>(p[2]) * fade * config.fade_blue_scale);
492 p[3] = static_cast<Uint8>(static_cast<float>(p[3]) * fade * opacity_scale);
493 }
494 }
495 SDL_UnlockSurface(canvas.get());
496 }
497
498 void Rain::draw_stream(int column, const Stream &stream) {
499 const int head_row = static_cast<int>(std::floor(stream.head));
500 const int x = column * cell_w;
501 for (int tail = stream.length; tail >= 0; --tail) {
502 const int row = head_row - tail;
503 if (row < -1 || row >= rows) {
504 continue;
505 }
506 const float age = static_cast<float>(tail) / static_cast<float>(std::max(1, stream.length));
507 int level = config.glyph_levels - static_cast<int>(std::round(age * static_cast<float>(config.glyph_levels + 1)));
508 if (tail == 0) {
509 level = config.glyph_levels + 1;
510 } else if (tail <= config.near_head_tail_threshold) {
511 level = config.glyph_levels;
512 }
513 level = std::clamp(level, 0, config.glyph_levels + 1);
514 const int glyph_index = (stream.glyphOffset + row * 17 + column * 11 + stream.shimmer + frame_counter / 3 + tail * 5) %
515 static_cast<int>(glyphs.size());
516 const Glyph &glyph = glyphs[glyph_index < 0 ? glyph_index + static_cast<int>(glyphs.size()) : glyph_index];
517 if (glyph.levels.empty()) {
518 continue;
519 }
520 const SDL_Rect clear_rect{x, row * cell_h, cell_w, cell_h};
521 SDL_FillSurfaceRect(canvas.get(), &clear_rect, SDL_MapSurfaceRGBA(canvas.get(), 0, 0, 0, 0));
522 SDL_Surface *surface = glyph.levels[level].get();
523 Uint8 previous_alpha = 255;
524 SDL_GetSurfaceAlphaMod(surface, &previous_alpha);
525 const Uint8 opacity_alpha = static_cast<Uint8>(std::lround(std::clamp(opacity, 0.0f, 1.0f) * 255.0f));
526
527 const int glyph_x = x + (cell_w - glyph.levels[level]->w) / 2;
528 const int glyph_y = row * cell_h;
529 const int glow_alpha = static_cast<int>(
530 std::lround(((tail == 0)
531 ? config.head_glow_alpha
532 : (tail <= config.near_head_tail_threshold ? config.near_head_glow_alpha
533 : config.trail_glow_alpha)) *
534 opacity));
535
536 SDL_SetSurfaceAlphaMod(surface, static_cast<Uint8>((glow_alpha * opacity_alpha) / 255));
537 SDL_Rect glow_dst{glyph_x - 1, glyph_y, glyph.levels[level]->w, glyph.levels[level]->h};
538 SDL_BlitSurface(surface, nullptr, canvas.get(), &glow_dst);
539 glow_dst.x = glyph_x + 1;
540 glow_dst.y = glyph_y;
541 SDL_BlitSurface(surface, nullptr, canvas.get(), &glow_dst);
542 glow_dst.x = glyph_x;
543 glow_dst.y = glyph_y + 1;
544 SDL_BlitSurface(surface, nullptr, canvas.get(), &glow_dst);
545
546 SDL_SetSurfaceAlphaMod(surface, static_cast<Uint8>((static_cast<int>(previous_alpha) * opacity_alpha) / 255));
547 SDL_Rect dst{glyph_x, glyph_y, glyph.levels[level]->w, glyph.levels[level]->h};
548 SDL_BlitSurface(surface, nullptr, canvas.get(), &dst);
549 SDL_SetSurfaceAlphaMod(surface, previous_alpha);
550 }
551 }
552
553 void Rain::clear_canvas() {
554 if (canvas != nullptr) {
555 SDL_FillSurfaceRect(canvas.get(), nullptr, SDL_MapSurfaceRGBA(canvas.get(), 0, 0, 0, 0));
556 }
557 }
558} // namespace matrix
static SDL_Color matrix_color(int level)
Definition rain.cpp:128
int height() const
Definition rain.cpp:343
static std::vector< Uint32 > full_symbol_set()
Definition rain.cpp:142
void render()
Definition rain.cpp:279
void resize(mxvk::VK_Window &window)
Definition rain.cpp:243
static SDL_Color head_color()
Definition rain.cpp:138
Rain(mxvk::VK_Window &window, RainConfig config)
Definition rain.cpp:166
void on_swapchain_recreated(mxvk::VK_Window &window)
Definition rain.cpp:323
void update(float dt)
Definition rain.cpp:264
const void * pixels() const
Definition rain.cpp:335
void event(SDL_Event &e)
Definition rain.cpp:236
void reset()
Definition rain.cpp:298
static std::vector< Uint32 > binary_symbol_set()
Definition rain.cpp:153
void sync_texture()
Definition rain.cpp:272
int width() const
Definition rain.cpp:339
void set_opacity(float opacity)
Definition rain.cpp:294
SDL_Surface * surface() const
Definition rain.cpp:331
void update_and_render(mxvk::VK_Window &window)
Definition rain.cpp:305
mxvk::VK_Sprite * sprite() const
Definition rain.cpp:327
int pitch() const
Definition rain.cpp:347
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:37
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:186
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
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:157
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