MXVK Vulkan Framework 0.35.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, 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) : mxvk::VK_Window(title, width, height, fullscreen, MXVK_VALIDATION, enable_vsync), assetRoot(path.empty() ? std::string(binary_matrix_ASSET_DIR) : path), glyph_size(requested_glyph_size), rng(std::random_device{}()) {
146 if (assetRoot == ".") {
147 assetRoot = binary_matrix_ASSET_DIR;
148 }
149
150 if (!color.empty()) {
151 const std::optional<SDL_Color> parsedColor = parse_color_spec(color);
152 if (!parsedColor) {
153 throw mxvk::Exception("Invalid binary_matrix color: " + color + " (expected #RRGGBB or R,G,B)");
154 }
155 digitColor = *parsedColor;
156 }
157
158 setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
159
160 if (!TTF_Init()) {
161 throw mxvk::Exception("Failed to initialize SDL_ttf: " + std::string(SDL_GetError()));
162 }
163
164 font.reset(TTF_OpenFont((assetRoot + "/data/NotoSansCJK-Bold.ttc").c_str(), glyph_size));
165 if (!font) {
166 throw mxvk::Exception("Failed to load binary matrix font: " + std::string(SDL_GetError()));
167 }
168 TTF_SetFontHinting(font.get(), TTF_HINTING_LIGHT);
169
170 const std::string spriteVertPath = assetRoot + "/data/sprite.vert.spv";
171 const std::string backgroundFragPath = assetRoot + "/data/background.frag.spv";
172 backgroundSprite = createSprite(assetRoot + "/data/bg.png", spriteVertPath, backgroundFragPath);
173 if (backgroundSprite == nullptr) {
174 throw mxvk::Exception("Failed to create binary matrix background sprite");
175 }
176
177 loadDigits();
178 rebuildForExtent();
179 lastFrame = Clock::now();
180 }
181
183 digitZeroSprite = nullptr;
184 digitOneSprite = nullptr;
185 backgroundSprite = nullptr;
186 font.reset();
187 TTF_Quit();
188 }
189
190 void event(SDL_Event &e) override {
191 if (e.type == SDL_EVENT_KEY_DOWN) {
192 if (e.key.key == SDLK_ESCAPE) {
193 exit();
194 } else if (e.key.key == SDLK_SPACE) {
195 randomizeStreams();
196 }
197 } else if (e.type == SDL_EVENT_MOUSE_MOTION) {
198 mouseX = e.motion.x;
199 mouseY = e.motion.y;
200 } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
201 mousePressed = true;
202 mouseX = e.button.x;
203 mouseY = e.button.y;
204 } else if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) {
205 mousePressed = false;
206 mouseX = e.button.x;
207 mouseY = e.button.y;
208 }
209 }
210
211 void proc() override {
212 rebuildForExtent();
213 updateCameraInput();
214 }
215
216 void onSwapchainRecreated() override {
217 if (digitZeroSprite != nullptr) {
218 digitZeroSprite->resize(this);
219 }
220 if (digitOneSprite != nullptr) {
221 digitOneSprite->resize(this);
222 }
223 }
224
225 void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override {
226 if (digitZeroSprite == nullptr || digitOneSprite == nullptr) {
227 return;
228 }
229 const auto now = Clock::now();
230 float dt = std::chrono::duration<float>(now - lastFrame).count();
231 lastFrame = now;
232 dt = std::clamp(dt, 0.0f, 1.0f / 15.0f);
233
234 const VkExtent2D extent = getSwapchainExtent();
235 const float aspect = (extent.height > 0U) ? static_cast<float>(extent.width) / static_cast<float>(extent.height) : 1.0f;
236
237 const float yaw = glm::radians(cameraYaw);
238 const float pitch = glm::radians(cameraPitch);
239 glm::mat4 cameraRotation(1.0f);
240 cameraRotation = glm::rotate(cameraRotation, yaw, glm::vec3(0.0f, 1.0f, 0.0f));
241 cameraRotation = glm::rotate(cameraRotation, pitch, glm::vec3(1.0f, 0.0f, 0.0f));
242
243 const glm::vec3 cameraPosition = glm::vec3(cameraRotation * glm::vec4(0.0f, 0.0f, cameraDistance, 1.0f));
244 const glm::vec3 cameraUp = glm::normalize(glm::vec3(cameraRotation * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)));
245 glm::mat4 view = glm::lookAt(cameraPosition, glm::vec3(0.0f), cameraUp);
246 glm::mat4 proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
247 proj[1][1] *= -1.0f;
248
249 updateBinaryRain(dt);
250 backgroundTime += dt;
251
252 drawBackground();
253 backgroundSprite->renderSprites(cmd, sprite_pipeline_layout, extent.width, extent.height);
254 backgroundSprite->clearQueue();
255
256 digitZeroSprite->updateCamera(imageIndex, view, proj);
257 digitOneSprite->updateCamera(imageIndex, view, proj);
258
259 for (int column = 0; column < columns; ++column) {
260 const Stream &stream = streams[column];
261 drawStream(column, stream);
262 }
263
264 digitZeroSprite->render(cmd, imageIndex);
265 digitZeroSprite->clearQueue();
266 digitOneSprite->render(cmd, imageIndex);
267 digitOneSprite->clearQueue();
268 }
269
270 private:
271 void loadDigits() {
272 SurfacePtr zero_surface = renderGlyph(font.get(), "0", digitColor);
273 SurfacePtr one_surface = renderGlyph(font.get(), "1", digitColor);
274
275 if (zero_surface == nullptr || one_surface == nullptr) {
276 throw mxvk::Exception("Failed to render binary matrix glyphs");
277 }
278
279 digitZeroSprite = createSprite3D(zero_surface.get());
280 digitOneSprite = createSprite3D(one_surface.get());
281
282 if (digitZeroSprite == nullptr || digitOneSprite == nullptr) {
283 throw mxvk::Exception("Failed to create binary matrix 3D sprites");
284 }
285
286 digitZeroSprite->setDepthTestEnabled(true);
287 digitZeroSprite->setDepthWriteEnabled(false);
288 digitZeroSprite->setAlphaDiscardThreshold(0.05f);
289
290 digitOneSprite->setDepthTestEnabled(true);
291 digitOneSprite->setDepthWriteEnabled(false);
292 digitOneSprite->setAlphaDiscardThreshold(0.05f);
293 }
294
295 void rebuildForExtent() {
296 const VkExtent2D extent = getSwapchainExtent();
297 const int width = static_cast<int>(extent.width);
298 const int height = static_cast<int>(extent.height);
299 if (width <= 0 || height <= 0 || (extentWidth == width && extentHeight == height)) {
300 return;
301 }
302
303 extentWidth = width;
304 extentHeight = height;
305
306 const int horizontal_spacing = std::max(1, static_cast<int>(std::round(static_cast<float>(glyph_size) * 0.6f)));
307 const int vertical_spacing = std::max(1, glyph_size);
308 columns = std::max(1, width / horizontal_spacing);
309 rows = std::max(1, height / vertical_spacing);
310
311 const float aspect = static_cast<float>(width) / static_cast<float>(std::max(1, height));
312 horizontalSpan = 2.15f * aspect;
313 verticalSpan = 3.6f;
314 columnStep = (horizontalSpan * 2.0f) / static_cast<float>(columns);
315 rowStep = (verticalSpan * 2.0f) / static_cast<float>(rows);
316 baseGlyphSize = std::min(columnStep, rowStep) * 0.82f;
317
318 streams.assign(static_cast<std::size_t>(columns), {});
319 randomizeStreams();
320 }
321
322 void randomizeStreams() {
323 std::uniform_real_distribution<float> headDist(-static_cast<float>(rows) * 1.2f, 0.0f);
324 std::uniform_real_distribution<float> speedDist(4.0f, 12.0f);
325 std::uniform_int_distribution<int> lengthDist(34, std::max(48, rows + rows / 2));
326 std::uniform_int_distribution<int> seedDist(0, 4095);
327 std::uniform_real_distribution<float> phaseDist(0.0f, 6.28318530717958647692f);
328 std::uniform_real_distribution<float> amplitudeDist(0.15f, 0.60f);
329 std::uniform_real_distribution<float> biasDist(-0.22f, 0.28f);
330
331 for (Stream &stream : streams) {
332 stream.head = headDist(rng);
333 stream.speed = speedDist(rng);
334 stream.length = std::min(lengthDist(rng), rows + rows / 2);
335 stream.bitSeed = seedDist(rng);
336 stream.phase = phaseDist(rng);
337 stream.depthPhase = phaseDist(rng);
338 stream.depthAmplitude = amplitudeDist(rng);
339 stream.depthBias = biasDist(rng);
340 }
341 }
342
343 void updateBinaryRain(float dt) {
344 lastScrollPhase += dt * 0.85f;
345
346 for (Stream &stream : streams) {
347 stream.head += stream.speed * dt;
348 if (stream.head - static_cast<float>(stream.length) > static_cast<float>(rows) + 2.0f) {
349 resetStream(stream);
350 }
351 }
352
353 if (std::fmod(lastScrollPhase, 0.22f) < dt * 0.85f) {
354 for (Stream &stream : streams) {
355 stream.bitSeed ^= (frameCounter & 3);
356 }
357 }
358
359 ++frameCounter;
360 }
361
362 void resetStream(Stream &stream) {
363 std::uniform_real_distribution<float> headDist(-static_cast<float>(rows) * 0.9f, -1.0f);
364 std::uniform_real_distribution<float> speedDist(4.0f, 12.0f);
365 std::uniform_int_distribution<int> lengthDist(34, std::max(48, rows + rows / 2));
366 std::uniform_int_distribution<int> seedDist(0, 4095);
367 std::uniform_real_distribution<float> phaseDist(0.0f, 6.28318530717958647692f);
368 std::uniform_real_distribution<float> amplitudeDist(0.15f, 0.60f);
369 std::uniform_real_distribution<float> biasDist(-0.22f, 0.28f);
370
371 stream.head = headDist(rng);
372 stream.speed = speedDist(rng);
373 stream.length = std::min(lengthDist(rng), rows + rows / 2);
374 stream.bitSeed = seedDist(rng);
375 stream.phase = phaseDist(rng);
376 stream.depthPhase = phaseDist(rng);
377 stream.depthAmplitude = amplitudeDist(rng);
378 stream.depthBias = biasDist(rng);
379 }
380
381 void drawStream(int column, const Stream &stream) {
382 const float x = -horizontalSpan + (static_cast<float>(column) + 0.5f) * columnStep;
383
384 const int headRow = static_cast<int>(std::floor(stream.head));
385 for (int tail = stream.length; tail >= 0; --tail) {
386 const int row = headRow - tail;
387 if (row < -1 || row >= rows) {
388 continue;
389 }
390
391 const float age = static_cast<float>(tail) / static_cast<float>(std::max(1, stream.length));
392 const float rowCenter = verticalSpan - (static_cast<float>(row) + 0.5f) * rowStep;
393 const float z = 0.0f;
394 const float size = baseGlyphSize;
395 int level = trailLevels - static_cast<int>(std::round(age * static_cast<float>(trailLevels)));
396 if (tail == 0) {
397 level = trailLevels;
398 } else if (tail <= 2) {
399 level = trailLevels - 1;
400 }
401 level = std::clamp(level, 0, trailLevels);
402 const SDL_Color tintColor = matrixTrailColor(level);
403 const glm::vec4 tint(static_cast<float>(tintColor.r) / 255.0f, static_cast<float>(tintColor.g) / 255.0f, static_cast<float>(tintColor.b) / 255.0f, static_cast<float>(tintColor.a) / 255.0f);
404
405 const int cell = static_cast<int>(std::floor(stream.head)) - tail;
406 const bool drawOne = ((stream.bitSeed + column * 13 + cell * 17 + tail * 3 + frameCounter / 3) & 1) != 0;
407 mxvk::VK_Sprite3D *sprite = drawOne ? digitOneSprite : digitZeroSprite;
408 sprite->drawSprite(glm::vec3(x, rowCenter, z), glm::vec2(size, size), tint, 0.0f);
409 }
410 }
411
412 void drawBackground() {
413 if (backgroundSprite == nullptr) {
414 return;
415 }
416
417 const VkExtent2D extent = getSwapchainExtent();
418 if (extent.width == 0U || extent.height == 0U) {
419 return;
420 }
421
422 backgroundSprite->setShaderParams(backgroundTime, static_cast<float>(mouseX), static_cast<float>(mouseY), mousePressed ? 1.0f : 0.0f);
423 backgroundSprite->drawSpriteRect(0, 0, static_cast<int>(extent.width), static_cast<int>(extent.height));
424 }
425
426 void updateCameraInput() {
427 const bool *keyboard = SDL_GetKeyboardState(nullptr);
428 if (keyboard == nullptr) {
429 return;
430 }
431
432 const auto now = Clock::now();
433 float dt = std::chrono::duration<float>(now - lastCameraInput).count();
434 lastCameraInput = now;
435 dt = std::clamp(dt, 0.0f, 1.0f / 15.0f);
436
437 const float orbitDelta = cameraOrbitSpeed * dt;
438 const float zoomDelta = cameraZoomSpeed * dt;
439
440 if (keyboard[SDL_SCANCODE_LEFT]) {
441 cameraYaw -= orbitDelta;
442 }
443 if (keyboard[SDL_SCANCODE_RIGHT]) {
444 cameraYaw += orbitDelta;
445 }
446 if (keyboard[SDL_SCANCODE_UP]) {
447 cameraPitch += orbitDelta;
448 }
449 if (keyboard[SDL_SCANCODE_DOWN]) {
450 cameraPitch -= orbitDelta;
451 }
452 if (keyboard[SDL_SCANCODE_PAGEUP]) {
453 cameraDistance = std::max(1.5f, cameraDistance - zoomDelta);
454 }
455 if (keyboard[SDL_SCANCODE_PAGEDOWN]) {
456 cameraDistance = std::min(12.0f, cameraDistance + zoomDelta);
457 }
458
459 cameraYaw = std::fmod(cameraYaw, 360.0f);
460 if (cameraYaw < 0.0f) {
461 cameraYaw += 360.0f;
462 }
463
464 cameraPitch = std::fmod(cameraPitch, 360.0f);
465 if (cameraPitch < 0.0f) {
466 cameraPitch += 360.0f;
467 }
468 }
469
470 static constexpr int trailLevels = 7;
471
472 std::string assetRoot;
473 int glyph_size = 22;
474 FontPtr font;
475 mxvk::VK_Sprite *backgroundSprite = nullptr;
476 mxvk::VK_Sprite3D *digitZeroSprite = nullptr;
477 mxvk::VK_Sprite3D *digitOneSprite = nullptr;
478 std::mt19937 rng;
479 Clock::time_point lastFrame{Clock::now()};
480 std::vector<Stream> streams;
481 int columns = 0;
482 int rows = 0;
483 int extentWidth = 0;
484 int extentHeight = 0;
485 float horizontalSpan = 0.0f;
486 float verticalSpan = 0.0f;
487 float columnStep = 0.0f;
488 float rowStep = 0.0f;
489 float baseGlyphSize = 0.0f;
490 float lastScrollPhase = 0.0f;
491 float backgroundTime = 0.0f;
492 float mouseX = 0.0f;
493 float mouseY = 0.0f;
494 bool mousePressed = false;
495 int frameCounter = 0;
496 float cameraYaw = 0.0f;
497 float cameraPitch = 0.0f;
498 float cameraDistance = 4.4f;
499 Clock::time_point lastCameraInput{Clock::now()};
500 static constexpr float cameraOrbitSpeed = 140.0f;
501 static constexpr float cameraZoomSpeed = 2.2f;
502 SDL_Color digitColor{255, 255, 255, 255};
503 };
504} // namespace example
505
506int main(int argc, char **argv) {
507 try {
508 Arguments args = proc_args(argc, argv);
509 example::BinaryMatrixWindow window(args.path, "-[ MXVK Binary Matrix ]-", args.width, args.height, args.fullscreen, args.enable_vsync, args.font_size, args.color);
510 window.loop();
511 } catch (mxvk::Exception &e) {
512 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
513 return EXIT_FAILURE;
514 } catch (ArgException<std::string> &e) {
515 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
516 return EXIT_FAILURE;
517 }
518
519 return EXIT_SUCCESS;
520}
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:854
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:169
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:39
VkPipelineLayout sprite_pipeline_layout
Definition mxvk.hpp:680
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:222
void loop()
Run the main event/render loop.
Definition mxvk.cpp:651
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_Window()=default
Construct an empty window object.
#define MXVK_VALIDATION
Definition mxvk.hpp:28
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)
int main()
Definition main.py:165
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
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:718
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:724
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:738
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:721
std::string color
Optional rain RGB tint (--color).
Definition argz.hpp:751
std::string path
Asset root; proc_args() defaults it to the executable directory.
Definition argz.hpp:723
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:720
int font_size
Matrix rain font size in pixels (--font-size).
Definition argz.hpp:749