MXVK Vulkan Framework 0.24.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"),
417 15, 39, SDL_Color{220, 228, 240, 255});
418 if (show_help) {
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});
421 }
422 }
423 }
424
425 public:
426 ExampleWindow(const Arguments &args, const std::string &text) : mxvk::VK_Window(text, args.width, args.height, args.fullscreen, MXVK_VALIDATION, args.enable_vsync) {
427 try {
428 current_path = (args.path.empty() || args.path == ".") ? std::string(shader_viewer_ASSET_DIR) : args.path;
429 shader_path = args.shaderPath.empty() ? current_path + "/data" : args.shaderPath;
430 shader_list_requested = !args.shaderPath.empty();
431 loadShaderIndex();
432 setInitialShaderIndex(args.shader_index);
433 std::string font_path = joinPath(current_path, "data/font.ttf");
434 if (!std::filesystem::exists(font_path)) {
435 font_path = joinPath(shader_viewer_ASSET_DIR, "data/font.ttf");
436 }
437 setFont(font_path, 20);
438 input_filename = args.filename;
439 model_filename = args.model;
440 camera_index = args.camera_index;
441 requested_fps = args.fps;
442 using_file = !input_filename.empty();
443 using_model = !model_filename.empty();
444
445 fallback_width = args.width;
446 fallback_height = args.height;
447 if (using_model) {
448 model_filename = resolveModelPath(model_filename);
449 const std::string texture_manifest = resolveOptionalPath(args.texture);
450 const std::string texture_base = args.resource_path.empty()
451 ? std::filesystem::path(texture_manifest.empty() ? model_filename : texture_manifest).parent_path().string()
452 : resolveOptionalPath(args.resource_path);
453 model_vertex_shader = joinPath(current_path, "data/model.vert.spv");
454 if (!std::filesystem::exists(model_vertex_shader)) {
455 model_vertex_shader = joinPath(shader_viewer_ASSET_DIR, "data/model.vert.spv");
456 }
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());
461 if (using_file) {
462 if (!openCaptureSource()) {
463 throw mxvk::Exception(std::format("shader_viewer: failed to open video file '{}'", input_filename));
464 }
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));
468 }
469 updateCaptureFpsSample();
470 std::cout << std::format("shader_viewer: video texture='{}' @ {:.1f} fps\n", input_filename, fps);
471 }
472 return;
473 }
474
475 if (!openCaptureSource()) {
476 if (using_file) {
477 throw mxvk::Exception(std::format("shader_viewer: failed to open video file '{}'", input_filename));
478 }
479 throw mxvk::Exception(std::format("shader_viewer: failed to open camera index {}", camera_index));
480 }
481 if (!using_file) {
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();
487 } else {
488 fps = capture.get(cv::CAP_PROP_FPS);
489 }
490 std::cout << "mxvk_cv: Capture opened at: " << fallback_width << "x" << fallback_height << " @ " << fps << " fps\n";
491 initializeCameraRendering();
492 } catch (...) {
493 cleanupWindowResources();
494 throw;
495 }
496 }
497
498 ~ExampleWindow() override {
499 cleanupWindowResources();
500 }
501
502 void event(SDL_Event &e) override {
503 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
504 exit();
505 } else if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_UP && !e.key.repeat) {
506 selectShader(-1);
507 } else if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_DOWN && !e.key.repeat) {
508 selectShader(1);
509 } else if (using_model && e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
510 switch (e.key.key) {
511 case SDLK_M:
512 menu_visible = !menu_visible;
513 break;
514 case SDLK_W:
515 wireframe = !wireframe;
516 break;
517 case SDLK_R:
518 case SDLK_P:
519 auto_rotate = !auto_rotate;
520 break;
521 case SDLK_H:
522 case SDLK_SPACE:
523 show_help = !show_help;
524 break;
525 case SDLK_HOME:
526 rotation_x_degrees = 0.0f;
527 rotation_y_degrees = 0.0f;
528 auto_rotation_radians = 0.0f;
529 camera_distance = 5.0f;
530 break;
531 case SDLK_EQUALS:
532 case SDLK_PLUS:
533 camera_distance = std::clamp(camera_distance - 0.5f, 0.4f, 100.0f);
534 break;
535 case SDLK_MINUS:
536 camera_distance = std::clamp(camera_distance + 0.5f, 0.4f, 100.0f);
537 break;
538 default:
539 break;
540 }
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);
550 }
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);
566 }
567 }
568
569 void onSwapchainRecreated() override {
570 if (using_model) {
571 model.resize(this);
572 } else {
573 initializeCameraRendering();
574 }
575 }
576
577 void proc() override {
578 if (using_model) {
579 if (using_file && !capture.readToModelTexture(model)) {
580 capture.close();
581 if (!openCaptureSource() || !capture.readToModelTexture(model)) {
582 std::cerr << "shader_viewer: failed to restart video texture stream\n";
583 }
584 }
585 if (using_file) {
586 updateCaptureFpsSample();
587 }
588
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;
597 }
598 if (keys[SDL_SCANCODE_RIGHT]) {
599 rotation_y_degrees += ROTATE_SPEED * delta_seconds;
600 }
601 if (keys[SDL_SCANCODE_A]) {
602 camera_distance = std::clamp(camera_distance - 2.5f * delta_seconds, 0.4f, 100.0f);
603 }
604 if (keys[SDL_SCANCODE_S]) {
605 camera_distance = std::clamp(camera_distance + 2.5f * delta_seconds, 0.4f, 100.0f);
606 }
607 }
608 if (auto_rotate) {
609 auto_rotation_radians = std::fmod(auto_rotation_radians + 0.55f * delta_seconds, 6.28318530718f);
610 }
611 updateFpsOverlay();
612 return;
613 }
614
615 if (!uploadCaptureFrameToSprite()) {
616 if (using_file) {
617 capture.close();
618 if (openCaptureSource()) {
619 if (!uploadCaptureFrameToSprite()) {
620 std::cerr << "shader_viewer: failed to upload restarted stream frame\n";
621 }
622 }
623 } else {
624 fps = configureCameraFps();
625 }
626 }
627
628 int target_w = fallback_width;
629 int target_h = fallback_height;
630 if (swapchain_extent.width > 0U && swapchain_extent.height > 0U) {
631 target_w = static_cast<int>(swapchain_extent.width);
632 target_h = static_cast<int>(swapchain_extent.height);
633 }
634
635 if (camera_sprite) {
636 updateShaderUniforms(target_w, target_h);
637 camera_sprite->drawSpriteRect(0, 0, target_w, target_h);
638 }
639 updateFpsOverlay();
640 }
641
642 void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override {
643 if (!using_model) {
644 return;
645 }
646
647 const VkExtent2D extent = getSwapchainExtent();
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();
650
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);
661
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;
665 mxvk::ModelFragmentUniforms fragment_uniforms{};
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);
671
672 mxvk::ModelFragmentPushConstants fragment_constants{};
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);
679 if (using_file) {
680 model.renderWithPushConstants(cmd, image_index, 0, ubo, wireframe);
681 } else {
682 model.render(cmd, image_index, wireframe);
683 }
684 }
685 };
686} // namespace example
687
688int main(int argc, char **argv) {
689 try {
690 Arguments args = proc_args(argc, argv);
691 example::ExampleWindow ex_window(args, "Shader Viewer");
692 ex_window.loop();
693 } catch (mxvk::Exception &e) {
694 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
695 return EXIT_FAILURE;
696 } catch (ArgException<std::string> &e) {
697 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
698 }
699 return EXIT_SUCCESS;
700}
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
ExampleWindow(const Arguments &args, const std::string &text)
Definition shaders.cpp:426
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
Definition shaders.cpp:569
void event(SDL_Event &e) override
Handle one SDL event.
Definition shaders.cpp:502
void proc() override
Execute one processing/update step.
Definition shaders.cpp:577
~ExampleWindow() override
Definition shaders.cpp:498
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override
Optional hook for derived classes to record extra draw commands.
Definition shaders.cpp:642
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:37
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:186
VkSwapchainKHR swapchain
Definition mxvk.hpp:492
void loop()
Run the main event/render loop.
Definition mxvk.cpp:600
VkDevice getDevice() const noexcept
Get the Vulkan logical device handle.
Definition mxvk.hpp:168
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
VkExtent2D swapchain_extent
Definition mxvk.hpp:495
void createDevice()
Create final device resources.
Definition mxvk.cpp:1645
std::string font_path
Definition mxvk.hpp:568
void exit()
Request loop termination.
Definition mxvk.cpp:1126
VkCommandPool command_pool
Definition mxvk.hpp:504
VK_Window()=default
Construct an empty window object.
void release()
Release Vulkan and SDL resources.
Definition mxvk.cpp:218
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
Definition mxvk.cpp:2991
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:3018
int main(void)
Definition main.cpp:7
#define MXVK_VALIDATION
Definition mxvk.hpp:27
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
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30
#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:730
std::string shaderPath
Optional SPV shader folder path (-S / --shader-path).
Definition argz.hpp:765
int camera_index
Optional camera index.
Definition argz.hpp:767
std::string model
Optional model filename (--model).
Definition argz.hpp:739
std::string texture
Optional texture file path (--texture).
Definition argz.hpp:764
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:733
double fps
Optional FPS override (--fps); non-positive means unspecified.
Definition argz.hpp:760
std::string resource_path
Resource path.
Definition argz.hpp:771
std::string filename
Optional input filename (--filename).
Definition argz.hpp:738
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 shader_index
Optional initial shader entry index.
Definition argz.hpp:769
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.