16#include <opencv2/videoio.hpp>
20#include <glm/ext/matrix_clip_space.hpp>
21#include <glm/ext/matrix_transform.hpp>
24#ifndef shader_viewer_ASSET_DIR
25#define shader_viewer_ASSET_DIR "."
28#ifndef shader_viewer_SHADER_DIR
29#define shader_viewer_SHADER_DIR "."
32#ifndef shader_viewer_SOURCE_DIR
33#define shader_viewer_SOURCE_DIR "."
37 [[nodiscard]] std::string
trimLine(
const std::string &text) {
38 auto begin = text.begin();
39 while (begin != text.end() && std::isspace(
static_cast<unsigned char>(*begin)) != 0) {
43 auto end = text.end();
44 while (end != begin && std::isspace(
static_cast<unsigned char>(*(end - 1))) != 0) {
48 return std::string(begin, end);
51 [[nodiscard]] std::string
joinPath(
const std::string &base,
const std::string &file) {
52 const std::filesystem::path file_path(file);
53 if (base.empty() || file_path.is_absolute()) {
54 return file_path.string();
56 return (std::filesystem::path(base) / file_path).string();
59 [[nodiscard]] std::string
resolveShaderEntry(
const std::string &shader_path,
const std::string &entry) {
60 const std::filesystem::path entry_path(entry);
61 if (entry_path.extension() ==
".spv") {
65 std::filesystem::path spv_entry = entry_path.parent_path() /
"spv" / entry_path.stem();
66 spv_entry.replace_extension(
".spv");
67 const std::filesystem::path spv_path = std::filesystem::path(shader_path) / spv_entry;
68 if (std::filesystem::exists(spv_path)) {
69 return spv_path.string();
72 std::filesystem::path sibling_entry = entry_path;
73 sibling_entry.replace_extension(
".spv");
74 const std::filesystem::path sibling_spv_path = std::filesystem::path(shader_path) / sibling_entry;
75 if (std::filesystem::exists(sibling_spv_path)) {
76 return sibling_spv_path.string();
83 std::string current_path =
".";
84 std::string shader_path;
85 std::string input_filename;
86 std::string model_filename;
87 std::string model_vertex_shader;
88 std::vector<std::string> shader_files;
89 int current_shader_index = 0;
91 double requested_fps = 0.0;
93 bool using_file =
false;
94 bool using_model =
false;
95 bool shader_list_requested =
false;
97 mxvk::VK_Sprite *camera_sprite =
nullptr;
98 mxvk::VKAbstractModel model{};
99 int fallback_width = 1280;
100 int fallback_height = 720;
101 double current_fps = 0.0;
102 double current_capture_fps = 0.0;
103 uint32_t fps_frame_count = 0;
104 uint32_t capture_fps_frame_count = 0;
105 std::chrono::steady_clock::time_point fps_sample_time{std::chrono::steady_clock::now()};
106 std::chrono::steady_clock::time_point capture_fps_sample_time{std::chrono::steady_clock::now()};
107 std::chrono::steady_clock::time_point shader_start_time{std::chrono::steady_clock::now()};
108 std::chrono::steady_clock::time_point previous_shader_frame_time{shader_start_time};
109 uint32_t shader_frame_count = 0;
110 float mouse_x = 0.0f;
111 float mouse_y = 0.0f;
112 bool mouse_pressed =
false;
113 std::string fps_text =
"FPS: --";
114 bool wireframe =
false;
115 bool auto_rotate =
true;
116 bool show_help =
true;
117 bool mouse_dragging =
false;
118 int last_mouse_x = 0;
119 int last_mouse_y = 0;
120 float rotation_x_degrees = 0.0f;
121 float rotation_y_degrees = 0.0f;
122 float auto_rotation_radians = 0.0f;
123 float camera_distance = 5.0f;
124 bool menu_visible =
true;
125 std::chrono::steady_clock::time_point last_model_update_time{std::chrono::steady_clock::now()};
127 [[nodiscard]]
bool openCaptureSource() {
129 return capture.open(input_filename);
131 return capture.
open(camera_index);
134 [[nodiscard]] std::string resolveModelPath(
const std::string &name)
const {
135 namespace fs = std::filesystem;
136 const fs::path requested(name);
137 std::error_code ec{};
138 if (fs::exists(requested, ec)) {
139 return fs::weakly_canonical(requested, ec).string();
145 const fs::path candidates[] = {
146 fs::path(current_path) / requested,
147 fs::path(current_path) /
"data" / requested,
148 source_root / requested,
149 source_root /
"models" / requested,
150 source_root /
"models" / requested.filename(),
151 runtime_root / requested,
152 runtime_root /
"models" / requested.filename(),
155 for (
const fs::path &candidate : candidates) {
156 if (fs::exists(candidate, ec)) {
157 return fs::weakly_canonical(candidate, ec).string();
161 throw mxvk::Exception(std::format(
"shader_viewer: failed to locate model '{}'", name));
164 [[nodiscard]] std::string resolveOptionalPath(
const std::string &name)
const {
168 namespace fs = std::filesystem;
169 const fs::path requested(name);
170 std::error_code ec{};
172 const fs::path candidates[] = {
174 fs::path(current_path) / requested,
175 fs::path(current_path) /
"data" / requested,
176 source_root / requested,
177 source_root /
"models" / requested,
179 for (
const fs::path &candidate : candidates) {
180 if (fs::exists(candidate, ec)) {
181 return fs::weakly_canonical(candidate, ec).string();
188 void loadShaderIndex() {
189 shader_files.clear();
190 current_shader_index = 0;
192 const std::string index_path =
joinPath(shader_path,
"index.txt");
193 std::ifstream input(index_path);
194 if (!input.is_open()) {
195 if (shader_list_requested) {
196 throw mxvk::Exception(std::format(
"shader_viewer: failed to open shader index '{}'", index_path));
202 while (std::getline(input, line)) {
203 const std::string entry =
trimLine(line);
204 if (entry.empty() || entry.front() ==
'#') {
208 if (std::filesystem::path(shader_file).extension() !=
".spv") {
209 std::cerr << std::format(
"shader_viewer: skipping non-SPIR-V shader entry '{}'\n", entry);
212 shader_files.push_back(shader_file);
215 if (shader_list_requested && shader_files.empty()) {
216 throw mxvk::Exception(std::format(
"shader_viewer: shader index '{}' did not list any shaders", index_path));
220 void setInitialShaderIndex(
int index) {
221 if (shader_files.empty()) {
222 current_shader_index = 0;
226 const int shader_count =
static_cast<int>(shader_files.size());
227 current_shader_index = index % shader_count;
228 if (current_shader_index < 0) {
229 current_shader_index += shader_count;
233 [[nodiscard]] std::string currentFragmentShader()
const {
234 if (shader_files.empty()) {
235 return joinPath(shader_path,
"fragment.frag.spv");
237 return shader_files[
static_cast<std::size_t
>(current_shader_index)];
240 void enableShaderUniforms() {
241 if (camera_sprite ==
nullptr) {
244 camera_sprite->enableExtendedUBO();
247 void updateShaderUniforms(
int target_w,
int target_h) {
248 if (camera_sprite ==
nullptr) {
252 const auto now = std::chrono::steady_clock::now();
253 const float elapsed_seconds = std::chrono::duration<float>(now - shader_start_time).count();
254 const float delta_seconds = std::chrono::duration<float>(now - previous_shader_frame_time).count();
255 previous_shader_frame_time = now;
256 ++shader_frame_count;
258 const float frame_rate = (delta_seconds > 0.0f) ? (1.0f / delta_seconds) : 0.0f;
259 camera_sprite->setShaderParams(1.0f, 1.0f, 1.0f, elapsed_seconds);
260 camera_sprite->setMouseState(mouse_x, mouse_y, mouse_pressed ? 1.0f : 0.0f);
261 camera_sprite->setUniform0(1.0f, 1.0f,
static_cast<float>(target_w),
static_cast<float>(target_h));
262 camera_sprite->setUniform1(delta_seconds, 0.0f, 0.0f, frame_rate);
263 camera_sprite->setUniform2(
static_cast<float>(shader_frame_count), elapsed_seconds, 48000.0f, 0.0f);
266 void updateCaptureFpsSample() {
267 ++capture_fps_frame_count;
268 const auto now = std::chrono::steady_clock::now();
269 const double elapsed = std::chrono::duration<double>(now - capture_fps_sample_time).count();
274 current_capture_fps =
static_cast<double>(capture_fps_frame_count) / elapsed;
275 capture_fps_frame_count = 0;
276 capture_fps_sample_time = now;
279 void cleanupWindowResources() {
283 camera_sprite =
nullptr;
288 if (capture.is_open()) {
293 [[nodiscard]]
double configureCameraFps() {
294 if (requested_fps > 0.0) {
295 capture.set(cv::CAP_PROP_FPS, requested_fps);
296 const double reportedFps = capture.get(cv::CAP_PROP_FPS);
297 if (reportedFps > 0.0 && std::abs(reportedFps - requested_fps) > 0.5) {
298 std::cerr << std::format(
"shader_viewer: requested {:.1f} capture fps, backend reports {:.1f} fps\n", requested_fps, reportedFps);
300 return (reportedFps > 0.0) ? reportedFps : requested_fps;
303 static constexpr std::array<double, 3> fpsChoices = {60.0, 30.0, 24.0};
305 for (
const double requestedFps : fpsChoices) {
306 capture.set(cv::CAP_PROP_FPS, requestedFps);
307 const double reportedFps = capture.get(cv::CAP_PROP_FPS);
308 if (reportedFps > 0.0 && reportedFps + 0.5 >= requestedFps) {
313 capture.set(cv::CAP_PROP_FPS, fpsChoices.back());
314 const double reportedFps = capture.get(cv::CAP_PROP_FPS);
315 return (reportedFps > 0.0) ? reportedFps : fpsChoices.back();
318 [[nodiscard]]
bool createOrRefreshCameraSprite() {
319 int frame_width =
static_cast<int>(capture.get(cv::CAP_PROP_FRAME_WIDTH));
320 int frame_height =
static_cast<int>(capture.get(cv::CAP_PROP_FRAME_HEIGHT));
321 if (frame_width <= 0 || frame_height <= 0) {
322 frame_width = fallback_width;
323 frame_height = fallback_height;
326 const std::string vertex_shader;
327 const std::string fragment_shader = currentFragmentShader();
329 if (camera_sprite ==
nullptr) {
330 camera_sprite =
createSprite(frame_width, frame_height);
331 enableShaderUniforms();
332 if (camera_sprite !=
nullptr) {
333 camera_sprite->createEmptySprite(frame_width, frame_height, vertex_shader, fragment_shader);
335 return camera_sprite !=
nullptr;
338 camera_sprite->createEmptySprite(frame_width, frame_height, vertex_shader, fragment_shader);
339 enableShaderUniforms();
343 void selectShader(
int direction) {
344 if (shader_files.empty()) {
348 const int shader_count =
static_cast<int>(shader_files.size());
349 current_shader_index = (current_shader_index + direction) % shader_count;
350 if (current_shader_index < 0) {
351 current_shader_index += shader_count;
359 model.setShaders(
this, model_vertex_shader, currentFragmentShader());
360 }
else if (!createOrRefreshCameraSprite()) {
361 throw mxvk::Exception(
"shader_viewer: failed to switch capture shader");
364 std::cout << std::format(
"shader_viewer: selected shader {} of {}: {}\n", current_shader_index + 1, shader_count, currentFragmentShader());
367 [[nodiscard]]
bool uploadCaptureFrameToSprite() {
368 if (camera_sprite ==
nullptr) {
372 if (!capture.readToSprite(*camera_sprite,
false)) {
376 updateCaptureFpsSample();
380 void initializeCameraRendering() {
385 if (!createOrRefreshCameraSprite()) {
386 throw mxvk::Exception(
"shader_viewer: failed to create capture sprite");
389 if (camera_sprite !=
nullptr && !uploadCaptureFrameToSprite()) {
390 std::cerr <<
"shader_viewer: failed to upload initial camera frame\n";
394 void updateFpsOverlay() {
396 const auto now = std::chrono::steady_clock::now();
397 const double elapsed = std::chrono::duration<double>(now - fps_sample_time).count();
398 if (elapsed >= 0.25) {
399 current_fps =
static_cast<double>(fps_frame_count) / elapsed;
401 fps_sample_time = now;
403 fps_text = std::format(
"FPS: {:.1f}", current_fps);
404 }
else if (requested_fps > 0.0) {
405 fps_text = std::format(
"FPS: {:.1f} Capture: {:.1f}/{:.1f}", current_fps, current_capture_fps, requested_fps);
407 fps_text = std::format(
"FPS: {:.1f} Capture: {:.1f}", current_fps, current_capture_fps);
414 printText(fps_text, 15, 15, SDL_Color{255, 255, 255, 255});
416 printText(std::format(
"Model: {} Wire: {} Auto: {} M: Toggle Menu", std::filesystem::path(model_filename).filename().
string(), wireframe ?
"on" :
"off", auto_rotate ?
"on" :
"off"),
417 15, 39, SDL_Color{220, 228, 240, 255});
419 printText(
"Drag/Left/Right rotate Wheel/A/S zoom Up/Down shader W wire R auto Home reset Esc quit",
420 15,
static_cast<int>(
getSwapchainExtent().height) - 30, SDL_Color{195, 205, 220, 255});
430 shader_list_requested = !args.
shaderPath.empty();
434 if (!std::filesystem::exists(
font_path)) {
439 model_filename = args.
model;
441 requested_fps = args.
fps;
442 using_file = !input_filename.empty();
443 using_model = !model_filename.empty();
445 fallback_width = args.
width;
446 fallback_height = args.
height;
448 model_filename = resolveModelPath(model_filename);
449 const std::string texture_manifest = resolveOptionalPath(args.
texture);
451 ? std::filesystem::path(texture_manifest.empty() ? model_filename : texture_manifest).parent_path().string()
453 model_vertex_shader =
joinPath(current_path,
"data/model.vert.spv");
454 if (!std::filesystem::exists(model_vertex_shader)) {
457 std::cout << std::format(
"shader_viewer: model='{}' fragment='{}'\n", model_filename, currentFragmentShader());
458 model.enableExtendedFragmentUniforms();
459 model.load(
this, model_filename, texture_manifest, texture_base, 1.0f);
460 model.setShaders(
this, model_vertex_shader, currentFragmentShader());
462 if (!openCaptureSource()) {
463 throw mxvk::Exception(std::format(
"shader_viewer: failed to open video file '{}'", input_filename));
465 fps = capture.get(cv::CAP_PROP_FPS);
466 if (!capture.readToModelTexture(model)) {
467 throw mxvk::Exception(std::format(
"shader_viewer: failed to upload the first video frame from '{}'", input_filename));
469 updateCaptureFpsSample();
470 std::cout << std::format(
"shader_viewer: video texture='{}' @ {:.1f} fps\n", input_filename, fps);
475 if (!openCaptureSource()) {
477 throw mxvk::Exception(std::format(
"shader_viewer: failed to open video file '{}'", input_filename));
479 throw mxvk::Exception(std::format(
"shader_viewer: failed to open camera index {}", camera_index));
482 capture.set(cv::CAP_PROP_FRAME_WIDTH, fallback_width);
483 capture.set(cv::CAP_PROP_FRAME_HEIGHT, fallback_height);
484 fallback_width = capture.get(cv::CAP_PROP_FRAME_WIDTH);
485 fallback_height = capture.get(cv::CAP_PROP_FRAME_HEIGHT);
486 fps = configureCameraFps();
488 fps = capture.get(cv::CAP_PROP_FPS);
490 std::cout <<
"mxvk_cv: Capture opened at: " << fallback_width <<
"x" << fallback_height <<
" @ " << fps <<
" fps\n";
491 initializeCameraRendering();
493 cleanupWindowResources();
499 cleanupWindowResources();
503 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
505 }
else if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_UP && !e.key.repeat) {
507 }
else if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_DOWN && !e.key.repeat) {
509 }
else if (using_model && e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
512 menu_visible = !menu_visible;
515 wireframe = !wireframe;
519 auto_rotate = !auto_rotate;
523 show_help = !show_help;
526 rotation_x_degrees = 0.0f;
527 rotation_y_degrees = 0.0f;
528 auto_rotation_radians = 0.0f;
529 camera_distance = 5.0f;
533 camera_distance = std::clamp(camera_distance - 0.5f, 0.4f, 100.0f);
536 camera_distance = std::clamp(camera_distance + 0.5f, 0.4f, 100.0f);
541 }
else if (e.type == SDL_EVENT_MOUSE_MOTION) {
542 mouse_x = e.motion.x;
543 mouse_y = e.motion.y;
544 if (using_model && mouse_dragging) {
545 rotation_y_degrees += (e.motion.x -
static_cast<float>(last_mouse_x)) * 0.5f;
546 rotation_x_degrees += (e.motion.y -
static_cast<float>(last_mouse_y)) * 0.5f;
547 rotation_x_degrees = std::clamp(rotation_x_degrees, -89.0f, 89.0f);
548 last_mouse_x =
static_cast<int>(e.motion.x);
549 last_mouse_y =
static_cast<int>(e.motion.y);
551 }
else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
552 mouse_pressed =
true;
553 mouse_x = e.button.x;
554 mouse_y = e.button.y;
555 mouse_dragging = using_model;
556 last_mouse_x =
static_cast<int>(e.button.x);
557 last_mouse_y =
static_cast<int>(e.button.y);
558 }
else if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
559 mouse_pressed =
false;
560 mouse_dragging =
false;
561 mouse_x = e.button.x;
562 mouse_y = e.button.y;
563 }
else if (using_model && e.type == SDL_EVENT_MOUSE_WHEEL) {
564 const float delta = (e.wheel.y != 0.0f) ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
565 camera_distance = std::clamp(camera_distance - delta * 0.45f, 0.4f, 100.0f);
573 initializeCameraRendering();
579 if (using_file && !capture.readToModelTexture(model)) {
581 if (!openCaptureSource() || !capture.readToModelTexture(model)) {
582 std::cerr <<
"shader_viewer: failed to restart video texture stream\n";
586 updateCaptureFpsSample();
589 const auto now = std::chrono::steady_clock::now();
590 const float delta_seconds = std::clamp(std::chrono::duration<float>(now - last_model_update_time).count(), 0.0f, 0.1f);
591 last_model_update_time = now;
592 const bool *keys = SDL_GetKeyboardState(
nullptr);
593 constexpr float ROTATE_SPEED = 120.0f;
594 if (keys !=
nullptr) {
595 if (keys[SDL_SCANCODE_LEFT]) {
596 rotation_y_degrees -= ROTATE_SPEED * delta_seconds;
598 if (keys[SDL_SCANCODE_RIGHT]) {
599 rotation_y_degrees += ROTATE_SPEED * delta_seconds;
601 if (keys[SDL_SCANCODE_A]) {
602 camera_distance = std::clamp(camera_distance - 2.5f * delta_seconds, 0.4f, 100.0f);
604 if (keys[SDL_SCANCODE_S]) {
605 camera_distance = std::clamp(camera_distance + 2.5f * delta_seconds, 0.4f, 100.0f);
609 auto_rotation_radians = std::fmod(auto_rotation_radians + 0.55f * delta_seconds, 6.28318530718f);
615 if (!uploadCaptureFrameToSprite()) {
618 if (openCaptureSource()) {
619 if (!uploadCaptureFrameToSprite()) {
620 std::cerr <<
"shader_viewer: failed to upload restarted stream frame\n";
624 fps = configureCameraFps();
628 int target_w = fallback_width;
629 int target_h = fallback_height;
636 updateShaderUniforms(target_w, target_h);
637 camera_sprite->drawSpriteRect(0, 0, target_w, target_h);
648 const float aspect = extent.height > 0U ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height) : 1.0f;
649 const float elapsed_seconds = std::chrono::duration<float>(std::chrono::steady_clock::now() - shader_start_time).count();
652 ubo.
model = glm::rotate(glm::mat4(1.0f), glm::radians(rotation_x_degrees), glm::vec3(1.0f, 0.0f, 0.0f));
653 ubo.
model = glm::rotate(ubo.
model, glm::radians(rotation_y_degrees) + auto_rotation_radians, glm::vec3(0.0f, 1.0f, 0.0f));
654 ubo.
model = glm::scale(ubo.
model, glm::vec3(model.modelRenderScale()));
655 ubo.
model = glm::translate(ubo.
model, model.modelCenterOffset());
656 ubo.
view = glm::lookAt(glm::vec3(0.0f, 0.0f, camera_distance), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
657 ubo.
proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
658 ubo.
proj[1][1] *= -1.0f;
659 ubo.
fx = glm::vec4(elapsed_seconds, wireframe ? 1.0f : 0.0f, 0.0f, 0.0f);
660 model.updateUBO(image_index, ubo);
662 const float delta_seconds = std::chrono::duration<float>(std::chrono::steady_clock::now() - previous_shader_frame_time).count();
663 previous_shader_frame_time = std::chrono::steady_clock::now();
664 ++shader_frame_count;
666 fragment_uniforms.
mouse = glm::vec4(mouse_x, mouse_y, mouse_pressed ? 1.0f : 0.0f, 0.0f);
667 fragment_uniforms.
u0 = glm::vec4(1.0f, 1.0f,
static_cast<float>(extent.width),
static_cast<float>(extent.height));
668 fragment_uniforms.
u1 = glm::vec4(delta_seconds, 0.0f, 0.0f, delta_seconds > 0.0f ? 1.0f / delta_seconds : 0.0f);
669 fragment_uniforms.
u2 = glm::vec4(
static_cast<float>(shader_frame_count), elapsed_seconds, 48000.0f, 0.0f);
670 model.updateFragmentUBO(image_index, fragment_uniforms);
673 fragment_constants.
screenWidth =
static_cast<float>(extent.width);
674 fragment_constants.
screenHeight =
static_cast<float>(extent.height);
675 fragment_constants.
spriteSizeW =
static_cast<float>(extent.width);
676 fragment_constants.
spriteSizeH =
static_cast<float>(extent.height);
677 fragment_constants.
params = glm::vec4(1.0f, 1.0f, 1.0f, elapsed_seconds);
678 model.setFragmentPushConstants(fragment_constants);
680 model.renderWithPushConstants(cmd, image_index, 0, ubo, wireframe);
682 model.render(cmd, image_index, wireframe);
688int main(
int argc,
char **argv) {
694 std::cerr << std::format(
"mxvk: Exception: {}\n", e.
text());
697 std::cerr << std::format(
"mxvk: Argument Exception: {}\n", e.
text());
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.
Exception thrown by Argz::proc() on unrecognised or malformed options.
ExampleWindow(const Arguments &args, const std::string &text)
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
void event(SDL_Event &e) override
Handle one SDL event.
void proc() override
Execute one processing/update step.
~ExampleWindow() override
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override
Optional hook for derived classes to record extra draw commands.
Vulkan OpenCV video capture source.
bool open(const std::string &filename)
Open a video file.
Main Vulkan window wrapper for MXVK.
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
void loop()
Run the main event/render loop.
VkDevice getDevice() const noexcept
Get the Vulkan logical device handle.
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.
VkExtent2D swapchain_extent
void createDevice()
Create final device resources.
void exit()
Request loop termination.
VkCommandPool command_pool
VK_Window()=default
Construct an empty window object.
void release()
Release Vulkan and SDL resources.
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
void printText(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for rendering during the current frame.
High-level model wrapper integrated with MXVK dynamic rendering.
OpenCV video-capture integration for the Vulkan backend.
std::string trimLine(const std::string &text)
std::string joinPath(const std::string &base, const std::string &file)
std::string resolveShaderEntry(const std::string &shader_path, const std::string &entry)
Utilities for loading and saving PNG images.
#define shader_viewer_ASSET_DIR
#define shader_viewer_SOURCE_DIR
Plain data structure returned by proc_args() with all common libmx2 CLI options.
std::string shaderPath
Optional SPV shader folder path (-S / --shader-path).
int camera_index
Optional camera index.
std::string model
Optional model filename (--model).
std::string texture
Optional texture file path (--texture).
int height
Viewport height in pixels (default: 720).
double fps
Optional FPS override (--fps); non-positive means unspecified.
std::string resource_path
Resource path.
std::string filename
Optional input filename (--filename).
std::string path
Asset root; proc_args() defaults it to the executable directory.
int width
Viewport width in pixels (default: 1280).
int shader_index
Optional initial shader entry index.
Sprite-compatible fragment parameters for UV-based model effects.