diff --git a/plugins/win-capture/data/locale/en-US.ini b/plugins/win-capture/data/locale/en-US.ini index cdcbdf918..feaf8166a 100644 --- a/plugins/win-capture/data/locale/en-US.ini +++ b/plugins/win-capture/data/locale/en-US.ini @@ -1,6 +1,9 @@ MonitorCapture="Display Capture" WindowCapture="Window Capture" WindowCapture.Window="Window" +WindowCapture.Method="Capture Method" +WindowCapture.Method.BitBlt="BitBlt (Windows 7 and up)" +WindowCapture.Method.WindowsGraphicsCapture="Windows Graphics Capture (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" diff --git a/plugins/win-capture/window-capture.c b/plugins/win-capture/window-capture.c index 57cc20cd6..95e471449 100644 --- a/plugins/win-capture/window-capture.c +++ b/plugins/win-capture/window-capture.c @@ -2,11 +2,16 @@ #include #include "dc-capture.h" #include "window-helpers.h" +#include "../../libobs/util/platform.h" +#include "../../libobs-winrt/winrt-capture.h" /* clang-format off */ #define TEXT_WINDOW_CAPTURE obs_module_text("WindowCapture") #define TEXT_WINDOW obs_module_text("WindowCapture.Window") +#define TEXT_METHOD obs_module_text("WindowCapture.Method") +#define TEXT_METHOD_BITBLT obs_module_text("WindowCapture.Method.BitBlt") +#define TEXT_METHOD_WGC obs_module_text("WindowCapture.Method.WindowsGraphicsCapture") #define TEXT_MATCH_PRIORITY obs_module_text("WindowCapture.Priority") #define TEXT_MATCH_TITLE obs_module_text("WindowCapture.Priority.Title") #define TEXT_MATCH_CLASS obs_module_text("WindowCapture.Priority.Class") @@ -18,12 +23,28 @@ #define WC_CHECK_TIMER 1.0f +struct winrt_exports { + bool *(*winrt_capture_supported)(); + struct winrt_capture *(*winrt_capture_init)(bool cursor, HWND window); + void (*winrt_capture_free)(struct winrt_capture *capture); + void (*winrt_capture_render)(struct winrt_capture *capture, + gs_effect_t *effect); + int32_t (*winrt_capture_width)(const struct winrt_capture *capture); + int32_t (*winrt_capture_height)(const struct winrt_capture *capture); +}; + +enum window_capture_method { + METHOD_BITBLT, + METHOD_WGC, +}; + struct window_capture { obs_source_t *source; char *title; char *class; char *executable; + enum window_capture_method method; enum window_priority priority; bool cursor; bool compatibility; @@ -31,6 +52,11 @@ struct window_capture { struct dc_capture capture; + bool wgc_supported; + void *winrt_module; + struct winrt_exports exports; + struct winrt_capture *capture_winrt; + float resize_timer; float check_window_timer; float cursor_check_time; @@ -41,6 +67,7 @@ struct window_capture { static void update_settings(struct window_capture *wc, obs_data_t *s) { + int method = (int)obs_data_get_int(s, "method"); const char *window = obs_data_get_string(s, "window"); int priority = (int)obs_data_get_int(s, "priority"); @@ -58,6 +85,10 @@ static void update_settings(struct window_capture *wc, obs_data_t *s) blog(LOG_DEBUG, "\tclass: %s", wc->class); } + if ((method == METHOD_WGC) && !wc->wgc_supported) + method = METHOD_BITBLT; + + wc->method = method; wc->priority = (enum window_priority)priority; wc->cursor = obs_data_get_bool(s, "cursor"); wc->use_wildcards = obs_data_get_bool(s, "use_wildcards"); @@ -72,11 +103,54 @@ static const char *wc_getname(void *unused) return TEXT_WINDOW_CAPTURE; } +#define WINRT_IMPORT(func) \ + do { \ + exports->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_init); + WINRT_IMPORT(winrt_capture_free); + WINRT_IMPORT(winrt_capture_render); + WINRT_IMPORT(winrt_capture_width); + WINRT_IMPORT(winrt_capture_height); + + return success; +} + static void *wc_create(obs_data_t *settings, obs_source_t *source) { struct window_capture *wc = bzalloc(sizeof(struct window_capture)); wc->source = source; + 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"; + bool use_winrt_capture = false; + wc->winrt_module = os_dlopen(module); + if (wc->winrt_module && + load_winrt_imports(&wc->exports, wc->winrt_module, + module) && + wc->exports.winrt_capture_supported()) { + wc->wgc_supported = true; + } + } + update_settings(wc, settings); return wc; } @@ -94,6 +168,9 @@ static void wc_destroy(void *data) bfree(wc->class); bfree(wc->executable); + if (wc->winrt_module) + os_dlclose(wc->winrt_module); + bfree(wc); } } @@ -111,28 +188,55 @@ static void wc_update(void *data, obs_data_t *settings) static uint32_t wc_width(void *data) { struct window_capture *wc = data; - return wc->capture.width; + return (wc->method == METHOD_WGC) + ? wc->exports.winrt_capture_width(wc->capture_winrt) + : wc->capture.width; } static uint32_t wc_height(void *data) { struct window_capture *wc = data; - return wc->capture.height; + return (wc->method == METHOD_WGC) + ? wc->exports.winrt_capture_height(wc->capture_winrt) + : wc->capture.height; } static void wc_defaults(obs_data_t *defaults) { + obs_data_set_default_int(defaults, "method", METHOD_BITBLT); obs_data_set_default_bool(defaults, "cursor", true); obs_data_set_default_bool(defaults, "compatibility", false); } -static obs_properties_t *wc_properties(void *unused) +static bool wc_capture_method_changed(obs_properties_t *props, + obs_property_t *p, obs_data_t *settings) { - UNUSED_PARAMETER(unused); + const int method = (int)obs_data_get_int(settings, "method"); + const bool use_bitblt = method == METHOD_BITBLT; + + p = obs_properties_get(props, "cursor"); + obs_property_set_visible(p, use_bitblt); + + p = obs_properties_get(props, "compatibility"); + obs_property_set_visible(p, use_bitblt); + + return true; +} + +static obs_properties_t *wc_properties(void *data) +{ + struct window_capture *wc = data; obs_properties_t *ppts = obs_properties_create(); obs_property_t *p; + p = obs_properties_add_list(ppts, "method", TEXT_METHOD, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(p, TEXT_METHOD_BITBLT, METHOD_BITBLT); + obs_property_list_add_int(p, TEXT_METHOD_WGC, METHOD_WGC); + obs_property_list_item_disable(p, 1, !wc->wgc_supported); + obs_property_set_modified_callback(p, wc_capture_method_changed); + p = obs_properties_add_list(ppts, "window", TEXT_WINDOW, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); @@ -151,6 +255,18 @@ static obs_properties_t *wc_properties(void *unused) return ppts; } +static void wc_hide(void *data) +{ + struct window_capture *wc = data; + + if (wc->capture_winrt) { + wc->exports.winrt_capture_free(wc->capture_winrt); + wc->capture_winrt = NULL; + } + + memset(&wc->last_rect, 0, sizeof(wc->last_rect)); +} + #define RESIZE_CHECK_TIME 0.2f #define CURSOR_CHECK_TIME 0.2f @@ -175,10 +291,22 @@ static void wc_tick(void *data, float seconds) return; } + if (wc->capture_winrt) { + wc->exports.winrt_capture_free(wc->capture_winrt); + wc->capture_winrt = NULL; + } + wc->check_window_timer = 0.0f; - wc->window = find_window(EXCLUDE_MINIMIZED, wc->priority, - wc->class, wc->title, wc->executable); + wc->window = (wc->method == METHOD_WGC) + ? find_window_top_level(EXCLUDE_MINIMIZED, + wc->priority, + wc->class, + wc->title, + wc->executable) + : find_window(EXCLUDE_MINIMIZED, + wc->priority, wc->class, + wc->title, wc->executable); if (!wc->window) { if (wc->capture.valid) dc_capture_free(&wc->capture); @@ -203,47 +331,62 @@ static void wc_tick(void *data, float seconds) if (!GetWindowThreadProcessId(wc->window, &target_pid)) target_pid = 0; - if (foreground_pid && target_pid && - foreground_pid != target_pid) - wc->capture.cursor_hidden = true; - else - wc->capture.cursor_hidden = false; + wc->capture.cursor_hidden = foreground_pid && target_pid && + foreground_pid != target_pid; wc->cursor_check_time = 0.0f; } obs_enter_graphics(); - GetClientRect(wc->window, &rect); + if (wc->method == METHOD_BITBLT) { + GetClientRect(wc->window, &rect); - if (!reset_capture) { - wc->resize_timer += seconds; + if (!reset_capture) { + wc->resize_timer += seconds; - if (wc->resize_timer >= RESIZE_CHECK_TIME) { - if (rect.bottom != wc->last_rect.bottom || - rect.right != wc->last_rect.right) - reset_capture = true; + if (wc->resize_timer >= RESIZE_CHECK_TIME) { + if ((rect.bottom - rect.top) != + (wc->last_rect.bottom - + wc->last_rect.top) || + (rect.right - rect.left) != + (wc->last_rect.right - + wc->last_rect.left)) + reset_capture = true; + wc->resize_timer = 0.0f; + } + } + + if (reset_capture) { wc->resize_timer = 0.0f; + wc->last_rect = rect; + dc_capture_free(&wc->capture); + dc_capture_init(&wc->capture, 0, 0, + rect.right - rect.left, + rect.bottom - rect.top, wc->cursor, + wc->compatibility); + } + + dc_capture_capture(&wc->capture, wc->window); + } else if (wc->method == METHOD_WGC) { + if (wc->window && (wc->capture_winrt == NULL)) { + wc->capture_winrt = wc->exports.winrt_capture_init( + wc->cursor, wc->window); } } - if (reset_capture) { - wc->resize_timer = 0.0f; - wc->last_rect = rect; - dc_capture_free(&wc->capture); - dc_capture_init(&wc->capture, 0, 0, rect.right, rect.bottom, - wc->cursor, wc->compatibility); - } - - dc_capture_capture(&wc->capture, wc->window); obs_leave_graphics(); } static void wc_render(void *data, gs_effect_t *effect) { struct window_capture *wc = data; - dc_capture_render(&wc->capture, obs_get_base_effect(OBS_EFFECT_OPAQUE)); + gs_effect_t *const opaque = obs_get_base_effect(OBS_EFFECT_OPAQUE); + if (wc->method == METHOD_WGC) + wc->exports.winrt_capture_render(wc->capture_winrt, opaque); + else + dc_capture_render(&wc->capture, opaque); UNUSED_PARAMETER(effect); } @@ -257,6 +400,7 @@ struct obs_source_info window_capture_info = { .destroy = wc_destroy, .update = wc_update, .video_render = wc_render, + .hide = wc_hide, .video_tick = wc_tick, .get_width = wc_width, .get_height = wc_height,