From 80b4a65cd2e7fe2cfd12efa4d8a0778958c739fe Mon Sep 17 00:00:00 2001 From: jpark37 Date: Tue, 26 Jan 2021 16:44:36 -0800 Subject: [PATCH] win-capture: Add WGC desktop capture Copy patterns from WGC window capture, and BitBlt monitor capture. --- plugins/win-capture/data/locale/en-US.ini | 6 +- .../win-capture/duplicator-monitor-capture.c | 538 ++++++++++++++---- 2 files changed, 446 insertions(+), 98 deletions(-) diff --git a/plugins/win-capture/data/locale/en-US.ini b/plugins/win-capture/data/locale/en-US.ini index 632e9bfae..34a780470 100644 --- a/plugins/win-capture/data/locale/en-US.ini +++ b/plugins/win-capture/data/locale/en-US.ini @@ -4,7 +4,7 @@ WindowCapture.Window="Window" WindowCapture.Method="Capture Method" WindowCapture.Method.Auto="Automatic" WindowCapture.Method.BitBlt="BitBlt (Windows 7 and up)" -WindowCapture.Method.WindowsGraphicsCapture="Windows Graphics Capture (Windows 10 1903 and up)" +WindowCapture.Method.WindowsGraphicsCapture="Windows 10 (1903 and up)" WindowCapture.Priority="Window Match Priority" WindowCapture.Priority.Title="Window title must match" WindowCapture.Priority.Class="Match title, otherwise find window of same type" @@ -16,6 +16,10 @@ SLIFix="SLI/Crossfire Capture Mode (Slow)" AllowTransparency="Allow Transparency" Monitor="Display" PrimaryMonitor="Primary Monitor" +Method="Capture Method" +Method.Auto="Automatic" +Method.DXGI="DXGI Desktop Duplication" +Method.WindowsGraphicsCapture="Windows 10 (1903 and up)" GameCapture="Game Capture" GameCapture.AnyFullscreen="Capture any fullscreen application" GameCapture.CaptureWindow="Capture specific window" diff --git a/plugins/win-capture/duplicator-monitor-capture.c b/plugins/win-capture/duplicator-monitor-capture.c index d7def0ad8..bdd4c45a6 100644 --- a/plugins/win-capture/duplicator-monitor-capture.c +++ b/plugins/win-capture/duplicator-monitor-capture.c @@ -2,8 +2,11 @@ #include #include +#include #include "cursor-capture.h" +#include "../../libobs/util/platform.h" +#include "../../libobs-winrt/winrt-capture.h" #define do_log(level, format, ...) \ blog(level, "[duplicator-monitor-capture: '%s'] " format, \ @@ -20,16 +23,61 @@ #define TEXT_COMPATIBILITY obs_module_text("Compatibility") #define TEXT_MONITOR obs_module_text("Monitor") #define TEXT_PRIMARY_MONITOR obs_module_text("PrimaryMonitor") +#define TEXT_METHOD obs_module_text("Method") +#define TEXT_METHOD_AUTO obs_module_text("WindowCapture.Method.Auto") +#define TEXT_METHOD_DXGI obs_module_text("Method.DXGI") +#define TEXT_METHOD_WGC obs_module_text("Method.WindowsGraphicsCapture") /* clang-format on */ #define RESET_INTERVAL_SEC 3.0f +typedef BOOL (*PFN_winrt_capture_supported)(); +typedef BOOL (*PFN_winrt_capture_cursor_toggle_supported)(); +typedef struct winrt_capture *(*PFN_winrt_capture_init_monitor)( + BOOL cursor, HMONITOR monitor); +typedef void (*PFN_winrt_capture_free)(struct winrt_capture *capture); + +typedef BOOL (*PFN_winrt_capture_active)(const struct winrt_capture *capture); +typedef void (*PFN_winrt_capture_show_cursor)(struct winrt_capture *capture, + BOOL visible); +typedef void (*PFN_winrt_capture_render)(struct winrt_capture *capture, + gs_effect_t *effect); +typedef uint32_t (*PFN_winrt_capture_width)(const struct winrt_capture *capture); +typedef uint32_t (*PFN_winrt_capture_height)( + const struct winrt_capture *capture); + +struct winrt_exports { + PFN_winrt_capture_supported winrt_capture_supported; + PFN_winrt_capture_cursor_toggle_supported + winrt_capture_cursor_toggle_supported; + PFN_winrt_capture_init_monitor winrt_capture_init_monitor; + PFN_winrt_capture_free winrt_capture_free; + PFN_winrt_capture_active winrt_capture_active; + PFN_winrt_capture_show_cursor winrt_capture_show_cursor; + PFN_winrt_capture_render winrt_capture_render; + PFN_winrt_capture_width winrt_capture_width; + PFN_winrt_capture_height winrt_capture_height; +}; + +enum display_capture_method { + METHOD_AUTO, + METHOD_DXGI, + METHOD_WGC, +}; + struct duplicator_capture { obs_source_t *source; + pthread_mutex_t update_mutex; int monitor; + int dxgi_index; + enum display_capture_method method; + bool reset_wgc; + HMONITOR handle; bool capture_cursor; bool showing; + LONG logged_width; + LONG logged_height; long x; long y; @@ -39,29 +87,114 @@ struct duplicator_capture { gs_duplicator_t *duplicator; float reset_timeout; struct cursor_data cursor_data; + + bool wgc_supported; + void *winrt_module; + struct winrt_exports exports; + struct winrt_capture *capture_winrt; +}; + +struct wgc_monitor_info { + int cur_id; + int desired_id; + int id; + RECT rect; + HMONITOR handle; }; /* ------------------------------------------------------------------------- */ +static const char *get_method_name(int method) +{ + const char *method_name = ""; + switch (method) { + case METHOD_DXGI: + method_name = "DXGI"; + break; + case METHOD_WGC: + method_name = "WGC"; + break; + } + + return method_name; +} + +static BOOL CALLBACK enum_monitor(HMONITOR handle, HDC hdc, LPRECT rect, + LPARAM param) +{ + struct wgc_monitor_info *monitor = (struct wgc_monitor_info *)param; + + if (monitor->cur_id == 0 || monitor->desired_id == monitor->cur_id) { + monitor->id = monitor->cur_id; + monitor->rect = *rect; + monitor->handle = handle; + } + + UNUSED_PARAMETER(hdc); + return (monitor->desired_id > monitor->cur_id++); +} + +static void log_settings(struct duplicator_capture *capture, int monitor, + LONG width, LONG height) +{ + info("update settings:\n" + "\tdisplay: %d (%ldx%ld)\n" + "\tcursor: %s\n" + "\tmethod: %s", + monitor + 1, width, height, + capture->capture_cursor ? "true" : "false", + get_method_name(capture->method)); +} + +static enum display_capture_method +choose_method(enum display_capture_method method, bool wgc_supported, + HMONITOR monitor, int *dxgi_index) +{ + if (!wgc_supported) + method = METHOD_DXGI; + + if (method != METHOD_WGC) { + obs_enter_graphics(); + *dxgi_index = gs_duplicator_get_monitor_index(monitor); + obs_leave_graphics(); + } + + if (method == METHOD_AUTO) + method = (*dxgi_index == -1) ? METHOD_WGC : METHOD_DXGI; + + return method; +} + static inline void update_settings(struct duplicator_capture *capture, obs_data_t *settings) { - capture->monitor = (int)obs_data_get_int(settings, "monitor"); + pthread_mutex_lock(&capture->update_mutex); + + struct wgc_monitor_info monitor = {0}; + monitor.desired_id = (int)obs_data_get_int(settings, "monitor"); + EnumDisplayMonitors(NULL, NULL, enum_monitor, (LPARAM)&monitor); + + capture->method = choose_method( + (int)obs_data_get_int(settings, "method"), + capture->wgc_supported, monitor.handle, &capture->dxgi_index); + + capture->monitor = monitor.id; + capture->handle = monitor.handle; + capture->capture_cursor = obs_data_get_bool(settings, "capture_cursor"); - obs_enter_graphics(); + capture->logged_width = monitor.rect.right - monitor.rect.left; + capture->logged_height = monitor.rect.bottom - monitor.rect.top; - struct gs_monitor_info info; - if (gs_get_duplicator_monitor_info(capture->monitor, &info)) { - info("update settings:\n" - "\tdisplay: %d (%ldx%ld)\n" - "\tcursor: %s", - capture->monitor + 1, info.cx, info.cy, - capture->capture_cursor ? "true" : "false"); + if (capture->duplicator) { + obs_enter_graphics(); + + gs_duplicator_destroy(capture->duplicator); + capture->duplicator = NULL; + + obs_leave_graphics(); } - gs_duplicator_destroy(capture->duplicator); - capture->duplicator = NULL; capture->width = 0; capture->height = 0; capture->x = 0; @@ -69,7 +202,7 @@ static inline void update_settings(struct duplicator_capture *capture, capture->rot = 0; capture->reset_timeout = RESET_INTERVAL_SEC; - obs_leave_graphics(); + pthread_mutex_unlock(&capture->update_mutex); } /* ------------------------------------------------------------------------- */ @@ -80,23 +213,47 @@ static const char *duplicator_capture_getname(void *unused) return TEXT_MONITOR_CAPTURE; } -static void duplicator_capture_destroy(void *data) +static void duplicator_actual_destroy(void *data) { struct duplicator_capture *capture = data; + if (capture->capture_winrt) { + capture->exports.winrt_capture_free(capture->capture_winrt); + capture->capture_winrt = NULL; + } + obs_enter_graphics(); - gs_duplicator_destroy(capture->duplicator); + if (capture->duplicator) { + gs_duplicator_destroy(capture->duplicator); + capture->duplicator = NULL; + } + cursor_data_free(&capture->cursor_data); obs_leave_graphics(); + if (capture->winrt_module) { + os_dlclose(capture->winrt_module); + capture->winrt_module = NULL; + } + + pthread_mutex_destroy(&capture->update_mutex); + bfree(capture); } +static void duplicator_capture_destroy(void *data) +{ + obs_queue_task(OBS_TASK_GRAPHICS, duplicator_actual_destroy, data, + false); +} + static void duplicator_capture_defaults(obs_data_t *settings) { + obs_data_set_default_int(settings, "method", METHOD_AUTO); obs_data_set_default_int(settings, "monitor", 0); + obs_data_set_default_int(settings, "monitor_wgc", 0); obs_data_set_default_bool(settings, "capture_cursor", true); } @@ -104,6 +261,39 @@ static void duplicator_capture_update(void *data, obs_data_t *settings) { struct duplicator_capture *mc = data; update_settings(mc, settings); + log_settings(mc, mc->monitor, mc->logged_width, mc->logged_height); + + mc->reset_wgc = true; +} + +#define WINRT_IMPORT(func) \ + do { \ + exports->func = (PFN_##func)os_dlsym(module, #func); \ + if (!exports->func) { \ + success = false; \ + blog(LOG_ERROR, \ + "Could not load function '%s' from " \ + "module '%s'", \ + #func, module_name); \ + } \ + } while (false) + +static bool load_winrt_imports(struct winrt_exports *exports, void *module, + const char *module_name) +{ + bool success = true; + + WINRT_IMPORT(winrt_capture_supported); + WINRT_IMPORT(winrt_capture_cursor_toggle_supported); + WINRT_IMPORT(winrt_capture_init_monitor); + WINRT_IMPORT(winrt_capture_free); + WINRT_IMPORT(winrt_capture_active); + WINRT_IMPORT(winrt_capture_show_cursor); + WINRT_IMPORT(winrt_capture_render); + WINRT_IMPORT(winrt_capture_width); + WINRT_IMPORT(winrt_capture_height); + + return success; } static void *duplicator_capture_create(obs_data_t *settings, @@ -114,7 +304,26 @@ static void *duplicator_capture_create(obs_data_t *settings, capture = bzalloc(sizeof(struct duplicator_capture)); capture->source = source; + pthread_mutex_init(&capture->update_mutex, NULL); + + obs_enter_graphics(); + const bool uses_d3d11 = gs_get_device_type() == GS_DEVICE_DIRECT3D_11; + obs_leave_graphics(); + + if (uses_d3d11) { + static const char *const module = "libobs-winrt"; + capture->winrt_module = os_dlopen(module); + if (capture->winrt_module && + load_winrt_imports(&capture->exports, capture->winrt_module, + module) && + capture->exports.winrt_capture_supported()) { + capture->wgc_supported = true; + } + } + update_settings(capture, settings); + log_settings(capture, capture->monitor, capture->logged_width, + capture->logged_height); return capture; } @@ -124,9 +333,14 @@ static void reset_capture_data(struct duplicator_capture *capture) struct gs_monitor_info monitor_info = {0}; gs_texture_t *texture = gs_duplicator_get_texture(capture->duplicator); - gs_get_duplicator_monitor_info(capture->monitor, &monitor_info); - capture->width = gs_texture_get_width(texture); - capture->height = gs_texture_get_height(texture); + gs_get_duplicator_monitor_info(capture->dxgi_index, &monitor_info); + if (texture) { + capture->width = gs_texture_get_width(texture); + capture->height = gs_texture_get_height(texture); + } else { + capture->width = 0; + capture->height = 0; + } capture->x = monitor_info.x; capture->y = monitor_info.y; capture->rot = monitor_info.rotation_degrees; @@ -134,9 +348,18 @@ static void reset_capture_data(struct duplicator_capture *capture) static void free_capture_data(struct duplicator_capture *capture) { - gs_duplicator_destroy(capture->duplicator); + if (capture->capture_winrt) { + capture->exports.winrt_capture_free(capture->capture_winrt); + capture->capture_winrt = NULL; + } + + if (capture->duplicator) { + gs_duplicator_destroy(capture->duplicator); + capture->duplicator = NULL; + } + cursor_data_free(&capture->cursor_data); - capture->duplicator = NULL; + capture->width = 0; capture->height = 0; capture->x = 0; @@ -169,26 +392,62 @@ static void duplicator_capture_tick(void *data, float seconds) obs_enter_graphics(); - if (!capture->duplicator) { - capture->reset_timeout += seconds; - - if (capture->reset_timeout >= RESET_INTERVAL_SEC) { - capture->duplicator = - gs_duplicator_create(capture->monitor); - - capture->reset_timeout = 0.0f; + if (capture->method == METHOD_WGC) { + if (capture->reset_wgc && capture->capture_winrt) { + capture->exports.winrt_capture_free( + capture->capture_winrt); + capture->capture_winrt = NULL; + capture->reset_wgc = false; + capture->reset_timeout = RESET_INTERVAL_SEC; } - } - if (!!capture->duplicator) { - if (capture->capture_cursor) - cursor_capture(&capture->cursor_data); + if (!capture->capture_winrt) { + capture->reset_timeout += seconds; - if (!gs_duplicator_update_frame(capture->duplicator)) { - free_capture_data(capture); + if (capture->reset_timeout >= RESET_INTERVAL_SEC) { + capture->capture_winrt = + capture->exports + .winrt_capture_init_monitor( + capture->capture_cursor, + capture->handle); - } else if (capture->width == 0) { - reset_capture_data(capture); + capture->reset_timeout = 0.0f; + } + } + + if (capture->capture_winrt) { + capture->exports.winrt_capture_show_cursor( + capture->capture_winrt, + capture->capture_cursor); + } + } else { + if (capture->capture_winrt) { + capture->exports.winrt_capture_free( + capture->capture_winrt); + capture->capture_winrt = NULL; + } + + if (!capture->duplicator) { + capture->reset_timeout += seconds; + + if (capture->reset_timeout >= RESET_INTERVAL_SEC) { + capture->duplicator = gs_duplicator_create( + capture->dxgi_index); + + capture->reset_timeout = 0.0f; + } + } + + if (capture->duplicator) { + if (capture->capture_cursor) + cursor_capture(&capture->cursor_data); + + if (!gs_duplicator_update_frame(capture->duplicator)) { + free_capture_data(capture); + + } else if (capture->width == 0) { + reset_capture_data(capture); + } } } @@ -203,13 +462,21 @@ static void duplicator_capture_tick(void *data, float seconds) static uint32_t duplicator_capture_width(void *data) { struct duplicator_capture *capture = data; - return capture->rot % 180 == 0 ? capture->width : capture->height; + return (capture->method == METHOD_WGC) + ? capture->exports.winrt_capture_width( + capture->capture_winrt) + : (capture->rot % 180 == 0 ? capture->width + : capture->height); } static uint32_t duplicator_capture_height(void *data) { struct duplicator_capture *capture = data; - return capture->rot % 180 == 0 ? capture->height : capture->width; + return (capture->method == METHOD_WGC) + ? capture->exports.winrt_capture_height( + capture->capture_winrt) + : (capture->rot % 180 == 0 ? capture->height + : capture->width); } static void draw_cursor(struct duplicator_capture *capture) @@ -222,88 +489,170 @@ static void draw_cursor(struct duplicator_capture *capture) static void duplicator_capture_render(void *data, gs_effect_t *effect) { struct duplicator_capture *capture = data; - gs_texture_t *texture; - int rot; - if (!capture->duplicator) - return; - - texture = gs_duplicator_get_texture(capture->duplicator); - if (!texture) - return; - - effect = obs_get_base_effect(OBS_EFFECT_OPAQUE); - - rot = capture->rot; - - const bool previous = gs_set_linear_srgb(false); - - while (gs_effect_loop(effect, "Draw")) { - if (rot != 0) { - float x = 0.0f; - float y = 0.0f; - - switch (rot) { - case 90: - x = (float)capture->height; - break; - case 180: - x = (float)capture->width; - y = (float)capture->height; - break; - case 270: - y = (float)capture->width; - break; + if (capture->method == METHOD_WGC) { + gs_effect_t *const opaque = + obs_get_base_effect(OBS_EFFECT_OPAQUE); + if (capture->capture_winrt) { + if (capture->exports.winrt_capture_active( + capture->capture_winrt)) { + capture->exports.winrt_capture_render( + capture->capture_winrt, opaque); + } else { + capture->exports.winrt_capture_free( + capture->capture_winrt); + capture->capture_winrt = NULL; } - - gs_matrix_push(); - gs_matrix_translate3f(x, y, 0.0f); - gs_matrix_rotaa4f(0.0f, 0.0f, 1.0f, RAD((float)rot)); } + } else { + gs_texture_t *texture; + int rot; - obs_source_draw(texture, 0, 0, 0, 0, false); + if (!capture->duplicator) + return; - if (rot != 0) - gs_matrix_pop(); - } + texture = gs_duplicator_get_texture(capture->duplicator); + if (!texture) + return; - gs_set_linear_srgb(previous); + effect = obs_get_base_effect(OBS_EFFECT_OPAQUE); - if (capture->capture_cursor) { - effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); + rot = capture->rot; + + const bool previous = gs_set_linear_srgb(false); while (gs_effect_loop(effect, "Draw")) { - draw_cursor(capture); + if (rot != 0) { + float x = 0.0f; + float y = 0.0f; + + switch (rot) { + case 90: + x = (float)capture->height; + break; + case 180: + x = (float)capture->width; + y = (float)capture->height; + break; + case 270: + y = (float)capture->width; + break; + } + + gs_matrix_push(); + gs_matrix_translate3f(x, y, 0.0f); + gs_matrix_rotaa4f(0.0f, 0.0f, 1.0f, + RAD((float)rot)); + } + + obs_source_draw(texture, 0, 0, 0, 0, false); + + if (rot != 0) + gs_matrix_pop(); + } + + gs_set_linear_srgb(previous); + + if (capture->capture_cursor) { + effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); + + while (gs_effect_loop(effect, "Draw")) { + draw_cursor(capture); + } } } } -static bool get_monitor_props(obs_property_t *monitor_list, int monitor_idx) +static BOOL CALLBACK enum_monitor_props(HMONITOR handle, HDC hdc, LPRECT rect, + LPARAM param) { + UNUSED_PARAMETER(hdc); + UNUSED_PARAMETER(rect); + + obs_property_t *monitor_list = (obs_property_t *)param; + MONITORINFO mi; + size_t monitor_id = 0; struct dstr monitor_desc = {0}; - struct gs_monitor_info info; + struct dstr resolution = {0}; + struct dstr format_string = {0}; - if (!gs_get_duplicator_monitor_info(monitor_idx, &info)) - return false; + monitor_id = obs_property_list_item_count(monitor_list); - dstr_catf(&monitor_desc, "%s %d: %ldx%ld @ %ld,%ld", TEXT_MONITOR, - monitor_idx + 1, info.cx, info.cy, info.x, info.y); + mi.cbSize = sizeof(mi); + GetMonitorInfo(handle, &mi); + + dstr_catf(&resolution, "%dx%d @ %d,%d", + mi.rcMonitor.right - mi.rcMonitor.left, + mi.rcMonitor.bottom - mi.rcMonitor.top, mi.rcMonitor.left, + mi.rcMonitor.top); + + dstr_copy(&format_string, "%s %d: %s"); + if (mi.dwFlags == MONITORINFOF_PRIMARY) { + dstr_catf(&format_string, " (%s)", TEXT_PRIMARY_MONITOR); + } + + dstr_catf(&monitor_desc, format_string.array, TEXT_MONITOR, + monitor_id + 1, resolution.array); obs_property_list_add_int(monitor_list, monitor_desc.array, - monitor_idx); + (int)monitor_id); dstr_free(&monitor_desc); + dstr_free(&resolution); + dstr_free(&format_string); + + return TRUE; +} + +static void update_settings_visibility(obs_properties_t *props, + struct duplicator_capture *capture) +{ + pthread_mutex_lock(&capture->update_mutex); + + const enum window_capture_method method = capture->method; + const bool dxgi_options = method == METHOD_DXGI; + const bool wgc_options = method == METHOD_WGC; + + const bool wgc_cursor_toggle = + wgc_options && + capture->exports.winrt_capture_cursor_toggle_supported(); + + obs_property_t *p = obs_properties_get(props, "cursor"); + obs_property_set_visible(p, dxgi_options || wgc_cursor_toggle); + + pthread_mutex_unlock(&capture->update_mutex); +} + +static bool display_capture_method_changed(obs_properties_t *props, + obs_property_t *p, + obs_data_t *settings) +{ + UNUSED_PARAMETER(p); + + struct duplicator_capture *capture = obs_properties_get_param(props); + update_settings(capture, settings); + + update_settings_visibility(props, capture); return true; } -static obs_properties_t *duplicator_capture_properties(void *unused) +static obs_properties_t *duplicator_capture_properties(void *data) { - int monitor_idx = 0; - - UNUSED_PARAMETER(unused); + struct duplicator_capture *capture = data; obs_properties_t *props = obs_properties_create(); + obs_properties_set_param(props, capture, NULL); + + obs_property_t *p = obs_properties_add_list(props, "method", + TEXT_METHOD, + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(p, TEXT_METHOD_AUTO, METHOD_AUTO); + obs_property_list_add_int(p, TEXT_METHOD_DXGI, METHOD_DXGI); + obs_property_list_add_int(p, TEXT_METHOD_WGC, METHOD_WGC); + obs_property_list_item_disable(p, 1, !capture->wgc_supported); + obs_property_set_modified_callback(p, display_capture_method_changed); obs_property_t *monitors = obs_properties_add_list( props, "monitor", TEXT_MONITOR, OBS_COMBO_TYPE_LIST, @@ -311,12 +660,7 @@ static obs_properties_t *duplicator_capture_properties(void *unused) obs_properties_add_bool(props, "capture_cursor", TEXT_CAPTURE_CURSOR); - obs_enter_graphics(); - - while (get_monitor_props(monitors, monitor_idx++)) - ; - - obs_leave_graphics(); + EnumDisplayMonitors(NULL, NULL, enum_monitor_props, (LPARAM)monitors); return props; }