MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
binary_matrix.cpp
Go to the documentation of this file.
1#include "mxvk/argz.hpp"
2#include "mxvk/mxvk.hpp"
4
5#include <SDL3/SDL.h>
6#include <SDL3_ttf/SDL_ttf.h>
7
8#include <algorithm>
9#include <charconv>
10#include <chrono>
11#include <cmath>
12#include <cstdlib>
13#include <format>
14#include <iostream>
15#include <memory>
16#include <optional>
17#include <random>
18#include <string>
19#include <vector>
20
21#include <glm/ext/matrix_clip_space.hpp>
22#include <glm/ext/matrix_transform.hpp>
23#include <glm/glm.hpp>
24
25namespace {
26 using Clock = std::chrono::steady_clock;
27
29 void operator()(SDL_Surface *surface) const {
30 if (surface != nullptr) {
31 SDL_DestroySurface(surface);
32 }
33 }
34 };
35
36 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
37
38 struct TtfDeleter {
39 void operator()(TTF_Font *font) const {
40 if (font != nullptr) {
41 TTF_CloseFont(font);
42 }
43 }
44 };
45
46 using FontPtr = std::unique_ptr<TTF_Font, TtfDeleter>;
47
48 struct Stream {
49 float head = 0.0f;
50 float speed = 0.0f;
51 int length = 0;
52 int bitSeed = 0;
53 float phase = 0.0f;
54 float depthPhase = 0.0f;
55 float depthAmplitude = 0.0f;
56 float depthBias = 0.0f;
57 };
58
59 SDL_Color matrixTrailColor(int level) {
60 constexpr int maxLevel = 7;
61 const float t = std::clamp(static_cast<float>(level) / static_cast<float>(maxLevel), 0.0f, 1.0f);
62 const Uint8 r = static_cast<Uint8>(std::lerp(0.0f, 180.0f, std::pow(t, 2.8f)));
63 const Uint8 g = static_cast<Uint8>(std::lerp(36.0f, 255.0f, std::pow(t, 0.72f)));
64 const Uint8 b = static_cast<Uint8>(std::lerp(8.0f, 205.0f, std::pow(t, 3.2f)));
65 const Uint8 a = static_cast<Uint8>(std::lerp(120.0f, 255.0f, t));
66 return SDL_Color{r, g, b, a};
67 }
68
69 std::string trim_copy(const std::string &value) {
70 const auto begin = value.find_first_not_of(" \t\r\n");
71 if (begin == std::string::npos) {
72 return {};
73 }
74 const auto end = value.find_last_not_of(" \t\r\n");
75 return value.substr(begin, end - begin + 1);
76 }
77
78 std::optional<Uint8> parse_u8_component(const std::string &value, int base) {
79 int parsed = 0;
80 const std::string trimmed = trim_copy(value);
81 const char *begin = trimmed.data();
82 const char *end = trimmed.data() + trimmed.size();
83 const std::from_chars_result result = std::from_chars(begin, end, parsed, base);
84 if (result.ec != std::errc{} || result.ptr != end || parsed < 0 || parsed > 255) {
85 return std::nullopt;
86 }
87 return static_cast<Uint8>(parsed);
88 }
89
90 std::optional<SDL_Color> parse_color_spec(const std::string &spec) {
91 const std::string value = trim_copy(spec);
92 if (value.empty()) {
93 return std::nullopt;
94 }
95
96 if (value.front() == '#') {
97 if (value.size() != 7) {
98 return std::nullopt;
99 }
100 const auto r = parse_u8_component(value.substr(1, 2), 16);
101 const auto g = parse_u8_component(value.substr(3, 2), 16);
102 const auto b = parse_u8_component(value.substr(5, 2), 16);
103 if (!r || !g || !b) {
104 return std::nullopt;
105 }
106 return SDL_Color{*r, *g, *b, 255};
107 }
108
109 const std::size_t first = value.find(',');
110 if (first == std::string::npos) {
111 return std::nullopt;
112 }
113 const std::size_t second = value.find(',', first + 1);
114 if (second == std::string::npos || value.find(',', second + 1) != std::string::npos) {
115 return std::nullopt;
116 }
117
118 const auto r = parse_u8_component(value.substr(0, first), 10);
119 const auto g = parse_u8_component(value.substr(first + 1, second - first - 1), 10);
120 const auto b = parse_u8_component(value.substr(second + 1), 10);
121 if (!r || !g || !b) {
122 return std::nullopt;
123 }
124 return SDL_Color{*r, *g, *b, 255};
125 }
126
127 SurfacePtr renderGlyph(TTF_Font *font, const std::string &glyph, const SDL_Color &color) {
128 SDL_Surface *rendered = TTF_RenderText_Blended(font, glyph.c_str(), 0, color);
129 if (rendered == nullptr) {
130 return {};
131 }
132
133 SurfacePtr converted(SDL_ConvertSurface(rendered, SDL_PIXELFORMAT_RGBA32));
134 SDL_DestroySurface(rendered);
135 if (converted != nullptr) {
136 SDL_SetSurfaceBlendMode(converted.get(), SDL_BLENDMODE_BLEND);
137 }
138 return converted;
139 }
140} // namespace
141
142namespace example {
143 class BinaryMatrixWindow final : public mxvk::VK_Window {
144 public:
145 BinaryMatrixWindow(const std::string &path,
146 const std::string &title,
147 const int width,
148 const int height,
149 const bool fullscreen,
150 const bool enable_vsync,
151 const int requested_glyph_size,
152 const std::string &color)
153 : mxvk::VK_Window(title, width, height, fullscreen, MXVK_VALIDATION, enable_vsync),
154 assetRoot(path.empty() ? std::string(binary_matrix_ASSET_DIR) : path),
155 glyph_size(requested_glyph_size),
156 rng(std::random_device{}()) {
157 if (assetRoot == ".") {
158 assetRoot = binary_matrix_ASSET_DIR;
159 }
160
161 if (!color.empty()) {
162 const std::optional<SDL_Color> parsedColor = parse_color_spec(color);
163 if (!parsedColor) {
164 throw mxvk::Exception("Invalid binary_matrix color: " + color + " (expected #RRGGBB or R,G,B)");
165 }
166 digitColor = *parsedColor;
167 }
168
169 setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
170
171 if (!TTF_Init()) {
172 throw mxvk::Exception("Failed to initialize SDL_ttf: " + std::string(SDL_GetError()));
173 }
174
175 font.reset(TTF_OpenFont((assetRoot + "/data/NotoSansCJK-Bold.ttc").c_str(), glyph_size));
176 if (!font) {
177 throw mxvk::Exception("Failed to load binary matrix font: " + std::string(SDL_GetError()));
178 }
179 TTF_SetFontHinting(font.get(), TTF_HINTING_LIGHT);
180
181 const std::string spriteVertPath = assetRoot + "/data/sprite.vert.spv";
182 const std::string backgroundFragPath = assetRoot + "/data/background.frag.spv";
183 backgroundSprite = createSprite(assetRoot + "/data/bg.png", spriteVertPath, backgroundFragPath);
184 if (backgroundSprite == nullptr) {
185 throw mxvk::Exception("Failed to create binary matrix background sprite");
186 }
187
188 loadDigits();
189 rebuildForExtent();
190 lastFrame = Clock::now();
191 }
192
194 digitZeroSprite = nullptr;
195 digitOneSprite = nullptr;
196 backgroundSprite = nullptr;
197 font.reset();
198 TTF_Quit();
199 }
200
201 void event(SDL_Event &e) override {
202 if (e.type == SDL_EVENT_KEY_DOWN) {
203 if (e.key.key == SDLK_ESCAPE) {
204 exit();
205 } else if (e.key.key == SDLK_SPACE) {
206 randomizeStreams();
207 }
208 } else if (e.type == SDL_EVENT_MOUSE_MOTION) {
209 mouseX = e.motion.x;
210 mouseY = e.motion.y;
211 } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
212 mousePressed = true;
213 mouseX = e.button.x;
214 mouseY = e.button.y;
215 } else if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) {
216 mousePressed = false;
217 mouseX = e.button.x;
218 mouseY = e.button.y;
219 }
220 }
221
222 void proc() override {
223 rebuildForExtent();
224 updateCameraInput();
225 }
226
227 void onSwapchainRecreated() override {
228 if (digitZeroSprite != nullptr) {
229 digitZeroSprite->resize(this);
230 }
231 if (digitOneSprite != nullptr) {
232 digitOneSprite->resize(this);
233 }
234 }
235
236 void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override {
237 if (digitZeroSprite == nullptr || digitOneSprite == nullptr) {
238 return;
239 }
240 const auto now = Clock::now();
241 float dt = std::chrono::duration<float>(now - lastFrame).count();
242 lastFrame = now;
243 dt = std::clamp(dt, 0.0f, 1.0f / 15.0f);
244
245 const VkExtent2D extent = getSwapchainExtent();
246 const float aspect = (extent.height > 0U)
247 ? static_cast<float>(extent.width) / static_cast<float>(extent.height)
248 : 1.0f;
249
250 const float yaw = glm::radians(cameraYaw);
251 const float pitch = glm::radians(cameraPitch);
252 glm::mat4 cameraRotation(1.0f);
253 cameraRotation = glm::rotate(cameraRotation, yaw, glm::vec3(0.0f, 1.0f, 0.0f));
254 cameraRotation = glm::rotate(cameraRotation, pitch, glm::vec3(1.0f, 0.0f, 0.0f));
255
256 const glm::vec3 cameraPosition = glm::vec3(cameraRotation * glm::vec4(0.0f, 0.0f, cameraDistance, 1.0f));
257 const glm::vec3 cameraUp = glm::normalize(glm::vec3(cameraRotation * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)));
258 glm::mat4 view = glm::lookAt(cameraPosition, glm::vec3(0.0f), cameraUp);
259 glm::mat4 proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
260 proj[1][1] *= -1.0f;
261
262 updateBinaryRain(dt);
263 backgroundTime += dt;
264
265 drawBackground();
266 backgroundSprite->renderSprites(cmd, sprite_pipeline_layout, extent.width, extent.height);
267 backgroundSprite->clearQueue();
268
269 digitZeroSprite->updateCamera(imageIndex, view, proj);
270 digitOneSprite->updateCamera(imageIndex, view, proj);
271
272 for (int column = 0; column < columns; ++column) {
273 const Stream &stream = streams[column];
274 drawStream(column, stream);
275 }
276
277 digitZeroSprite->render(cmd, imageIndex);
278 digitZeroSprite->clearQueue();
279 digitOneSprite->render(cmd, imageIndex);
280 digitOneSprite->clearQueue();
281 }
282
283 private:
284 void loadDigits() {
285 SurfacePtr zero_surface = renderGlyph(font.get(), "0", digitColor);
286 SurfacePtr one_surface = renderGlyph(font.get(), "1", digitColor);
287
288 if (zero_surface == nullptr || one_surface == nullptr) {
289 throw mxvk::Exception("Failed to render binary matrix glyphs");
290 }
291
292 digitZeroSprite = createSprite3D(zero_surface.get());
293 digitOneSprite = createSprite3D(one_surface.get());
294
295 if (digitZeroSprite == nullptr || digitOneSprite == nullptr) {
296 throw mxvk::Exception("Failed to create binary matrix 3D sprites");
297 }
298
299 digitZeroSprite->setDepthTestEnabled(true);
300 digitZeroSprite->setDepthWriteEnabled(false);
301 digitZeroSprite->setAlphaDiscardThreshold(0.05f);
302
303 digitOneSprite->setDepthTestEnabled(true);
304 digitOneSprite->setDepthWriteEnabled(false);
305 digitOneSprite->setAlphaDiscardThreshold(0.05f);
306 }
307
308 void rebuildForExtent() {
309 const VkExtent2D extent = getSwapchainExtent();
310 const int width = static_cast<int>(extent.width);
311 const int height = static_cast<int>(extent.height);
312 if (width <= 0 || height <= 0 || (extentWidth == width && extentHeight == height)) {
313 return;
314 }
315
316 extentWidth = width;
317 extentHeight = height;
318
319 const int horizontal_spacing = std::max(1, static_cast<int>(std::round(static_cast<float>(glyph_size) * 0.6f)));
320 const int vertical_spacing = std::max(1, glyph_size);
321 columns = std::max(1, width / horizontal_spacing);
322 rows = std::max(1, height / vertical_spacing);
323
324 const float aspect = static_cast<float>(width) / static_cast<float>(std::max(1, height));
325 horizontalSpan = 2.15f * aspect;
326 verticalSpan = 3.6f;
327 columnStep = (horizontalSpan * 2.0f) / static_cast<float>(columns);
328 rowStep = (verticalSpan * 2.0f) / static_cast<float>(rows);
329 baseGlyphSize = std::min(columnStep, rowStep) * 0.82f;
330
331 streams.assign(static_cast<std::size_t>(columns), {});
332 randomizeStreams();
333 }
334
335 void randomizeStreams() {
336 std::uniform_real_distribution<float> headDist(-static_cast<float>(rows) * 1.2f, 0.0f);
337 std::uniform_real_distribution<float> speedDist(4.0f, 12.0f);
338 std::uniform_int_distribution<int> lengthDist(34, std::max(48, rows + rows / 2));
339 std::uniform_int_distribution<int> seedDist(0, 4095);
340 std::uniform_real_distribution<float> phaseDist(0.0f, 6.28318530717958647692f);
341 std::uniform_real_distribution<float> amplitudeDist(0.15f, 0.60f);
342 std::uniform_real_distribution<float> biasDist(-0.22f, 0.28f);
343
344 for (Stream &stream : streams) {
345 stream.head = headDist(rng);
346 stream.speed = speedDist(rng);
347 stream.length = std::min(lengthDist(rng), rows + rows / 2);
348 stream.bitSeed = seedDist(rng);
349 stream.phase = phaseDist(rng);
350 stream.depthPhase = phaseDist(rng);
351 stream.depthAmplitude = amplitudeDist(rng);
352 stream.depthBias = biasDist(rng);
353 }
354 }
355
356 void updateBinaryRain(float dt) {
357 lastScrollPhase += dt * 0.85f;
358
359 for (Stream &stream : streams) {
360 stream.head += stream.speed * dt;
361 if (stream.head - static_cast<float>(stream.length) > static_cast<float>(rows) + 2.0f) {
362 resetStream(stream);
363 }
364 }
365
366 if (std::fmod(lastScrollPhase, 0.22f) < dt * 0.85f) {
367 for (Stream &stream : streams) {
368 stream.bitSeed ^= (frameCounter & 3);
369 }
370 }
371
372 ++frameCounter;
373 }
374
375 void resetStream(Stream &stream) {
376 std::uniform_real_distribution<float> headDist(-static_cast<float>(rows) * 0.9f, -1.0f);
377 std::uniform_real_distribution<float> speedDist(4.0f, 12.0f);
378 std::uniform_int_distribution<int> lengthDist(34, std::max(48, rows + rows / 2));
379 std::uniform_int_distribution<int> seedDist(0, 4095);
380 std::uniform_real_distribution<float> phaseDist(0.0f, 6.28318530717958647692f);
381 std::uniform_real_distribution<float> amplitudeDist(0.15f, 0.60f);
382 std::uniform_real_distribution<float> biasDist(-0.22f, 0.28f);
383
384 stream.head = headDist(rng);
385 stream.speed = speedDist(rng);
386 stream.length = std::min(lengthDist(rng), rows + rows / 2);
387 stream.bitSeed = seedDist(rng);
388 stream.phase = phaseDist(rng);
389 stream.depthPhase = phaseDist(rng);
390 stream.depthAmplitude = amplitudeDist(rng);
391 stream.depthBias = biasDist(rng);
392 }
393
394 void drawStream(int column, const Stream &stream) {
395 const float x = -horizontalSpan + (static_cast<float>(column) + 0.5f) * columnStep;
396
397 const int headRow = static_cast<int>(std::floor(stream.head));
398 for (int tail = stream.length; tail >= 0; --tail) {
399 const int row = headRow - tail;
400 if (row < -1 || row >= rows) {
401 continue;
402 }
403
404 const float age = static_cast<float>(tail) / static_cast<float>(std::max(1, stream.length));
405 const float rowCenter = verticalSpan - (static_cast<float>(row) + 0.5f) * rowStep;
406 const float z = 0.0f;
407 const float size = baseGlyphSize;
408 int level = trailLevels - static_cast<int>(std::round(age * static_cast<float>(trailLevels)));
409 if (tail == 0) {
410 level = trailLevels;
411 } else if (tail <= 2) {
412 level = trailLevels - 1;
413 }
414 level = std::clamp(level, 0, trailLevels);
415 const SDL_Color tintColor = matrixTrailColor(level);
416 const glm::vec4 tint(static_cast<float>(tintColor.r) / 255.0f,
417 static_cast<float>(tintColor.g) / 255.0f,
418 static_cast<float>(tintColor.b) / 255.0f,
419 static_cast<float>(tintColor.a) / 255.0f);
420
421 const int cell = static_cast<int>(std::floor(stream.head)) - tail;
422 const bool drawOne = ((stream.bitSeed + column * 13 + cell * 17 + tail * 3 + frameCounter / 3) & 1) != 0;
423 mxvk::VK_Sprite3D *sprite = drawOne ? digitOneSprite : digitZeroSprite;
424 sprite->drawSprite(glm::vec3(x, rowCenter, z), glm::vec2(size, size), tint, 0.0f);
425 }
426 }
427
428 void drawBackground() {
429 if (backgroundSprite == nullptr) {
430 return;
431 }
432
433 const VkExtent2D extent = getSwapchainExtent();
434 if (extent.width == 0U || extent.height == 0U) {
435 return;
436 }
437
438 backgroundSprite->setShaderParams(backgroundTime,
439 static_cast<float>(mouseX),
440 static_cast<float>(mouseY),
441 mousePressed ? 1.0f : 0.0f);
442 backgroundSprite->drawSpriteRect(0, 0, static_cast<int>(extent.width), static_cast<int>(extent.height));
443 }
444
445 void updateCameraInput() {
446 const bool *keyboard = SDL_GetKeyboardState(nullptr);
447 if (keyboard == nullptr) {
448 return;
449 }
450
451 const auto now = Clock::now();
452 float dt = std::chrono::duration<float>(now - lastCameraInput).count();
453 lastCameraInput = now;
454 dt = std::clamp(dt, 0.0f, 1.0f / 15.0f);
455
456 const float orbitDelta = cameraOrbitSpeed * dt;
457 const float zoomDelta = cameraZoomSpeed * dt;
458
459 if (keyboard[SDL_SCANCODE_LEFT]) {
460 cameraYaw -= orbitDelta;
461 }
462 if (keyboard[SDL_SCANCODE_RIGHT]) {
463 cameraYaw += orbitDelta;
464 }
465 if (keyboard[SDL_SCANCODE_UP]) {
466 cameraPitch += orbitDelta;
467 }
468 if (keyboard[SDL_SCANCODE_DOWN]) {
469 cameraPitch -= orbitDelta;
470 }
471 if (keyboard[SDL_SCANCODE_PAGEUP]) {
472 cameraDistance = std::max(1.5f, cameraDistance - zoomDelta);
473 }
474 if (keyboard[SDL_SCANCODE_PAGEDOWN]) {
475 cameraDistance = std::min(12.0f, cameraDistance + zoomDelta);
476 }
477
478 cameraYaw = std::fmod(cameraYaw, 360.0f);
479 if (cameraYaw < 0.0f) {
480 cameraYaw += 360.0f;
481 }
482
483 cameraPitch = std::fmod(cameraPitch, 360.0f);
484 if (cameraPitch < 0.0f) {
485 cameraPitch += 360.0f;
486 }
487 }
488
489 static constexpr int trailLevels = 7;
490
491 std::string assetRoot;
492 int glyph_size = 22;
493 FontPtr font;
494 mxvk::VK_Sprite *backgroundSprite = nullptr;
495 mxvk::VK_Sprite3D *digitZeroSprite = nullptr;
496 mxvk::VK_Sprite3D *digitOneSprite = nullptr;
497 std::mt19937 rng;
498 Clock::time_point lastFrame{Clock::now()};
499 std::vector<Stream> streams;
500 int columns = 0;
501 int rows = 0;
502 int extentWidth = 0;
503 int extentHeight = 0;
504 float horizontalSpan = 0.0f;
505 float verticalSpan = 0.0f;
506 float columnStep = 0.0f;
507 float rowStep = 0.0f;
508 float baseGlyphSize = 0.0f;
509 float lastScrollPhase = 0.0f;
510 float backgroundTime = 0.0f;
511 float mouseX = 0.0f;
512 float mouseY = 0.0f;
513 bool mousePressed = false;
514 int frameCounter = 0;
515 float cameraYaw = 0.0f;
516 float cameraPitch = 0.0f;
517 float cameraDistance = 4.4f;
518 Clock::time_point lastCameraInput{Clock::now()};
519 static constexpr float cameraOrbitSpeed = 140.0f;
520 static constexpr float cameraZoomSpeed = 2.2f;
521 SDL_Color digitColor{255, 255, 255, 255};
522 };
523} // namespace example
524
525int main(int argc, char **argv) {
526 try {
527 Arguments args = proc_args(argc, argv);
529 args.path, "-[ MXVK Binary Matrix ]-", args.width, args.height, args.fullscreen, args.enable_vsync, args.font_size, args.color);
530 window.loop();
531 } catch (mxvk::Exception &e) {
532 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
533 return EXIT_FAILURE;
534 } catch (ArgException<std::string> &e) {
535 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
536 return EXIT_FAILURE;
537 }
538
539 return EXIT_SUCCESS;
540}
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:872
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:178
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
void proc() override
Execute one processing/update step.
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override
Optional hook for derived classes to record extra draw commands.
void event(SDL_Event &e) override
Handle one SDL event.
BinaryMatrixWindow(const std::string &path, const std::string &title, const int width, const int height, const bool fullscreen, const bool enable_vsync, const int requested_glyph_size, const std::string &color)
std::string text() const
void drawSprite(const glm::vec3 &position, const glm::vec2 &size, const glm::vec4 &color=glm::vec4(1.0f), float rotationRadians=0.0f)
Queue a billboard sprite for rendering.
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:37
VkPipelineLayout sprite_pipeline_layout
Definition mxvk.hpp:543
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:186
void loop()
Run the main event/render loop.
Definition mxvk.cpp:600
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_Window()=default
Construct an empty window object.
int main(void)
Definition main.cpp:7
#define MXVK_VALIDATION
Definition mxvk.hpp:27
std::unique_ptr< TTF_Font, TtfDeleter > FontPtr
std::unique_ptr< SDL_Surface, SurfaceDeleter > SurfacePtr
SurfacePtr renderGlyph(TTF_Font *font, const std::string &glyph, const SDL_Color &color)
std::optional< SDL_Color > parse_color_spec(const std::string &spec)
std::optional< Uint8 > parse_u8_component(const std::string &value, int base)
std::string trim_copy(const std::string &value)
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30
std::default_random_engine & rng()
Returns the thread-local random number engine used by simulation helpers.
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:730
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:736
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:750
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:733
std::string color
Optional rain RGB tint (--color).
Definition argz.hpp:763
std::string path
Asset root; proc_args() defaults it to the executable directory.
Definition argz.hpp:735
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:732
int font_size
Matrix rain font size in pixels (--font-size).
Definition argz.hpp:761