Преглед изворни кода

win-capture: Windows Graphics Capture support

Users will now have the option of legacy window capture via BitBlt, or
Windows Graphics Capture, which is new to Windows 10.

There are two annoyances with the new capture method though. One is that
there is a bright, yellow border added to the original window (but not
the OBS view of it). The other is that the mouse cursor is always
captured, and we won't be able to capture without cursor until a later
version of Windows 10 is released.

It should also be noted that DPI scaling is now applied, which may
result in blurrier images caused by Windows rescaling.
jpark37 пре 6 година
родитељ
комит
ffc48dbbab
2 измењених фајлова са 174 додато и 27 уклоњено
  1. 3 0
      plugins/win-capture/data/locale/en-US.ini
  2. 171 27
      plugins/win-capture/window-capture.c

+ 3 - 0
plugins/win-capture/data/locale/en-US.ini

@@ -1,6 +1,9 @@
 MonitorCapture="Display Capture"
 MonitorCapture="Display Capture"
 WindowCapture="Window Capture"
 WindowCapture="Window Capture"
 WindowCapture.Window="Window"
 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="Window Match Priority"
 WindowCapture.Priority.Title="Window title must match"
 WindowCapture.Priority.Title="Window title must match"
 WindowCapture.Priority.Class="Match title, otherwise find window of same type"
 WindowCapture.Priority.Class="Match title, otherwise find window of same type"

+ 171 - 27
plugins/win-capture/window-capture.c

@@ -2,11 +2,16 @@
 #include <util/dstr.h>
 #include <util/dstr.h>
 #include "dc-capture.h"
 #include "dc-capture.h"
 #include "window-helpers.h"
 #include "window-helpers.h"
+#include "../../libobs/util/platform.h"
+#include "../../libobs-winrt/winrt-capture.h"
 
 
 /* clang-format off */
 /* clang-format off */
 
 
 #define TEXT_WINDOW_CAPTURE obs_module_text("WindowCapture")
 #define TEXT_WINDOW_CAPTURE obs_module_text("WindowCapture")
 #define TEXT_WINDOW         obs_module_text("WindowCapture.Window")
 #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_PRIORITY obs_module_text("WindowCapture.Priority")
 #define TEXT_MATCH_TITLE    obs_module_text("WindowCapture.Priority.Title")
 #define TEXT_MATCH_TITLE    obs_module_text("WindowCapture.Priority.Title")
 #define TEXT_MATCH_CLASS    obs_module_text("WindowCapture.Priority.Class")
 #define TEXT_MATCH_CLASS    obs_module_text("WindowCapture.Priority.Class")
@@ -18,12 +23,28 @@
 
 
 #define WC_CHECK_TIMER 1.0f
 #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 {
 struct window_capture {
 	obs_source_t *source;
 	obs_source_t *source;
 
 
 	char *title;
 	char *title;
 	char *class;
 	char *class;
 	char *executable;
 	char *executable;
+	enum window_capture_method method;
 	enum window_priority priority;
 	enum window_priority priority;
 	bool cursor;
 	bool cursor;
 	bool compatibility;
 	bool compatibility;
@@ -31,6 +52,11 @@ struct window_capture {
 
 
 	struct dc_capture capture;
 	struct dc_capture capture;
 
 
+	bool wgc_supported;
+	void *winrt_module;
+	struct winrt_exports exports;
+	struct winrt_capture *capture_winrt;
+
 	float resize_timer;
 	float resize_timer;
 	float check_window_timer;
 	float check_window_timer;
 	float cursor_check_time;
 	float cursor_check_time;
@@ -41,6 +67,7 @@ struct window_capture {
 
 
 static void update_settings(struct window_capture *wc, obs_data_t *s)
 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");
 	const char *window = obs_data_get_string(s, "window");
 	int priority = (int)obs_data_get_int(s, "priority");
 	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);
 		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->priority = (enum window_priority)priority;
 	wc->cursor = obs_data_get_bool(s, "cursor");
 	wc->cursor = obs_data_get_bool(s, "cursor");
 	wc->use_wildcards = obs_data_get_bool(s, "use_wildcards");
 	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;
 	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)
 static void *wc_create(obs_data_t *settings, obs_source_t *source)
 {
 {
 	struct window_capture *wc = bzalloc(sizeof(struct window_capture));
 	struct window_capture *wc = bzalloc(sizeof(struct window_capture));
 	wc->source = source;
 	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);
 	update_settings(wc, settings);
 	return wc;
 	return wc;
 }
 }
