UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
vulkan_sdl2.cpp
Go to the documentation of this file.
1
8 /*
9 * Copyright (c) 2018-2023 CESNET, z. s. p. o.
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, is permitted provided that the following conditions
14 * are met:
15 *
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 *
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 *
23 * 3. Neither the name of CESNET nor the names of its contributors may be
24 * used to endorse or promote products derived from this software without
25 * specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
28 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
29 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
30 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
31 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
32 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
33 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
34 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
36 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
37 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
38 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 */
47#ifdef HAVE_CONFIG_H
48#include "config.h"
49#include "config_unix.h"
50#include "config_win32.h"
51#endif // HAVE_CONFIG_H
52
53#include "debug.h"
54#include "host.h"
55#include "keyboard_control.h" // K_*
56#include "lib_common.h"
57#include "messaging.h"
58#include "module.h"
59#include "utils/color_out.h"
60#include "video_display.h"
62#include "video.h"
63#include "utils/fs.h"
64//remove leaking macros
65#undef min
66#undef max
67
68#ifdef __MINGW32__
69#define VK_USE_PLATFORM_WIN32_KHR
70#endif
71
72#include "vulkan_display.hpp" // vulkan.h must be before GLFW/SDL
73
74// @todo remove the defines when no longer needed
75#ifdef __arm64__
76#define SDL_DISABLE_MMINTRIN_H 1
77#define SDL_DISABLE_IMMINTRIN_H 1
78#endif // defined __arm64__
79
80#include <SDL2/SDL.h>
81#include <SDL2/SDL_vulkan.h>
82
83#ifdef __MINGW32__
84#include <SDL2/SDL_syswm.h>
85#endif
86
87#include <algorithm>
88#include <array>
89#include <atomic>
90#include <cassert>
91#include <condition_variable>
92#include <cstdint>
93#include <cstring>
94#include <limits>
95#include <mutex>
96#include <memory>
97#include <queue>
98#include <string>
99#include <string_view>
100#include <type_traits>
101#include <unordered_map>
102#include <utility> // pair
103
104namespace {
105
106namespace vkd = vulkan_display;
107namespace chrono = std::chrono;
108using namespace std::literals;
109
110constexpr int magic_vulkan_sdl2 = 0x3cc234a2;
111constexpr int initial_frame_count = 0;
112#define MOD_NAME "[VULKAN_SDL2] "
113
114
115void display_vulkan_new_message(module*);
116video_frame* display_vulkan_getf(void* state);
117
118class WindowCallback final : public vkd::WindowChangedCallback {
119 SDL_Window* window = nullptr;
120public:
121 explicit WindowCallback(SDL_Window* window):
122 window{window} { }
123
124 vkd::WindowParameters get_window_parameters() override {
125 assert(window);
126 int width = 0;
127 int height = 0;
128 SDL_Vulkan_GetDrawableSize(window, &width, &height);
129 if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) {
130 width = 0;
131 height = 0;
132 }
133 return { static_cast<uint32_t>(width), static_cast<uint32_t>(height) };
134 }
135};
136
137struct FrameMappings{
138 struct Mapping{
140 vkd::TransferImage image;
141 };
142
143 std::vector<Mapping> mappings;
144
145 video_frame* create_frame(vkd::TransferImage image){
146 for(auto& pair : mappings){
147 if(pair.image == nullptr){
148 pair.image = image;
149 return pair.frame;
150 }
151 }
152 auto new_frame = vf_alloc(1);
153 mappings.emplace_back(Mapping{new_frame, image});
154 return new_frame;
155 }
156
158 for(auto& pair : mappings){
159 if(pair.frame == frame){
160 return pair.image;
161 }
162 }
163 assert(false);
164 return {};
165 }
166
167 ~FrameMappings(){
168 for(auto& pair : mappings){
169 vf_free(pair.frame);
170 }
171 }
172};
173
174struct state_vulkan_sdl2 {
175 module mod{};
176
177 Uint32 sdl_user_new_message_event;
178
179 chrono::steady_clock::time_point time{};
180 uint64_t frames = 0;
181
182 bool deinterlace = false;
183 bool fullscreen = false;
184 bool keep_aspect = false;
185
186 int width = 0;
187 int height = 0;
188
189 SDL_Window* window = nullptr;
190
191 // Use raw pointers because std::unique_ptr might not have standard layout
192 vkd::VulkanDisplay* vulkan = nullptr;
193 WindowCallback* window_callback = nullptr;
194 FrameMappings* frame_mappings = new FrameMappings();
195
196 std::atomic<bool> should_exit = false;
197 video_desc current_desc{};
198
199 explicit state_vulkan_sdl2(module* parent) {
201 mod.priv_magic = magic_vulkan_sdl2;
202 mod.new_message = display_vulkan_new_message;
203 mod.cls = MODULE_CLASS_DATA;
204 module_register(&mod, parent);
205
206 sdl_user_new_message_event = SDL_RegisterEvents(1);
207 assert(sdl_user_new_message_event != static_cast<Uint32>(-1));
208 }
209
210 state_vulkan_sdl2(const state_vulkan_sdl2& other) = delete;
211 state_vulkan_sdl2& operator=(const state_vulkan_sdl2& other) = delete;
212 state_vulkan_sdl2(state_vulkan_sdl2&& other) = delete;
213 state_vulkan_sdl2& operator=(state_vulkan_sdl2&& other) = delete;
214
215 ~state_vulkan_sdl2() {
216 delete frame_mappings;
217 module_done(&mod);
218 }
219};
220
221// make sure that state_vulkan_sdl2 is C compatible
222static_assert(std::is_standard_layout_v<state_vulkan_sdl2>);
223
224//todo C++20 : change to to_array
225constexpr std::array<std::pair<char, std::string_view>, 3> display_vulkan_keybindings{{
226 {'d', "toggle deinterlace"},
227 {'f', "toggle fullscreen"},
228 {'q', "quit"}
229}};
230
231constexpr void update_description(const video_desc& video_desc, video_frame& frame) {
236 for (unsigned i = 0; i < video_desc.tile_count; i++) {
239 }
240}
241
242constexpr int64_t translate_sdl_key_to_ug(SDL_Keysym sym) {
243 sym.mod &= ~(KMOD_NUM | KMOD_CAPS); // remove num+caps lock modifiers
244
245 // ctrl alone -> do not interpret
246 if (sym.sym == SDLK_LCTRL || sym.sym == SDLK_RCTRL) {
247 return 0;
248 }
249
250 bool ctrl = false;
251 bool shift = false;
252 if (sym.mod & KMOD_CTRL) {
253 ctrl = true;
254 }
255 sym.mod &= ~KMOD_CTRL;
256
257 if (sym.mod & KMOD_SHIFT) {
258 shift = true;
259 }
260 sym.mod &= ~KMOD_SHIFT;
261
262 if (sym.mod != 0) {
263 return -1;
264 }
265
266 if ((sym.sym & SDLK_SCANCODE_MASK) == 0) {
267 if (shift) {
268 sym.sym = toupper(sym.sym);
269 }
270 return ctrl ? K_CTRL(sym.sym) : sym.sym;
271 }
272 switch (sym.sym) {
273 case SDLK_RIGHT: return K_RIGHT;
274 case SDLK_LEFT: return K_LEFT;
275 case SDLK_DOWN: return K_DOWN;
276 case SDLK_UP: return K_UP;
277 case SDLK_PAGEDOWN: return K_PGDOWN;
278 case SDLK_PAGEUP: return K_PGUP;
279 }
280 return -1;
281}
282
283constexpr bool display_vulkan_process_key(state_vulkan_sdl2& s, int64_t key) {
284 switch (key) {
285 case 'd':
286 s.deinterlace = !s.deinterlace;
287 log_msg(LOG_LEVEL_INFO, "Deinterlacing: %s\n",
288 s.deinterlace ? "ON" : "OFF");
289 return true;
290 case 'f': {
291 s.fullscreen = !s.fullscreen;
292 int mouse_x = 0, mouse_y = 0;
293 SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
294 SDL_SetWindowFullscreen(s.window, s.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
295 SDL_WarpMouseGlobal(mouse_x, mouse_y);
296 return true;
297 }
298 case 'q':
299 exit_uv(0);
300 return true;
301 default:
302 return false;
303 }
304}
305
306void log_and_exit_uv(std::exception& e) {
307 LOG(LOG_LEVEL_ERROR) << MOD_NAME << e.what() << std::endl;
308 exit_uv(EXIT_FAILURE);
309}
310
311void process_user_messages(state_vulkan_sdl2& s) {
312 msg_universal* msg = nullptr;
313 while ((msg = reinterpret_cast<msg_universal*>(check_message(&s.mod)))) {
314 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Received message: %s\n", msg->text);
315 response* r = nullptr;
316 int key;
317 if (strstr(msg->text, "win-title ") == msg->text) {
318 SDL_SetWindowTitle(s.window, msg->text + strlen("win-title "));
319 r = new_response(RESPONSE_OK, NULL);
320 } else if (sscanf(msg->text, "%d", &key) == 1) {
321 if (!display_vulkan_process_key(s, key)) {
322 r = new_response(RESPONSE_BAD_REQUEST, "Unsupported key for SDL");
323 } else {
324 r = new_response(RESPONSE_OK, NULL);
325 }
326 } else {
327 r = new_response(RESPONSE_BAD_REQUEST, "Wrong command");
328 }
329 free_message(reinterpret_cast<message*>(msg), r);
330 }
331}
332
333void process_events(state_vulkan_sdl2& s) {
334 SDL_Event sdl_event;
335 while (SDL_PollEvent(&sdl_event)) {
336 if (sdl_event.type == s.sdl_user_new_message_event) {
337 process_user_messages(s);
338 } else if (sdl_event.type == SDL_KEYDOWN && sdl_event.key.repeat == 0) {
339 auto& keysym = sdl_event.key.keysym;
341 MOD_NAME "Pressed key %s (scancode: %d, sym: %d, mod: %d)!\n",
342 SDL_GetKeyName(keysym.sym),
343 keysym.scancode,
344 keysym.sym,
345 keysym.mod);
346 int64_t sym = translate_sdl_key_to_ug(keysym);
347 if (sym > 0) {
348 if (!display_vulkan_process_key(s, sym)) {
349 // unknown key -> pass to control
351 }
352 } else if (sym == -1) {
354 MOD_NAME "Cannot translate key %s (scancode: %d, sym: %d, mod: %d)!\n",
355 SDL_GetKeyName(keysym.sym),
356 keysym.scancode,
357 keysym.sym,
358 keysym.mod);
359 }
360
361 } else if (sdl_event.type == SDL_WINDOWEVENT) {
362 // https://forums.libsdl.org/viewtopic.php?p=38342
363 if (s.keep_aspect && sdl_event.window.event == SDL_WINDOWEVENT_RESIZED) {
364 int old_width = s.width, old_height = s.height;
365 auto width = sdl_event.window.data1;
366 auto height = sdl_event.window.data2;
367 if (old_width == width) {
368 width = old_width * height / old_height;
369 } else if (old_height == height) {
370 height = old_height * width / old_width;
371 } else {
372 auto area = int64_t{ width } * height;
373 width = sqrt(area * old_width / old_height);
374 height = sqrt(area * old_height / old_width);
375 }
376 s.width = width;
377 s.height = height;
378 SDL_SetWindowSize(s.window, width, height);
379 debug_msg(MOD_NAME "resizing to %d x %d\n", width, height);
380 } else if (sdl_event.window.event == SDL_WINDOWEVENT_EXPOSED
381 || sdl_event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
382 {
383 s.vulkan->window_parameters_changed();
384 }
385
386 } else if (sdl_event.type == SDL_QUIT) {
387 exit_uv(0);
388 }
389 }
390}
391
392void display_vulkan_run(void* state) {
393 auto* s = static_cast<state_vulkan_sdl2*>(state);
394 assert(s->mod.priv_magic == magic_vulkan_sdl2);
395
396 s->time = chrono::steady_clock::now();
397 while (!s->should_exit) {
398 process_events(*s);
399 try {
400 bool displayed = s->vulkan->display_queued_image();
401 if (displayed) {
402 s->frames++;
403 }
404 }
405 catch (std::exception& e) { log_and_exit_uv(e); break; }
406 auto now = chrono::steady_clock::now();
407 double seconds = chrono::duration<double>{ now - s->time }.count();
408 if (seconds > 5) {
409 double fps = s->frames / seconds;
410 log_msg(LOG_LEVEL_INFO, MOD_NAME "%llu frames in %g seconds = %g FPS\n",
411 static_cast<long long unsigned>(s->frames), seconds, fps);
412 s->time = now;
413 s->frames = 0;
414 }
415 }
416 SDL_HideWindow(s->window);
417
418}
419
420void sdl2_print_displays() {
421 for (int i = 0; i < SDL_GetNumVideoDisplays(); ++i) {
422 if (i > 0) {
423 std::cout << ", ";
424 }
425 const char* dname = SDL_GetDisplayName(i);
426 if (dname == nullptr) {
427 dname = SDL_GetError();
428 }
429 col() << SBOLD(i) << " - " << dname;
430 }
431 std::cout << "\n";
432}
433
434void print_gpus() {
436 std::vector<const char*>required_extensions{};
437 std::vector<std::pair<std::string, bool>> gpus{};
438 try {
439 instance.init(required_extensions, false);
440 instance.get_available_gpus(gpus);
441 }
442 catch (std::exception& e) { log_and_exit_uv(e); return; }
443
444 col() << "\n\tVulkan GPUs:\n";
445 uint32_t counter = 0;
446 for (const auto& gpu : gpus) {
447 col() << (gpu.second ? "" : TERM_FG_RED);
448 col() << "\t\t " << counter++ << "\t - " << gpu.first;
449 col() << (gpu.second ? "" : " - unsuitable") << TERM_RESET << '\n';
450 }
451}
452
453void show_help() {
454 SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
455
456 auto print_drivers = []() {
457 for (int i = 0; i < SDL_GetNumVideoDrivers(); ++i) {
458 col() << (i == 0 ? "" : ", ") << SBOLD(SDL_GetVideoDriver(i));
459 }
460 col() << '\n';
461 };
462
463 col() << "VULKAN_SDL2 options:\n";
464 col() << SBOLD(SRED("\t-d vulkan_sdl2")
465 << "[:d|:fs|:keep-aspect|:nocursor|:nodecorate|:novsync|:tearing|:validation|:display=<dis_id>|"
466 ":driver=<drv>|:gpu=<gpu_id>|:pos=<x>,<y>|:size=<W>x<H>|:window_flags=<f>|:help])") << "\n";
467
468 col() << ("\twhere:\n");
469
470 col() << SBOLD("\t d") << " - deinterlace\n";
471 col() << SBOLD("\t fs") << " - fullscreen\n";
472
473 col() << SBOLD("\t keep-aspect") << " - keep window aspect ratio respecive to the video\n";
474 col() << SBOLD("\t nocursor") << " - hides cursor\n";
475 col() << SBOLD("\t nodecorate") << " - disable window border\n";
476 col() << SBOLD("\t novsync") << " - disable vsync\n";
477 col() << SBOLD("\t tearing") << " - permits screen tearing\n";
478 col() << SBOLD("\t validation") << " - enable vulkan validation layers\n";
479
480 col() << SBOLD("\tdisplay=<dis_id>") << " - display index, available indices: ";
482 col() << SBOLD("\t driver=<drv>") << " - available drivers: ";
483 print_drivers();
484 col() << SBOLD("\t gpu=<gpu_id>") << " - gpu index selected from the following list or keywords " << SBOLD("integrated") " or " << SBOLD("discrete") "\n";
485 col() << SBOLD("\t pos=<x>,<y>") << " - set window position\n";
486 col() << SBOLD("\t size=<W>x<H>") << " - set window size\n";
487 col() << SBOLD("\twindow_flags=<f>") << " - flags to be passed to SDL_CreateWindow (use prefix 0x for hex)\n";
488
489 print_gpus();
490
491 col() << "\n\tKeyboard shortcuts:\n";
492 for (auto& binding : display_vulkan_keybindings) {
493 col() << SBOLD("\t\t'" << binding.first) << "'\t - " << binding.second << "\n";
494 }
495 SDL_Quit();
496}
497
498
499struct CodecToVulkanFormat{
501 vulkan_display::Format vulkan_format;
502};
503
504
505// Ultragrid to VulkanDisplay Format mapping
506const std::vector<CodecToVulkanFormat>& get_ug_to_vkd_format_mapping(state_vulkan_sdl2& s){
507 //the backup vkd::Format must follow the corrresponding native vkd::Format
508 constexpr std::array<CodecToVulkanFormat, 10> format_mapping {{
509 {RGBA, vkd::Format::RGBA8},
510 {RGB, vkd::Format::RGB8},
511 {UYVY, vkd::Format::UYVY8_422},
512 {UYVY, vkd::Format::UYVY8_422_conv},
513 {YUYV, vkd::Format::YUYV8_422},
514 {Y216, vkd::Format::YUYV16_422},
515 {Y416, vkd::Format::UYVA16_422_conv},
516 {R10k, vkd::Format::RGB10A2_conv},
517 {RG48, vkd::Format::RGB16},
518 }};
519
520 static std::vector<CodecToVulkanFormat> supported_formats_mapping{};
521 if(!supported_formats_mapping.empty()){
522 return supported_formats_mapping;
523 }
524
525 for(auto& pair: format_mapping){
526 try {
527 if (!supported_formats_mapping.empty() && supported_formats_mapping.back().ug_codec == pair.ug_codec){
528 continue;
529 }
530 bool format_supported = s.vulkan->is_image_description_supported({1920, 1080, pair.vulkan_format });
531 if (format_supported) {
532 supported_formats_mapping.push_back(pair);
533 }
534 }
535 catch (std::exception& e) { log_and_exit_uv(e); }
536 }
537 return supported_formats_mapping;
538}
539
540
541vkd::ImageDescription to_vkd_image_desc(const video_desc& ultragrid_desc, state_vulkan_sdl2& s) {
542 auto& mapping = get_ug_to_vkd_format_mapping(s);
543 codec_t searched_codec = ultragrid_desc.color_spec;
544 auto iter = std::find_if(mapping.begin(), mapping.end(),
545 [searched_codec](auto pair) { return pair.ug_codec == searched_codec; });
546 assert(iter != mapping.end());
547 return { ultragrid_desc.width, ultragrid_desc.height, iter->vulkan_format };
548}
549
550
551bool display_vulkan_reconfigure(void* state, video_desc desc) {
552 auto* s = static_cast<state_vulkan_sdl2*>(state);
553 assert(s->mod.priv_magic == magic_vulkan_sdl2);
554
555 assert(desc.tile_count == 1);
556 s->current_desc = desc;
557 if (!s->vulkan->is_image_description_supported(to_vkd_image_desc(desc, *s))){
559 MOD_NAME "Video description is not supported - frames are too big or format isn't supported.");
560 return false;
561 }
562 return true;
563}
564
570void draw_splashscreen(state_vulkan_sdl2& s) {
571 vkd::TransferImage image;
572 try {
573 image = s.vulkan->acquire_image({splash_width, splash_height, vkd::Format::RGBA8});
574 }
575 catch (std::exception& e) { log_and_exit_uv(e); return; }
576 const char* source = splash_data;
577 char* dest = reinterpret_cast<char*>(image.get_memory_ptr());
578 auto padding = image.get_row_pitch() - splash_width * 4;
579
580 for (unsigned row = 0; row < splash_height; row++) {
581 for (unsigned int col = 0; col < splash_width; col++) {
582 HEADER_PIXEL(source, dest);
583 dest += 4;
584 }
585 dest += padding;
586 }
587 try {
588 s.vulkan->queue_image(image, true);
589 s.vulkan->display_queued_image();
590 }
591 catch (std::exception& e) { log_and_exit_uv(e); }
592}
593
594// todo C++20: replace with member function
595constexpr bool starts_with(std::string_view str, std::string_view match){
596 return str.rfind(match, /*check only 0-th pos*/ 0) == 0;
597};
598
599struct command_line_arguments {
600 bool cursor = true;
601 bool help = false;
602 bool vsync = true;
603 bool tearing_permitted = false;
604 bool validation = false;
605
606 int display_idx = 0;
607 int x = SDL_WINDOWPOS_UNDEFINED;
608 int y = SDL_WINDOWPOS_UNDEFINED;
609
610 uint32_t window_flags = 0 ;
611 uint32_t gpu_idx = vkd::no_gpu_selected;
612 std::string driver{};
613};
614
615bool parse_command_line_arguments(command_line_arguments& args, state_vulkan_sdl2& s, std::string_view arguments_sv) {
616 constexpr auto npos = std::string_view::npos;
617 constexpr std::string_view wrong_option_msg = MOD_NAME "Wrong option: ";
618
619 // todo C++20: replace with std::views::split(options, ":")
620 auto next_token = [](std::string_view & options) -> std::string_view {
621 auto colon_pos = options.find(':');
622 auto token = options.substr(0, colon_pos);
623 options.remove_prefix(colon_pos == npos ? options.size() : colon_pos + 1);
624 return token;
625 };
626
627 while (!arguments_sv.empty()) try {
628 const std::string_view token = next_token(arguments_sv);
629 if (token.empty()) {
630 continue;
631 }
632
633 //svtoi = string_view to int
634 auto svtoi = [token](std::string_view str) -> int {
635 std::size_t endpos = 0;
636 int result = std::stoi(std::string(str), &endpos, 0);
637 if (endpos != str.size()) {
638 throw std::runtime_error{ std::string(token) };
639 }
640 return result;
641 };
642
643 if (token == "d") {
644 s.deinterlace = true;
645 } else if (token == "fs") {
646 s.fullscreen = true;
647 } else if (token == "keep-aspect") {
648 s.keep_aspect = true;
649 } else if (token == "nocursor") {
650 args.cursor = false;
651 } else if (token == "nodecorate") {
652 args.window_flags |= SDL_WINDOW_BORDERLESS;
653 } else if (token == "novsync") {
654 args.vsync = false;
655 } else if (token == "tearing") {
656 args.tearing_permitted = true;
657 } else if (token == "validation") {
658 args.validation = true;
659 } else if (starts_with(token, "display=")) {
660 constexpr auto pos = "display="sv.size();
661 args.display_idx = svtoi(token.substr(pos));
662 } else if (starts_with(token, "driver=")) {
663 constexpr auto pos = "driver="sv.size();
664 args.driver = std::string{ token.substr(pos) };
665 } else if (starts_with(token, "gpu=")) {
666 if (token == "integrated"sv) {
667 args.gpu_idx = vulkan_display::gpu_integrated;
668 } else if (token == "discrete"sv) {
669 args.gpu_idx = vulkan_display::gpu_discrete;
670 } else {
671 constexpr auto pos = "gpu="sv.size();
672 args.gpu_idx = svtoi(token.substr(pos));
673 }
674 } else if (starts_with(token, "pos=")) {
675 auto tok = token;
676 tok.remove_prefix("pos="sv.size());
677 auto comma = tok.find(',');
678 if (comma == npos) {
679 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Missing colon in option:"
680 << token << '\n';
681 return false;
682 }
683 args.x = svtoi(tok.substr(0, comma));
684 args.y = svtoi(tok.substr(comma + 1));
685 } else if (starts_with(token, "size=")) {
686 auto tok = token;
687 tok.remove_prefix("size="sv.size());
688 auto x = tok.find('x');
689 if (x == npos) {
690 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Missing deliminer 'x' in option:"
691 << token << '\n';
692 return false;
693 }
694 s.width = svtoi(tok.substr(0, x));
695 s.height = svtoi(tok.substr(x + 1));
696 } else if (starts_with(token, "window_flags=")) {
697 constexpr auto pos = "window_flags="sv.size();
698 int flags = svtoi(token.substr(pos));
699 args.window_flags |= flags;
700 } else if (token == "help") {
701 show_help();
702 args.help = true;
703 } else {
704 LOG(LOG_LEVEL_ERROR) << wrong_option_msg << token << '\n';
705 return false;
706 }
707 }
708 catch (std::exception& e) {
709 LOG(LOG_LEVEL_ERROR) << wrong_option_msg << e.what() << '\n';
710 return false;
711 }
712 return true;
713}
714
715void vulkan_display_log(vkd::LogLevel vkd_log_level, std::string_view sv){
716 using L = vkd::LogLevel;
717
719 switch(vkd_log_level){
720 case L::fatal: log_level = LOG_LEVEL_FATAL; break;
721 case L::error: log_level = LOG_LEVEL_ERROR; break;
722 case L::warning: log_level = LOG_LEVEL_WARNING; break;
723 case L::notice: log_level = LOG_LEVEL_NOTICE; break;
724 case L::info: log_level = LOG_LEVEL_INFO; break;
725 case L::verbose: log_level = LOG_LEVEL_VERBOSE; break;
726 case L::debug: log_level = LOG_LEVEL_DEBUG; break;
727 }
728 LOG(log_level) << MOD_NAME << sv << std::endl;
729}
730
731void* display_vulkan_init(module* parent, const char* fmt, unsigned int flags) {
733 log_msg(LOG_LEVEL_ERROR, "UltraGrid VULKAN_SDL2 module currently doesn't support audio!\n");
734 return nullptr;
735 }
736
737 auto s = std::make_unique<state_vulkan_sdl2>(parent);
738
739 command_line_arguments args{};
740 if (fmt) {
741 if (!parse_command_line_arguments(args, *s, fmt)) {
742 return nullptr;
743 }
744 if (args.help) {
745 return INIT_NOERR;
746 }
747 }
748
749 int ret = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
750 if (ret < 0) {
751 log_msg(LOG_LEVEL_ERROR, "Unable to initialize SDL2: %s\n", SDL_GetError());
752 return nullptr;
753 }
754
755 ret = SDL_VideoInit(args.driver.empty() ? nullptr : args.driver.c_str());
756 if (ret < 0) {
757 log_msg(LOG_LEVEL_ERROR, "Unable to initialize SDL2 video: %s\n", SDL_GetError());
758 return nullptr;
759 }
760 log_msg(LOG_LEVEL_NOTICE, "[SDL] Using driver: %s\n", SDL_GetCurrentVideoDriver());
761
762 SDL_ShowCursor(args.cursor);
763 SDL_DisableScreenSaver();
764
765 for (auto& binding : display_vulkan_keybindings) {
766 std::string msg = std::to_string(static_cast<int>(binding.first));
767 keycontrol_register_key(&s->mod, binding.first, msg.c_str(), binding.second.data());
768 }
769
770 log_msg(LOG_LEVEL_NOTICE, "SDL2 initialized successfully.\n");
771
772 const char* window_title = "UltraGrid - Vulkan Display";
773 if (get_commandline_param("window-title")) {
774 window_title = get_commandline_param("window-title");
775 }
776
777 int x = (args.x == SDL_WINDOWPOS_UNDEFINED ? SDL_WINDOWPOS_CENTERED_DISPLAY(args.display_idx) : args.x);
778 int y = (args.y == SDL_WINDOWPOS_UNDEFINED ? SDL_WINDOWPOS_CENTERED_DISPLAY(args.display_idx) : args.y);
779 if(s->width == -1 && s->height == -1){
780 SDL_DisplayMode mode;
781 int display_index = 0;
782 if(SDL_GetDesktopDisplayMode(display_index, &mode)){
783 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to get display size\n");
784 mode.w = 0;
785 mode.h = 0;
786 }
787 s->width = mode.w;
788 s->height = mode.h;
789
790 log_msg(LOG_LEVEL_INFO, MOD_NAME "Detected display size: %dx%d\n", mode.w, mode.h);
791 }
792 if (s->width == 0) s->width = 960;
793 if (s->height == 0) s->height = 540;
794
795 int window_flags = args.window_flags | SDL_WINDOW_RESIZABLE | SDL_WINDOW_VULKAN | SDL_WINDOW_ALLOW_HIGHDPI;
796 if (s->fullscreen) {
797 window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
798 }
799
800 s->window = SDL_CreateWindow(window_title, x, y, s->width, s->height, window_flags);
801 if (!s->window) {
802 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unable to create window : %s\n", SDL_GetError());
803 return nullptr;
804 }
805 s->window_callback = new WindowCallback(s->window);
806
807 uint32_t extension_count = 0;
808 SDL_Vulkan_GetInstanceExtensions(s->window, &extension_count, nullptr);
809 std::vector<const char*> required_extensions(extension_count);
810 SDL_Vulkan_GetInstanceExtensions(s->window, &extension_count, required_extensions.data());
811 assert(extension_count > 0);
812
813 const char *path = get_install_root();
814 std::string path_to_shaders{ path ? path : ".." };
815 path_to_shaders = path_to_shaders + "/share/ultragrid/vulkan_shaders";
816 LOG(LOG_LEVEL_INFO) << MOD_NAME "Path to shaders: " << path_to_shaders << '\n';
817 try {
819 instance.init(required_extensions, args.validation, vulkan_display_log);
820#ifdef __MINGW32__
821 //SDL2 for MINGW has problem creating surface
822 SDL_SysWMinfo wmInfo{};
823 SDL_VERSION(&wmInfo.version);
824 SDL_GetWindowWMInfo(s->window, &wmInfo);
825 HWND hwnd = wmInfo.info.win.window;
826 HINSTANCE hinst = wmInfo.info.win.hinstance;
827 vk::Win32SurfaceCreateInfoKHR create_info{};
828 create_info
829 .setHinstance(hinst)
830 .setHwnd(hwnd);
831 vk::SurfaceKHR surface = nullptr;
832 if (instance.get_instance().createWin32SurfaceKHR(&create_info, nullptr, &surface) != vk::Result::eSuccess) {
833 throw std::runtime_error("Surface cannot be created.");
834 }
835#else
836 VkSurfaceKHR surface{};
837 if (!SDL_Vulkan_CreateSurface(s->window, (VkInstance) instance.get_instance(), &surface)) {
838 std::cout << SDL_GetError() << std::endl;
839 throw std::runtime_error("SDL cannot create surface.");
840 }
841#endif
842 s->vulkan = new vkd::VulkanDisplay{};
843 s->vulkan->init(std::move(instance), vk::SurfaceKHR(surface),
844 initial_frame_count, *s->window_callback,
845 args.gpu_idx, std::move(path_to_shaders),
846 args.vsync, args.tearing_permitted);
847 LOG(LOG_LEVEL_NOTICE) << MOD_NAME "Vulkan display initialised." << std::endl;
848 }
849 catch (std::exception& e) { log_and_exit_uv(e); return nullptr; }
850
851 draw_splashscreen(*s);
852 return static_cast<void*>(s.release());
853}
854
855void display_vulkan_done(void* state) {
856 auto* s = static_cast<state_vulkan_sdl2*>(state);
857 assert(s->mod.priv_magic == magic_vulkan_sdl2);
858
859 SDL_ShowCursor(SDL_ENABLE);
860
861 try {
862 s->vulkan->destroy();
863 }
864 catch (std::exception& e) { log_and_exit_uv(e); }
865
866 delete s->vulkan;
867 s->vulkan = nullptr;
868 delete s->window_callback;
869 s->window_callback = nullptr;
870
871 if (s->window) {
872 SDL_DestroyWindow(s->window);
873 s->window = nullptr;
874 }
875
876 SDL_Quit();
877
878 delete s;
879}
880
881video_frame* display_vulkan_getf(void* state) {
882 auto* s = static_cast<state_vulkan_sdl2*>(state);
883 assert(s->mod.priv_magic == magic_vulkan_sdl2);
884
885 const auto& desc = s->current_desc;
886 vkd::TransferImage image;
887 try {
888 image = s->vulkan->acquire_image(to_vkd_image_desc(desc, *s));
889 }
890 catch (std::exception& e) { log_and_exit_uv(e); return nullptr; }
891 video_frame& frame = *s->frame_mappings->create_frame(image);
892
893 update_description(desc, frame);
894 frame.tiles[0].data_len = image.get_row_pitch() * image.get_size().height;
895 frame.tiles[0].data = reinterpret_cast<char*>(image.get_memory_ptr());
896 return &frame;
897}
898
899bool display_vulkan_putf(void* state, video_frame* frame, long long timeout_ns) {
900 auto* s = static_cast<state_vulkan_sdl2*>(state);
901 assert(s->mod.priv_magic == magic_vulkan_sdl2);
902
903 if (!frame) {
904 s->should_exit = true;
905 return true;
906 }
907
908 vkd::TransferImage image = s->frame_mappings->get_image(frame);
909
910 if (timeout_ns == PUTF_DISCARD) {
911 try {
912 s->vulkan->discard_image(image);
913 }
914 catch (std::exception& e) { log_and_exit_uv(e); return false; }
915 return true;
916 }
917
918 if (s->deinterlace) {
920 vc_deinterlace(reinterpret_cast<unsigned char*>(image.get_memory_ptr()),
921 image.get_row_pitch(),
922 image.get_size().height);
923 });
924 }
925
926 try {
927 return !s->vulkan->queue_image(image, timeout_ns != PUTF_BLOCKING);
928 }
929 catch (std::exception& e) { log_and_exit_uv(e); return 1; }
930 return true;
931}
932
933bool display_vulkan_get_property(void* state, int property, void* val, size_t* len) {
934 auto* s = static_cast<state_vulkan_sdl2*>(state);
935 assert(s->mod.priv_magic == magic_vulkan_sdl2);
936
937 switch (property) {
939 auto& mapping = get_ug_to_vkd_format_mapping(*s);
940 std::vector<codec_t> codecs{};
941 codecs.reserve(mapping.size());
942 LOG(LOG_LEVEL_INFO) << MOD_NAME "Supported codecs are: ";
943 for (auto&[codec, vkd_fromat] : mapping) {
944 codecs.push_back(codec);
946 }
947 LOG(LOG_LEVEL_INFO) << std::endl;
948 size_t codecs_len = codecs.size() * sizeof(codec_t);
949 if (codecs_len > *len) {
950 return false;
951 }
952 memcpy(val, codecs.data(), codecs_len);
953 *len = codecs_len;
954 break;
955 }
957 if (sizeof(int) > *len) {
958 return false;
959 }
960 *len = sizeof(int);
961
962 vkd::TransferImage image;
963 const auto& desc = s->current_desc;
964 assert(s->current_desc.width != 0);
965 try {
966 image = s->vulkan->acquire_image(to_vkd_image_desc(desc, *s));
967 auto value = static_cast<int>(image.get_row_pitch());
968 memcpy(val, &value, sizeof(value));
969 s->vulkan->discard_image(image);
970 }
971 catch (std::exception& e) { log_and_exit_uv(e); return false; }
972 break;
973 }
974 default:
975 return false;
976 }
977 return true;
978}
979
980void display_vulkan_new_message(module* mod) {
981 auto s = reinterpret_cast<state_vulkan_sdl2*>(mod);
982 assert(s->mod.priv_magic == magic_vulkan_sdl2);
983
984 SDL_Event event{};
985 event.type = s->sdl_user_new_message_event;
986 SDL_PushEvent(&event);
987}
988
989
990static void display_vulkan_put_audio_frame([[maybe_unused]] void* state, [[maybe_unused]] const struct audio_frame* frame)
991{
992}
993
994
995bool display_vulkan_reconfigure_audio([[maybe_unused]] void* state, [[maybe_unused]] int quant_samples,
996 [[maybe_unused]] int channels, [[maybe_unused]] int sample_rate)
997{
998 return false;
999}
1000
1001const video_display_info display_vulkan_info = {
1002 [](device_info** available_cards, int* count, [[maybe_unused]] void (**deleter)(void*)) {
1003 *count = 1;
1004 *available_cards = static_cast<device_info*>(calloc(1, sizeof(device_info)));
1005 auto& info = **available_cards;
1006 strcpy(info.dev, "");
1007 strcpy(info.name, "VULKAN_SDL2 SW display");
1008 info.repeatable = true;
1009 },
1010 display_vulkan_init,
1011 display_vulkan_run,
1012 display_vulkan_done,
1013 display_vulkan_getf,
1014 display_vulkan_putf,
1015 display_vulkan_reconfigure,
1016 display_vulkan_get_property,
1017 display_vulkan_put_audio_frame,
1018 display_vulkan_reconfigure_audio,
1020};
1021
1022} // namespace
1023
1024
1026
void ** state
int void * val
struct video_desc desc
int int int sample_rate
int int channels
struct video_frame * frame
const char * fmt
int void size_t * len
const char unsigned int flags
int void(** deleter)(void *))
int property
int quant_samples
struct video_frame * vf_alloc(int count)
Allocates blank video frame.
void vf_free(struct video_frame *buf)
Frees video_frame structure.
const char * get_codec_name(codec_t codec)
static const struct codec_param mapping[AC_COUNT]
Definition libavcodec.c:87
static void show_help()
Definition wasapi.cpp:193
void set_process_function(std::function< void(TransferImage &image)> function)
void discard_image(TransferImage image)
bool queue_image(TransferImage img, bool discardable)
bool is_image_description_supported(ImageDescription description)
void init(VulkanInstance &&instance, vk::SurfaceKHR surface, uint32_t transfer_image_count, WindowChangedCallback &window, uint32_t gpu_index=no_gpu_selected, std::string path_to_shaders="./shaders", bool vsync=true, bool tearing_permitted=false)
TransferImage acquire_image(ImageDescription description)
void window_parameters_changed(WindowParameters new_parameters)
Hint to vulkan display that some window parameters spicified in struct WindowParameters changed....
#define SRED(x)
Definition color_out.h:85
#define TERM_FG_RED
Definition color_out.h:50
#define TERM_RESET
Definition color_out.h:47
#define SBOLD(x)
Definition color_out.h:80
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG_LEVEL_VERBOSE
Definition debug.h:63
#define LOG(level)
Definition debug.h:313
#define debug_msg(...)
#define debug_msg(...) log_msg(LOG_LEVEL_DEBUG, "[pid/%d +%d %s] ", getpid(), LINE,...
Definition debug.h:92
#define LOG_LEVEL_FATAL
errors that prevent UG run
Definition debug.h:58
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
const char * get_install_root(void)
Definition fs.c:116
#define PUTF_DISCARD
#define PUTF_BLOCKING
#define VIDEO_DISPLAY_ABI_VERSION
#define DISPLAY_FLAG_AUDIO_ANY
#define DISPLAY_NO_GENERIC_FPS_INDICATOR
@ DISPLAY_PROPERTY_BUF_PITCH
requested framebuffer pitch - int (bytes), may be PITCH_DEFAULT
@ DISPLAY_PROPERTY_CODECS
list of natively supported codecs - codec_t[]
void module_init_default(struct module *module_data)
Definition module.c:48
void module_register(struct module *module_data, struct module *parent)
Definition module.c:82
void module_done(struct module *module_data)
Definition module.c:93
struct module * get_root_module(struct module *node)
Definition module.c:203
@ MODULE_CLASS_DATA
Definition module.h:86
int count
Definition hd-rum.c:44
void exit_uv(int status)
Definition host.cpp:625
const char * get_commandline_param(const char *key)
Definition host.cpp:894
#define INIT_NOERR
Definition host.h:73
#define LOG_LEVEL_DEBUG
Definition kernels.cu:48
volatile int log_level
Definition debug.cpp:65
bool keycontrol_register_key(struct module *receiver_mod, int64_t key, const char *message, const char *description)
Registers callback message for given key.
void keycontrol_send_key(struct module *root, int64_t key)
#define K_LEFT
#define K_RIGHT
#define K_PGDOWN
#define K_DOWN
#define K_CTRL(x)
#define K_PGUP
#define K_UP
@ LIBRARY_CLASS_VIDEO_DISPLAY
Definition lib_common.h:79
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
Definition lib_common.h:144
void free_message(struct message *msg, struct response *r)
struct response * new_response(int status, const char *text)
struct message * check_message(struct module *mod)
#define RESPONSE_OK
Definition messaging.h:57
#define RESPONSE_BAD_REQUEST
Definition messaging.h:60
constexpr uint32_t gpu_integrated
constexpr uint32_t gpu_discrete
constexpr uint32_t no_gpu_selected
enum openssl_mode mode
static void sdl2_print_displays()
Definition sdl2.c:330
static int64_t translate_sdl_key_to_ug(SDL_Keysym sym)
Definition sdl2.c:192
const char * parent
Definition settings.cpp:198
#define HEADER_PIXEL(data, pixel)
Definition splashscreen.h:8
static const char *const splash_data
static const unsigned int splash_width
Definition splashscreen.h:3
static const unsigned int splash_height
Definition splashscreen.h:4
Definition rtp.c:206
struct used to comunicate with audio capture/playback device
Definition types.h:99
Definition rtp.c:253
unsigned int width
Definition types.h:280
char * data
Raw tile data. Pointer must be at least 4B aligned.
Definition types.h:287
unsigned int data_len
length of the data
Definition types.h:288
unsigned int height
Definition types.h:281
unsigned int tile_count
Definition types.h:185
codec_t color_spec
Definition types.h:182
enum interlacing_t interlacing
Definition types.h:184
double fps
Definition types.h:183
unsigned int width
width of every tile (!)
Definition types.h:179
unsigned int height
height of every tile (!)
Definition types.h:180
Struct video_frame represents a video frame and contains video description.
Definition types.h:305
enum interlacing_t interlacing
Definition types.h:307
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
Definition types.h:345
double fps
Definition types.h:308
unsigned int tile_count
Definition types.h:344
codec_t color_spec
Definition types.h:306
codec_t
Definition types.h:65
@ YUYV
YCbCr 422 8-bit - Y0 Cb Y1 Cr.
Definition types.h:72
@ R10k
RGB 10-bit packed - RGBX, big-endian (2 bit padding)
Definition types.h:73
@ RGB
RGB 8-bit (packed into 24-bit word)
Definition types.h:80
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
Definition types.h:71
@ RG48
16-bit RGB little-endian (RlRhGl...)
Definition types.h:95
@ Y416
interleaved little-endian YCbCr 4444 16-bit - UYVA
Definition types.h:99
@ Y216
YCbCr 422 16-bit little-endian - Y0 Cb Y1 Cr.
Definition types.h:98
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
codec_t ug_codec
Definition v4l2_common.h:68
This is an umbrella header for video functions.
void vc_deinterlace(unsigned char *src, long src_linesize, int lines)
Deinterlaces framebuffer.
const char * key
Definition cineform.cpp:125
static struct @76 codecs[]
#define MOD_NAME
static class worker_pool instance
Definition worker.cpp:250