MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
shaders.cpp
Go to the documentation of this file.
1#include "mxvk/argz.hpp"
2#include "mxvk/mxvk.hpp"
4#include "mxvk/mxvk_cv.hpp"
6#include <algorithm>
7#include <array>
8#include <cctype>
9#include <chrono>
10#include <cmath>
11#include <cstdlib>
12#include <filesystem>
13#include <format>
14#include <fstream>
15#include <iostream>
16#include <opencv2/videoio.hpp>
17#include <string>
18#include <vector>
19
20#include <glm/ext/matrix_clip_space.hpp>
21#include <glm/ext/matrix_transform.hpp>
22#include <glm/glm.hpp>
23
24#ifndef shader_viewer_ASSET_DIR
25#define shader_viewer_ASSET_DIR "."
26#endif
27
28#ifndef shader_viewer_SHADER_DIR
29#define shader_viewer_SHADER_DIR "."
30#endif
31
32#ifndef shader_viewer_SOURCE_DIR
33#define shader_viewer_SOURCE_DIR "."
34#endif
35
36namespace example {
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) {
40 ++begin;
41 }
42
43 auto end = text.end();
44 while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1))) != 0) {
45 --end;
46 }
47
48 return std::string(begin, end);
49 }
50
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();
55 }
56 return (std::filesystem::path(base) / file_path).string();
57 }
58
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") {
62 return joinPath(shader_path, entry);
63 }
64
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();
70 }
71
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();
77 }
78
79 return joinPath(shader_path, entry);
80 }
81
82 class ExampleWindow : public mxvk::VK_Window {
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;
90 double fps = 60.0f;
91 double requested_fps = 0.0;
92 int camera_index = 0;
93 bool using_file = false;
94 bool using_model = false;
95 bool shader_list_requested = false;
96 mxvk::VK_Capture capture{};
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()};
126
127 [[nodiscard]] bool openCaptureSource() {
128 if (using_file) {
129 return capture.open(input_filename);
130 }
131 return capture.open(camera_index);
132 }
133
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();
140 }
141 ec.clear();
142
143 const fs::path source_root = fs::path(shader_viewer_SOURCE_DIR).parent_path().parent_path();
144 const fs::path runtime_root = fs::path(shader_viewer_ASSET_DIR).parent_path().parent_path();
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(),
153 };
154
155 for (const fs::path &candidate : candidates) {
156 if (fs::exists(candidate, ec)) {
157 return fs::weakly_canonical(candidate, ec).string();
158 }
159 ec.clear();
160 }
161 throw mxvk::Exception(std::format("shader_viewer: failed to locate model '{}'", name));
162 }
163
164 [[nodiscard]] std::string resolveOptionalPath(const std::string &name) const {
165 if (name.empty()) {
166 return {};
167 }
168 namespace fs = std::filesystem;
169 const fs::path requested(name);
170 std::error_code ec{};
171 const fs::path source_root = fs::path(shader_viewer_SOURCE_DIR).parent_path().parent_path();
172 const fs::path candidates[] = {
173 requested,
174 fs::path(current_path) / requested,
175 fs::path(current_path) / "data" / requested,
176 source_root / requested,
177 source_root / "models" / requested,
178 };
179 for (const fs::path &candidate : candidates) {
180 if (fs::exists(candidate, ec)) {
181 return fs::weakly_canonical(candidate, ec).string();
182 }
183 ec.clear();
184 }
185 return name;
186 }
187
188 void loadShaderIndex() {
189 shader_files.clear();
190 current_shader_index = 0;
191
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));
197 }
198 return;
199 }
200
201 std::string line;
202 while (std::getline(input, line)) {
203 const std::string entry = trimLine(line);
204 if (entry.empty() || entry.front() == '#') {
205 continue;
206 }
207 const std::string shader_file = resolveShaderEntry(shader_path, entry);
208 if (std::filesystem::path(shader_file).extension() != ".spv") {
209 std::cerr << std::format("shader_viewer: skipping non-SPIR-V shader entry '{}'\n", entry);
210 continue;
211 }
212 shader_files.push_back(shader_file);
213 }
214
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));
217 }
218 }
219
220 void setInitialShaderIndex(int index) {
221 if (shader_files.empty()) {
222 current_shader_index = 0;
223 return;
224 }
225
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;
230 }
231 }
232
233 [[nodiscard]] std::string currentFragmentShader() const {
234 if (shader_files.empty()) {
235 return joinPath(shader_path, "fragment.frag.spv");
236 }
237 return shader_files[static_cast<std::size_t>(current_shader_index)];
238 }
239
240 void enableShaderUniforms() {
241 if (camera_sprite == nullptr) {
242 return;
243 }
244 camera_sprite->enableExtendedUBO();
245 }
246
247 void updateShaderUniforms(int target_w, int target_h) {
248 if (camera_sprite == nullptr) {
249 return;
250 }
251
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;
257
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);
264 }
265
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();
270 if (elapsed < 0.5) {
271 return;
272 }
273
274 current_capture_fps = static_cast<double>(capture_fps_frame_count) / elapsed;
275 capture_fps_frame_count = 0;
276 capture_fps_sample_time = now;
277 }
278
279 void cleanupWindowResources() {
280 if (getDevice() != VK_NULL_HANDLE) {
281 vkDeviceWaitIdle(getDevice());
282 }
283 camera_sprite = nullptr;
284 if (using_model) {
285 model.cleanup(this);
286 }
287 release();
288 if (capture.is_open()) {
289 capture.close();
290 }
291 }
292
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);
299 }
300 return (reportedFps > 0.0) ? reportedFps : requested_fps;
301 }
302
303 static constexpr std::array<double, 3> fpsChoices = {60.0, 30.0, 24.0};
304
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) {
309 return reportedFps;
310 }
311 }
312
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();
316 }
317
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;
324 }
325
326 const std::string vertex_shader;
327 const std::string fragment_shader = currentFragmentShader();
328
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);
334 }
335 return camera_sprite != nullptr;
336 }
337
338 camera_sprite->createEmptySprite(frame_width, frame_height, vertex_shader, fragment_shader);
339 enableShaderUniforms();
340 return true;
341 }
342
343 void selectShader(int direction) {
344 if (shader_files.empty()) {
345 return;
346 }
347
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;
352 }
353
354 if (getDevice() != VK_NULL_HANDLE) {
355 vkDeviceWaitIdle(getDevice());
356 }
357
358 if (using_model) {
359 model.setShaders(this, model_vertex_shader, currentFragmentShader());
360 } else if (!createOrRefreshCameraSprite()) {
361 throw mxvk::Exception("shader_viewer: failed to switch capture shader");
362 }
363
364 std::cout << std::format("shader_viewer: selected shader {} of {}: {}\n", current_shader_index + 1, shader_count, currentFragmentShader());
365 }
366
367 [[nodiscard]] bool uploadCaptureFrameToSprite() {
368 if (camera_sprite == nullptr) {
369 return false;
370 }
371
372 if (!capture.readToSprite(*camera_sprite, false)) {
373 return false;
374 }
375
376 updateCaptureFpsSample();
377 return true;
378 }
379
380 void initializeCameraRendering() {
381 if (swapchain == VK_NULL_HANDLE || command_pool == VK_NULL_HANDLE) {
382 createDevice();
383 }
384
385 if (!createOrRefreshCameraSprite()) {
386 throw mxvk::Exception("shader_viewer: failed to create capture sprite");
387 }
388
389 if (camera_sprite != nullptr && !uploadCaptureFrameToSprite()) {
390 std::cerr << "shader_viewer: failed to upload initial camera frame\n";
391 }
392 }
393
394 void updateFpsOverlay() {
395 ++fps_frame_count;
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;
400 fps_frame_count = 0;
401 fps_sample_time = now;
402 if (using_file) {
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);
406 } else {
407 fps_text = std::format("FPS: {:.1f} Capture: {:.1f}", current_fps, current_capture_fps);
408 }
409 }
410
411 if (!menu_visible)
412 return;
413
414 printText(fps_text, 15, 15, SDL_Color{255, 255, 255, 255});
415 if (using_model) {
416 printText(std::format("Model: {} Wire: {} Auto: {} M: Toggle Menu", std::filesystem::path(model_filename).filename().string(), wireframe ? "on" : "off", auto_rotate ? "on" : "off"), 15, 39, SDL_Color{220, 228, 240, 255});
417 if (show_help) {
418 printText("Drag/Left/Right rotate Wheel/A/S zoom Up/Down shader W wire R auto Home reset Esc quit", 15, static_cast<int>(getSwapchainExtent().height) - 30, SDL_Color{195, 205, 220, 255});
419 }
420 }
421 }
422
423 public:
424 ExampleWindow(const Arguments &args, const std::string &text) : mxvk::VK_Window(text, args.width, args.height, args.fullscreen, MXVK_VALIDATION, args.enable_vsync) {
425 try {
426 current_path = (args.path.empty() || args.path == ".") ? std::string(shader_viewer_ASSET_DIR) : args.path;
427 shader_path = args.shaderPath.empty() ? current_path + "/data" : args.shaderPath;
428 shader_list_requested = !args.shaderPath.empty();
429 loadShaderIndex();
430 setInitialShaderIndex(args.shader_index);
431 std::string font_path = joinPath(current_path, "data/font.ttf");
432 if (!std::filesystem::exists(font_path)) {
433 font_path = joinPath(shader_viewer_ASSET_DIR, "data/font.ttf");
434 }
435 setFont(font_path, 20);
436 input_filename = args.filename;
437 model_filename = args.model;
438 camera_index = args.camera_index;
439 requested_fps = args.fps;
440 using_file = !input_filename.empty();
441 using_model = !model_filename.empty();
442
443 fallback_width = args.width;
444 fallback_height = args.height;
445 if (using_model) {
446 model_filename = resolveModelPath(model_filename);
447 const std::string texture_manifest = resolveOptionalPath(args.texture);
448 const std::string texture_base = args.resource_path.empty() ? std::filesystem::path(texture_manifest.empty() ? model_filename : texture_manifest).parent_path().string() : resolveOptionalPath(args.resource_path);
449 model_vertex_shader = joinPath(current_path, "data/model.vert.spv");
450 if (!std::filesystem::exists(model_vertex_shader)) {
451 model_vertex_shader = joinPath(shader_viewer_ASSET_DIR, "data/model.vert.spv");
452 }
453 std::cout << std::format("shader_viewer: model='{}' fragment='{}'\n", model_filename, currentFragmentShader());
454 model.enableExtendedFragmentUniforms();
455 model.load(this, model_filename, texture_manifest, texture_base, 1.0f);
456 model.setShaders(this, model_vertex_shader, currentFragmentShader());
457 if (using_file) {
458 if (!openCaptureSource()) {
459 throw mxvk::Exception(std::format("shader_viewer: failed to open video file '{}'", input_filename));
460 }
461 fps = capture.get(cv::CAP_PROP_FPS);
462 if (!capture.readToModelTexture(model)) {
463 throw mxvk::Exception(std::format("shader_viewer: failed to upload the first video frame from '{}'", input_filename));
464 }
465 updateCaptureFpsSample();
466 std::cout << std::format("shader_viewer: video texture='{}' @ {:.1f} fps\n", input_filename, fps);
467 }
468 return;
469 }
470
471 if (!openCaptureSource()) {
472 if (using_file) {
473 throw mxvk::Exception(std::format("shader_viewer: failed to open video file '{}'", input_filename));
474 }
475 throw mxvk::Exception(std::format("shader_viewer: failed to open camera index {}", camera_index));
476 }
477 if (!using_file) {
478 capture.set(cv::CAP_PROP_FRAME_WIDTH, fallback_width);
479 capture.set(cv::CAP_PROP_FRAME_HEIGHT, fallback_height);
480 fallback_width = capture.get(cv::CAP_PROP_FRAME_WIDTH);
481 fallback_height = capture.get(cv::CAP_PROP_FRAME_HEIGHT);
482 fps = configureCameraFps();
483 } else {
484 fps = capture.get(cv::CAP_PROP_FPS);
485 }
486 std::cout << "mxvk_cv: Capture opened at: " << fallback_width << "x" << fallback_height << " @ " << fps << " fps\n";
487 initializeCameraRendering();
488 } catch (...) {
489 cleanupWindowResources();
490 throw;
491 }
492 }
493
494 ~ExampleWindow() override { cleanupWindowResources(); }
495
496 void event(SDL_Event &e) override {
497 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
498 exit();
499 } else if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_UP && !e.key.repeat) {
500 selectShader(-1);
501 } else if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_DOWN && !e.key.repeat) {
502 selectShader(1);
503 } else if (using_model && e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
504 switch (e.key.key) {
505 case SDLK_M:
506 menu_visible = !menu_visible;
507 break;
508 case SDLK_W:
509 wireframe = !wireframe;
510 break;
511 case SDLK_R:
512 case SDLK_P:
513 auto_rotate = !auto_rotate;
514 break;
515 case SDLK_H:
516 case SDLK_SPACE:
517 show_help = !show_help;
518 break;
519 case SDLK_HOME:
520 rotation_x_degrees = 0.0f;
521 rotation_y_degrees = 0.0f;
522 auto_rotation_radians = 0.0f;
523 camera_distance = 5.0f;
524 break;
525 case SDLK_EQUALS:
526 case SDLK_PLUS:
527 camera_distance = std::clamp(camera_distance - 0.5f, 0.4f, 100.0f);
528 break;
529 case SDLK_MINUS:
530 camera_distance = std::clamp(camera_distance + 0.5f, 0.4f, 100.0f);
531 break;
532 default:
533 break;
534 }
535 } else if (e.type == SDL_EVENT_MOUSE_MOTION) {
536 mouse_x = e.motion.x;
537 mouse_y = e.motion.y;
538 if (using_model && mouse_dragging) {
539 rotation_y_degrees += (e.motion.x - static_cast<float>(last_mouse_x)) * 0.5f;
540 rotation_x_degrees += (e.motion.y - static_cast<float>(last_mouse_y)) * 0.5f;
541 rotation_x_degrees = std::clamp(rotation_x_degrees, -89.0f, 89.0f);
542 last_mouse_x = static_cast<int>(e.motion.x);
543 last_mouse_y = static_cast<int>(e.motion.y);
544 }
545 } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
546 mouse_pressed = true;
547 mouse_x = e.button.x;
548 mouse_y = e.button.y;
549 mouse_dragging = using_model;
550 last_mouse_x = static_cast<int>(e.button.x);
551 last_mouse_y = static_cast<int>(e.button.y);
552 } else if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
553 mouse_pressed = false;
554 mouse_dragging = false;
555 mouse_x = e.button.x;
556 mouse_y = e.button.y;
557 } else if (using_model && e.type == SDL_EVENT_MOUSE_WHEEL) {
558 const float delta = (e.wheel.y != 0.0f) ? e.wheel.y : static_cast<float>(e.wheel.integer_y);
559 camera_distance = std::clamp(camera_distance - delta * 0.45f, 0.4f, 100.0f);
560 }
561 }
562
563 void onSwapchainRecreated() override {
564 if (using_model) {
565 model.resize(this);
566 } else {
567 initializeCameraRendering();
568 }
569 }
570
571 void proc() override {
572 if (using_model) {
573 if (using_file && !capture.readToModelTexture(model)) {
574 capture.close();
575 if (!openCaptureSource() || !capture.readToModelTexture(model)) {
576 std::cerr << "shader_viewer: failed to restart video texture stream\n";
577 }
578 }
579 if (using_file) {
580 updateCaptureFpsSample();
581 }
582
583 const auto now = std::chrono::steady_clock::now();
584 const float delta_seconds = std::clamp(std::chrono::duration<float>(now - last_model_update_time).count(), 0.0f, 0.1f);
585 last_model_update_time = now;
586 const bool *keys = SDL_GetKeyboardState(nullptr);
587 constexpr float ROTATE_SPEED = 120.0f;
588 if (keys != nullptr) {
589 if (keys[SDL_SCANCODE_LEFT]) {
590 rotation_y_degrees -= ROTATE_SPEED * delta_seconds;
591 }
592 if (keys[SDL_SCANCODE_RIGHT]) {
593 rotation_y_degrees += ROTATE_SPEED * delta_seconds;
594 }
595 if (keys[SDL_SCANCODE_A]) {
596 camera_distance = std::clamp(camera_distance - 2.5f * delta_seconds, 0.4f, 100.0f);
597 }
598 if (keys[SDL_SCANCODE_S]) {
599 camera_distance = std::clamp(camera_distance + 2.5f * delta_seconds, 0.4f, 100.0f);
600 }
601 }
602 if (auto_rotate) {
603 auto_rotation_radians = std::fmod(auto_rotation_radians + 0.55f * delta_seconds, 6.28318530718f);
604 }
605 updateFpsOverlay();
606 return;
607 }
608
609 if (!uploadCaptureFrameToSprite()) {
610 if (using_file) {
611 capture.close();
612 if (openCaptureSource()) {
613 if (!uploadCaptureFrameToSprite()) {
614 std::cerr << "shader_viewer: failed to upload restarted stream frame\n";
615 }
616 }
617 } else {
618 fps = configureCameraFps();
619 }
620 }
621
622 int target_w = fallback_width;
623 int target_h = fallback_height;
624 if (swapchain_extent.width > 0U && swapchain_extent.height > 0U) {
625 target_w = static_cast<int>(swapchain_extent.width);
626 target_h = static_cast<int>(swapchain_extent.height);
627 }
628
629 if (camera_sprite) {
630 updateShaderUniforms(target_w, target_h);
631 camera_sprite->drawSpriteRect(0, 0, target_w, target_h);
632 }
633 updateFpsOverlay();
634 }
635
636 void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override {
637 if (!using_model) {
638 return;
639 }
640
641 const VkExtent2D extent = getSwapchainExtent();
642 const float aspect = extent.height > 0U ? static_cast<float>(extent.width) / static_cast<float>(extent.height) : 1.0f;
643 const float elapsed_seconds = std::chrono::duration<float>(std::chrono::steady_clock::now() - shader_start_time).count();
644
646 ubo.model = glm::rotate(glm::mat4(1.0f), glm::radians(rotation_x_degrees), glm::vec3(1.0f, 0.0f, 0.0f));
647 ubo.model = glm::rotate(ubo.model, glm::radians(rotation_y_degrees) + auto_rotation_radians, glm::vec3(0.0f, 1.0f, 0.0f));
648 ubo.model = glm::scale(ubo.model, glm::vec3(model.modelRenderScale()));
649 ubo.model = glm::translate(ubo.model, model.modelCenterOffset());
650 ubo.view = glm::lookAt(glm::vec3(0.0f, 0.0f, camera_distance), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
651 ubo.proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
652 ubo.proj[1][1] *= -1.0f;
653 ubo.fx = glm::vec4(elapsed_seconds, wireframe ? 1.0f : 0.0f, 0.0f, 0.0f);
654 model.updateUBO(image_index, ubo);
655
656 const float delta_seconds = std::chrono::duration<float>(std::chrono::steady_clock::now() - previous_shader_frame_time).count();
657 previous_shader_frame_time = std::chrono::steady_clock::now();
658 ++shader_frame_count;
659 mxvk::ModelFragmentUniforms fragment_uniforms{};
660 fragment_uniforms.mouse = glm::vec4(mouse_x, mouse_y, mouse_pressed ? 1.0f : 0.0f, 0.0f);
661 fragment_uniforms.u0 = glm::vec4(1.0f, 1.0f, static_cast<float>(extent.width), static_cast<float>(extent.height));
662 fragment_uniforms.u1 = glm::vec4(delta_seconds, 0.0f, 0.0f, delta_seconds > 0.0f ? 1.0f / delta_seconds : 0.0f);
663 fragment_uniforms.u2 = glm::vec4(static_cast<float>(shader_frame_count), elapsed_seconds, 48000.0f, 0.0f);
664 model.updateFragmentUBO(image_index, fragment_uniforms);
665
666 mxvk::ModelFragmentPushConstants fragment_constants{};
667 fragment_constants.screenWidth = static_cast<float>(extent.width);
668 fragment_constants.screenHeight = static_cast<float>(extent.height);
669 fragment_constants.spriteSizeW = static_cast<float>(extent.width);
670 fragment_constants.spriteSizeH = static_cast<float>(extent.height);
671 fragment_constants.params = glm::vec4(1.0f, 1.0f, 1.0f, elapsed_seconds);
672 model.setFragmentPushConstants(fragment_constants);
673 if (using_file) {
674 model.renderWithPushConstants(cmd, image_index, 0, ubo, wireframe);
675 } else {
676 model.render(cmd, image_index, wireframe);
677 }
678 }
679 };
680} // namespace example
681
682int main(int argc, char **argv) {
683 try {
684 Arguments args = proc_args(argc, argv);
685 example::ExampleWindow ex_window(args, "Shader Viewer");
686 ex_window.loop();
687 } catch (mxvk::Exception &e) {
688 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
689 return EXIT_FAILURE;
690 } catch (ArgException<std::string> &e) {
691 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
692 }
693 return EXIT_SUCCESS;
694}
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
ExampleWindow(const Arguments &args, const std::string &text)
Definition shaders.cpp:424
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
Definition shaders.cpp:563
void event(SDL_Event &e) override
Handle one SDL event.
Definition shaders.cpp:496
void proc() override
Execute one processing/update step.
Definition shaders.cpp:571
~ExampleWindow() override
Definition shaders.cpp:494
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override
Optional hook for derived classes to record extra draw commands.
Definition shaders.cpp:636
std::string text() const
Vulkan OpenCV video capture source.
Definition mxvk_cv.hpp:30
bool open(const std::string &filename)
Open a video file.
Definition mxvk_cv.cpp:14
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:39
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:222
VkSwapchainKHR swapchain
Definition mxvk.hpp:613
void loop()
Run the main event/render loop.
Definition mxvk.cpp:651
VkDevice getDevice() const noexcept
Get the Vulkan logical device handle.
Definition mxvk.hpp:198
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
VkExtent2D swapchain_extent
Definition mxvk.hpp:616
void createDevice()
Create final device resources.
Definition mxvk.cpp:2028
std::string font_path
Definition mxvk.hpp:714
void exit()
Request loop termination.
Definition mxvk.cpp:1378
VkCommandPool command_pool
Definition mxvk.hpp:627
VK_Window()=default
Construct an empty window object.
void release()
Release Vulkan and SDL resources.
Definition mxvk.cpp:247
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
Definition mxvk.cpp:3872
void printText(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for rendering during the current frame.
Definition mxvk.cpp:3919
#define MXVK_VALIDATION
Definition mxvk.hpp:28
High-level model wrapper integrated with MXVK dynamic rendering.
OpenCV video-capture integration for the Vulkan backend.
std::string trimLine(const std::string &text)
Definition shaders.cpp:37
std::string joinPath(const std::string &base, const std::string &file)
Definition shaders.cpp:51
std::string resolveShaderEntry(const std::string &shader_path, const std::string &entry)
Definition shaders.cpp:59
int main()
Definition main.py:165
Definition model.py:1
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
#define shader_viewer_ASSET_DIR
Definition shaders.cpp:25
#define shader_viewer_SOURCE_DIR
Definition shaders.cpp:33
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:718
std::string shaderPath
Optional SPV shader folder path (-S / --shader-path).
Definition argz.hpp:753
int camera_index
Optional camera index.
Definition argz.hpp:755
std::string model
Optional model filename (--model).
Definition argz.hpp:727
std::string texture
Optional texture file path (--texture).
Definition argz.hpp:752
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:721
double fps
Optional FPS override (--fps); non-positive means unspecified.
Definition argz.hpp:748
std::string resource_path
Resource path.
Definition argz.hpp:759
std::string filename
Optional input filename (--filename).
Definition argz.hpp:726
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 shader_index
Optional initial shader entry index.
Definition argz.hpp:757
Sprite-compatible fragment parameters for UV-based model effects.
Extended shader-viewer uniforms available to fragment shaders at binding 1.
Default transform UBO payload for model shaders.