@@ -94,6 +168,9 @@ static void wc_destroy(void *data)
 		bfree(wc->class);
 		bfree(wc->class);
 		bfree(wc->executable);
 		bfree(wc->executable);
 
 
+		if (wc->winrt_module)
+			os_dlclose(wc->winrt_module);
+
 		bfree(wc);
 		bfree(wc);
 	}
 	}
 }
 }
@@ -111,28 +188,55 @@ static void wc_update(void *data, obs_data_t *settings)
 static uint32_t wc_width(void *data)
 static uint32_t wc_width(void *data)
 {
 {
 	struct window_capture *wc = 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)
 static uint32_t wc_height(void *data)
 {
 {
 	struct window_capture *wc = 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)
 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, "cursor", true);
 	obs_data_set_default_bool(defaults, "compatibility", false);
 	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_properties_t *ppts = obs_properties_create();
 	obs_property_t *p;
 	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,
 	p = obs_properties_add_list(ppts, "window", TEXT_WINDOW,
 				    OBS_COMBO_TYPE_LIST,
 				    OBS_COMBO_TYPE_LIST,
 				    OBS_COMBO_FORMAT_STRING);
 				    OBS_COMBO_FORMAT_STRING);
@@ -151,6 +255,18 @@ static obs_properties_t *wc_properties(void *unused)
 	return ppts;
 	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 RESIZE_CHECK_TIME 0.2f
 #define CURSOR_CHECK_TIME 0.2f
 #define CURSOR_CHECK_TIME 0.2f
 
 
@@ -175,10 +291,22 @@ static void wc_tick(void *data, float seconds)
 			return;
 			return;
 		}
 		}
 
 
+		if (wc->capture_winrt) {
+			wc->exports.winrt_capture_free(wc->capture_winrt);
+			wc->capture_winrt = NULL;
+		}
+
 		wc->check_window_timer = 0.0f;
 		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->window) {
 			if (wc->capture.valid)
 			if (wc->capture.valid)
 				dc_capture_free(&wc->capture);
 				dc_capture_free(&wc->capture);
@@ -203,47 +331,62 @@ static void wc_tick(void *data, float seconds)
 		if (!GetWindowThreadProcessId(wc->window, &target_pid))
 		if (!GetWindowThreadProcessId(wc->window, &target_pid))
 			target_pid = 0;
 			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;
 		wc->cursor_check_time = 0.0f;
 	}
 	}
 
 
 	obs_enter_graphics();
 	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->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);
 		}
 		}
-	}
 
 
-	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);
+	} 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);
+		}
 	}
 	}
 
 
-	dc_capture_capture(&wc->capture, wc->window);
 	obs_leave_graphics();
 	obs_leave_graphics();
 }
 }
 
 
 static void wc_render(void *data, gs_effect_t *effect)
 static void wc_render(void *data, gs_effect_t *effect)
 {
 {
 	struct window_capture *wc = data;
 	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);
 	UNUSED_PARAMETER(effect);
 }
 }
@@ -257,6 +400,7 @@ struct obs_source_info window_capture_info = {
 	.destroy = wc_destroy,
 	.destroy = wc_destroy,
 	.update = wc_update,
 	.update = wc_update,
 	.video_render = wc_render,
 	.video_render = wc_render,
+	.hide = wc_hide,
 	.video_tick = wc_tick,
 	.video_tick = wc_tick,
 	.get_width = wc_width,
 	.get_width = wc_width,
 	.get_height = wc_height,
 	.get_height = wc_height,