From 426535f3a6dccb8d08b98571ac4a8414b0540a38 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Fri, 7 Oct 2022 04:20:29 +0900 Subject: [PATCH] d3d11screencapture: Add WinRT API based capture mode Add Windows Graphics Capture (WGC) API based screen capture mode. The conditions where this mode is used: * Explicitly requested by user (capture-api property) * To capture specific window * When DXGI desktop duplication API does not work on hybrid graphics systems (e.g., multi-gpu laptop) Full features of this implementation require Windows 11. And Windows 11 SDK is required to build this feature. Part-of: --- .../sys/d3d11/gstd3d11screencapture.cpp | 41 + .../sys/d3d11/gstd3d11screencapture.h | 36 +- .../sys/d3d11/gstd3d11screencapturesrc.cpp | 329 ++++-- .../sys/d3d11/gstd3d11winrtcapture.cpp | 992 ++++++++++++++++++ .../sys/d3d11/gstd3d11winrtcapture.h | 41 + .../gst-plugins-bad/sys/d3d11/meson.build | 25 + 6 files changed, 1382 insertions(+), 82 deletions(-) create mode 100644 subprojects/gst-plugins-bad/sys/d3d11/gstd3d11winrtcapture.cpp create mode 100644 subprojects/gst-plugins-bad/sys/d3d11/gstd3d11winrtcapture.h diff --git a/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapture.cpp b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapture.cpp index 1324b26f82..2506253942 100644 --- a/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapture.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapture.cpp @@ -93,6 +93,47 @@ gst_d3d11_screen_capture_get_colorimetry (GstD3D11ScreenCapture * capture, return klass->get_colorimetry (capture, colorimetry); } +gboolean +gst_d3d11_screen_capture_unlock (GstD3D11ScreenCapture * capture) +{ + GstD3D11ScreenCaptureClass *klass; + + g_return_val_if_fail (GST_IS_D3D11_SCREEN_CAPTURE (capture), FALSE); + klass = GST_D3D11_SCREEN_CAPTURE_GET_CLASS (capture); + + if (klass->unlock) + return klass->unlock (capture); + + return TRUE; +} + +gboolean +gst_d3d11_screen_capture_unlock_stop (GstD3D11ScreenCapture * capture) +{ + GstD3D11ScreenCaptureClass *klass; + + g_return_val_if_fail (GST_IS_D3D11_SCREEN_CAPTURE (capture), FALSE); + klass = GST_D3D11_SCREEN_CAPTURE_GET_CLASS (capture); + + if (klass->unlock_stop) + return klass->unlock_stop (capture); + + return TRUE; +} + +void +gst_d3d11_screen_capture_show_border (GstD3D11ScreenCapture * capture, + gboolean show) +{ + GstD3D11ScreenCaptureClass *klass; + + g_return_if_fail (GST_IS_D3D11_SCREEN_CAPTURE (capture)); + klass = GST_D3D11_SCREEN_CAPTURE_GET_CLASS (capture); + + if (klass->show_border) + klass->show_border (capture, show); +} + GstFlowReturn gst_d3d11_screen_capture_do_capture (GstD3D11ScreenCapture * capture, GstD3D11Device * device, ID3D11Texture2D * texture, diff --git a/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapture.h b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapture.h index e4878e4dfe..dbaef6496a 100644 --- a/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapture.h +++ b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapture.h @@ -59,6 +59,13 @@ struct _GstD3D11ScreenCaptureClass gboolean (*get_colorimetry) (GstD3D11ScreenCapture * capture, GstVideoColorimetry * colorimetry); + gboolean (*unlock) (GstD3D11ScreenCapture * capture); + + gboolean (*unlock_stop) (GstD3D11ScreenCapture * capture); + + void (*show_border) (GstD3D11ScreenCapture * capture, + gboolean show); + GstFlowReturn (*do_capture) (GstD3D11ScreenCapture * capture, GstD3D11Device * device, ID3D11Texture2D * texture, @@ -83,17 +90,24 @@ gboolean gst_d3d11_screen_capture_get_size (GstD3D11ScreenCapture * captu gboolean gst_d3d11_screen_capture_get_colorimetry (GstD3D11ScreenCapture * capture, GstVideoColorimetry * colorimetry); -GstFlowReturn gst_d3d11_screen_capture_do_capture (GstD3D11ScreenCapture * capture, - GstD3D11Device * device, - ID3D11Texture2D * texture, - ID3D11RenderTargetView * rtv, - ID3D11VertexShader * vs, - ID3D11PixelShader * ps, - ID3D11InputLayout * layout, - ID3D11SamplerState * sampler, - ID3D11BlendState * blend, - D3D11_BOX * crop_box, - gboolean draw_mouse); +gboolean gst_d3d11_screen_capture_unlock (GstD3D11ScreenCapture * capture); + +gboolean gst_d3d11_screen_capture_unlock_stop (GstD3D11ScreenCapture * capture); + +void gst_d3d11_screen_capture_show_border (GstD3D11ScreenCapture * capture, + gboolean show); + +GstFlowReturn gst_d3d11_screen_capture_do_capture (GstD3D11ScreenCapture * capture, + GstD3D11Device * device, + ID3D11Texture2D * texture, + ID3D11RenderTargetView * rtv, + ID3D11VertexShader * vs, + ID3D11PixelShader * ps, + ID3D11InputLayout * layout, + ID3D11SamplerState * sampler, + ID3D11BlendState * blend, + D3D11_BOX * crop_box, + gboolean draw_mouse); HRESULT gst_d3d11_screen_capture_find_output_for_monitor (HMONITOR monitor, IDXGIAdapter1 ** adapter, diff --git a/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapturesrc.cpp b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapturesrc.cpp index f8ceb1eca6..815a77cdf4 100644 --- a/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapturesrc.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11screencapturesrc.cpp @@ -38,6 +38,9 @@ #include "gstd3d11screencapturesrc.h" #include "gstd3d11dxgicapture.h" +#ifdef HAVE_WINRT_CAPTURE +#include "gstd3d11winrtcapture.h" +#endif #include "gstd3d11pluginutils.h" #include #include @@ -59,14 +62,44 @@ enum PROP_CROP_Y, PROP_CROP_WIDTH, PROP_CROP_HEIGHT, - - PROP_LAST, + PROP_WINDOW_HANDLE, + PROP_SHOW_BORDER, + PROP_CAPTURE_API, + PROP_ADAPTER, }; -static GParamSpec *properties[PROP_LAST]; +typedef enum +{ + GST_D3D11_SCREEN_CAPTURE_API_DXGI, + GST_D3D11_SCREEN_CAPTURE_API_WGC, +} GstD3D11ScreenCaptureAPI; + +#ifdef HAVE_WINRT_CAPTURE +#define GST_TYPE_D3D11_SCREEN_CAPTURE_API (gst_d3d11_screen_capture_api_get_type()) +static GType +gst_d3d11_screen_capture_api_get_type (void) +{ + static GType api_type = 0; + + GST_D3D11_CALL_ONCE_BEGIN { + static const GEnumValue api_types[] = { + {GST_D3D11_SCREEN_CAPTURE_API_DXGI, "DXGI Desktop Duplication", "dxgi"}, + {GST_D3D11_SCREEN_CAPTURE_API_WGC, "Windows Graphics Capture", "wgc"}, + {0, nullptr, nullptr}, + }; + + api_type = g_enum_register_static ("GstD3D11ScreenCaptureAPI", api_types); + } GST_D3D11_CALL_ONCE_END; + + return api_type; +} +#endif #define DEFAULT_MONITOR_INDEX -1 #define DEFAULT_SHOW_CURSOR FALSE +#define DEFAULT_SHOW_BORDER FALSE +#define DEFAULT_CAPTURE_API GST_D3D11_SCREEN_CAPTURE_API_DXGI +#define DEFAULT_ADAPTER -1 static GstStaticCaps template_caps = GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES @@ -89,7 +122,11 @@ struct _GstD3D11ScreenCaptureSrc gint64 adapter_luid; gint monitor_index; HMONITOR monitor_handle; + HWND window_handle; gboolean show_cursor; + gboolean show_border; + GstD3D11ScreenCaptureAPI capture_api; + gint adapter; guint crop_x; guint crop_y; @@ -108,9 +145,12 @@ struct _GstD3D11ScreenCaptureSrc ID3D11InputLayout *layout; ID3D11SamplerState *sampler; ID3D11BlendState *blend; + + CRITICAL_SECTION lock; }; static void gst_d3d11_screen_capture_src_dispose (GObject * object); +static void gst_d3d11_screen_capture_src_finalize (GObject * object); static void gst_d3d11_screen_capture_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_d3d11_screen_capture_src_get_property (GObject * object, @@ -150,61 +190,93 @@ gst_d3d11_screen_capture_src_class_init (GstD3D11ScreenCaptureSrcClass * klass) GstCaps *caps; gobject_class->dispose = gst_d3d11_screen_capture_src_dispose; + gobject_class->finalize = gst_d3d11_screen_capture_src_finalize; gobject_class->set_property = gst_d3d11_screen_capture_src_set_property; gobject_class->get_property = gst_d3d11_screen_capture_src_get_property; - properties[PROP_MONITOR_INDEX] = + g_object_class_install_property (gobject_class, PROP_MONITOR_INDEX, g_param_spec_int ("monitor-index", "Monitor Index", - "Zero-based index for monitor to capture (-1 = primary monitor)", - -1, G_MAXINT, DEFAULT_MONITOR_INDEX, - (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | - G_PARAM_STATIC_STRINGS)); + "Zero-based index for monitor to capture (-1 = primary monitor)", + -1, G_MAXINT, DEFAULT_MONITOR_INDEX, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + G_PARAM_STATIC_STRINGS))); - properties[PROP_MONITOR_HANDLE] = + g_object_class_install_property (gobject_class, PROP_MONITOR_HANDLE, g_param_spec_uint64 ("monitor-handle", "Monitor Handle", - "A HMONITOR handle of monitor to capture", - 0, G_MAXUINT64, 0, - (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | - G_PARAM_STATIC_STRINGS)); + "A HMONITOR handle of monitor to capture", + 0, G_MAXUINT64, 0, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + G_PARAM_STATIC_STRINGS))); - properties[PROP_SHOW_CURSOR] = + g_object_class_install_property (gobject_class, PROP_SHOW_CURSOR, g_param_spec_boolean ("show-cursor", - "Show Mouse Cursor", "Whether to show mouse cursor", - DEFAULT_SHOW_CURSOR, - (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + "Show Mouse Cursor", "Whether to show mouse cursor", + DEFAULT_SHOW_CURSOR, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - properties[PROP_CROP_X] = + g_object_class_install_property (gobject_class, PROP_CROP_X, g_param_spec_uint ("crop-x", "Crop X", - "Horizontal coordinate of top left corner for the screen capture area", - 0, G_MAXUINT, 0, - (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + "Horizontal coordinate of top left corner for the screen capture area", + 0, G_MAXUINT, 0, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - properties[PROP_CROP_Y] = + g_object_class_install_property (gobject_class, PROP_CROP_Y, g_param_spec_uint ("crop-y", "Crop Y", - "Vertical coordinate of top left corner for the screen capture area", - 0, G_MAXUINT, 0, - (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + "Vertical coordinate of top left corner for the screen capture area", + 0, G_MAXUINT, 0, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - properties[PROP_CROP_WIDTH] = + g_object_class_install_property (gobject_class, PROP_CROP_WIDTH, g_param_spec_uint ("crop-width", "Crop Width", - "Width of screen capture area (0 = maximum)", - 0, G_MAXUINT, 0, - (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + "Width of screen capture area (0 = maximum)", + 0, G_MAXUINT, 0, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - properties[PROP_CROP_HEIGHT] = + g_object_class_install_property (gobject_class, PROP_CROP_HEIGHT, g_param_spec_uint ("crop-height", "Crop Height", - "Height of screen capture area (0 = maximum)", - 0, G_MAXUINT, 0, - (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + "Height of screen capture area (0 = maximum)", + 0, G_MAXUINT, 0, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - g_object_class_install_properties (gobject_class, PROP_LAST, properties); +#ifdef HAVE_WINRT_CAPTURE + if (gst_d3d11_winrt_capture_load_library ()) { + g_object_class_install_property (gobject_class, PROP_WINDOW_HANDLE, + g_param_spec_uint64 ("window-handle", "Window Handle", + "A HWND handle of window to capture", + 0, G_MAXUINT64, 0, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (gobject_class, PROP_SHOW_BORDER, + g_param_spec_boolean ("show-border", "Show Border", + "Show border lines to capture area when WGC mode is selected", + DEFAULT_SHOW_BORDER, + (GParamFlags) (GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_READWRITE + | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (gobject_class, PROP_CAPTURE_API, + g_param_spec_enum ("capture-api", "Capture API", "Capture API to use", + GST_TYPE_D3D11_SCREEN_CAPTURE_API, + DEFAULT_CAPTURE_API, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (gobject_class, PROP_ADAPTER, + g_param_spec_int ("adapter", "Adapter", + "DXGI Adapter index for creating device when WGC mode is selected " + "(-1 for default)", + -1, G_MAXINT32, DEFAULT_ADAPTER, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_STATIC_STRINGS))); + } +#endif element_class->set_context = GST_DEBUG_FUNCPTR (gst_d3d11_screen_capture_src_set_context); gst_element_class_set_static_metadata (element_class, "Direct3D11 screen capture src", "Source/Video", - "Capture desktop image by using Desktop Duplication API", + "Captures desktop screen", "Seungha Yang "); caps = gst_d3d11_get_updated_template_caps (&template_caps); @@ -228,7 +300,6 @@ gst_d3d11_screen_capture_src_class_init (GstD3D11ScreenCaptureSrcClass * klass) GST_DEBUG_FUNCPTR (gst_d3d11_screen_capture_src_unlock_stop); basesrc_class->query = GST_DEBUG_FUNCPTR (gst_d3d11_screen_capture_src_src_query); - basesrc_class->create = GST_DEBUG_FUNCPTR (gst_d3d11_screen_capture_src_create); } @@ -241,8 +312,13 @@ gst_d3d11_screen_capture_src_init (GstD3D11ScreenCaptureSrc * self) self->monitor_index = DEFAULT_MONITOR_INDEX; self->show_cursor = DEFAULT_SHOW_CURSOR; + self->show_border = DEFAULT_SHOW_BORDER; + self->capture_api = DEFAULT_CAPTURE_API; + self->adapter = DEFAULT_ADAPTER; self->min_latency = GST_CLOCK_TIME_NONE; self->max_latency = GST_CLOCK_TIME_NONE; + + InitializeCriticalSection (&self->lock); } static void @@ -256,11 +332,22 @@ gst_d3d11_screen_capture_src_dispose (GObject * object) G_OBJECT_CLASS (parent_class)->dispose (object); } +static void +gst_d3d11_screen_capture_src_finalize (GObject * object) +{ + GstD3D11ScreenCaptureSrc *self = GST_D3D11_SCREEN_CAPTURE_SRC (object); + + DeleteCriticalSection (&self->lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + static void gst_d3d11_screen_capture_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstD3D11ScreenCaptureSrc *self = GST_D3D11_SCREEN_CAPTURE_SRC (object); + GstD3D11CSLockGuard lk (&self->lock); switch (prop_id) { case PROP_MONITOR_INDEX: @@ -284,6 +371,20 @@ gst_d3d11_screen_capture_src_set_property (GObject * object, guint prop_id, case PROP_CROP_HEIGHT: self->crop_h = g_value_get_uint (value); break; + case PROP_WINDOW_HANDLE: + self->window_handle = (HWND) g_value_get_uint64 (value); + break; + case PROP_SHOW_BORDER: + self->show_border = g_value_get_boolean (value); + if (self->capture) + gst_d3d11_screen_capture_show_border (self->capture, self->show_border); + break; + case PROP_CAPTURE_API: + self->capture_api = (GstD3D11ScreenCaptureAPI) g_value_get_enum (value); + break; + case PROP_ADAPTER: + self->adapter = g_value_get_int (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -318,6 +419,18 @@ gst_d3d11_screen_capture_src_get_property (GObject * object, guint prop_id, case PROP_CROP_HEIGHT: g_value_set_uint (value, self->crop_h); break; + case PROP_WINDOW_HANDLE: + g_value_set_uint64 (value, (guint64) self->window_handle); + break; + case PROP_SHOW_BORDER: + g_value_set_boolean (value, self->show_border); + break; + case PROP_CAPTURE_API: + g_value_set_enum (value, self->capture_api); + break; + case PROP_ADAPTER: + g_value_set_int (value, self->adapter); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -329,9 +442,15 @@ gst_d3d11_screen_capture_src_set_context (GstElement * element, GstContext * context) { GstD3D11ScreenCaptureSrc *self = GST_D3D11_SCREEN_CAPTURE_SRC (element); + GstD3D11CSLockGuard lk (&self->lock); - gst_d3d11_handle_set_context_for_adapter_luid (element, - context, self->adapter_luid, &self->device); + if (self->capture_api == GST_D3D11_SCREEN_CAPTURE_API_DXGI) { + gst_d3d11_handle_set_context_for_adapter_luid (element, + context, self->adapter_luid, &self->device); + } else { + gst_d3d11_handle_set_context (element, + context, self->adapter, &self->device); + } GST_ELEMENT_CLASS (parent_class)->set_context (element, context); } @@ -375,7 +494,7 @@ gst_d3d11_screen_capture_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter) GstVideoColorimetry color; if (!self->capture) { - GST_DEBUG_OBJECT (self, "Duplication object is not configured yet"); + GST_DEBUG_OBJECT (self, "capture object is not configured yet"); return gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (bsrc)); } @@ -728,72 +847,130 @@ gst_d3d11_screen_capture_src_start (GstBaseSrc * bsrc) ComPtr < IDXGIAdapter1 > adapter; DXGI_ADAPTER_DESC desc; HRESULT hr; + GstD3D11ScreenCapture *capture = nullptr; + GstD3D11ScreenCaptureAPI capture_api = self->capture_api; - if (monitor) { - hr = gst_d3d11_screen_capture_find_output_for_monitor (monitor, - &adapter, nullptr); - } else if (self->monitor_index < 0) { - hr = gst_d3d11_screen_capture_find_primary_monitor (&monitor, - &adapter, nullptr); + EnterCriticalSection (&self->lock); + if (self->window_handle) { + self->capture_api = GST_D3D11_SCREEN_CAPTURE_API_WGC; } else { - hr = gst_d3d11_screen_capture_find_nth_monitor (self->monitor_index, - &monitor, &adapter, nullptr); + if (monitor) { + hr = gst_d3d11_screen_capture_find_output_for_monitor (monitor, + &adapter, nullptr); + } else if (self->monitor_index < 0) { + hr = gst_d3d11_screen_capture_find_primary_monitor (&monitor, + &adapter, nullptr); + } else { + hr = gst_d3d11_screen_capture_find_nth_monitor (self->monitor_index, + &monitor, &adapter, nullptr); + } + + if (FAILED (hr)) + goto error; } - if (FAILED (hr)) - goto error; + if (self->capture_api == GST_D3D11_SCREEN_CAPTURE_API_DXGI) { + hr = adapter->GetDesc (&desc); + if (FAILED (hr)) + goto error; - hr = adapter->GetDesc (&desc); - if (FAILED (hr)) - goto error; + self->adapter_luid = gst_d3d11_luid_to_int64 (&desc.AdapterLuid); + gst_clear_object (&self->device); - self->adapter_luid = gst_d3d11_luid_to_int64 (&desc.AdapterLuid); - gst_clear_object (&self->device); - - if (!gst_d3d11_ensure_element_data_for_adapter_luid (GST_ELEMENT_CAST (self), - self->adapter_luid, &self->device)) { - GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, - ("D3D11 device for LUID %" G_GINT64_FORMAT " is unavailble", - self->adapter_luid), (nullptr)); - - return FALSE; + gst_d3d11_ensure_element_data_for_adapter_luid (GST_ELEMENT_CAST (self), + self->adapter_luid, &self->device); + } else { + gst_clear_object (&self->device); + gst_d3d11_ensure_element_data (GST_ELEMENT_CAST (self), + self->adapter, &self->device); } - self->capture = gst_d3d11_dxgi_capture_new (self->device, monitor); - if (!self->capture) + if (!self->device) + goto no_device; + +#ifdef HAVE_WINRT_CAPTURE + if (self->window_handle) { + capture = gst_d3d11_winrt_capture_new (self->device, nullptr, + self->window_handle); + } else if (self->capture_api == GST_D3D11_SCREEN_CAPTURE_API_WGC) { + capture = gst_d3d11_winrt_capture_new (self->device, monitor, nullptr); + } +#endif + + if (!capture) + capture = gst_d3d11_dxgi_capture_new (self->device, monitor); + + if (!capture) goto error; /* Check if we can open device */ - ret = gst_d3d11_screen_capture_prepare (self->capture); + ret = gst_d3d11_screen_capture_prepare (capture); switch (ret) { case GST_D3D11_SCREEN_CAPTURE_FLOW_EXPECTED_ERROR: case GST_FLOW_OK: break; case GST_D3D11_SCREEN_CAPTURE_FLOW_UNSUPPORTED: - goto unsupported; - default: - goto error; +#ifdef HAVE_WINRT_CAPTURE + /* Try WinRT capture if DXGI capture does not work */ + if (self->capture_api == GST_D3D11_SCREEN_CAPTURE_API_DXGI) { + self->capture_api = GST_D3D11_SCREEN_CAPTURE_API_WGC; + gst_clear_object (&capture); + GST_WARNING_OBJECT (self, "DXGI capture is not available"); + capture = gst_d3d11_winrt_capture_new (self->device, monitor, nullptr); + if (capture && gst_d3d11_screen_capture_prepare (capture) == GST_FLOW_OK) { + GST_INFO_OBJECT (self, "Fallback to Windows Graphics Capture"); + break; + } + } +#endif + goto unsupported; + default: + goto error; } - if (!gst_d3d11_screen_capture_prepare_shader (self)) + if (self->capture_api == GST_D3D11_SCREEN_CAPTURE_API_DXGI && + !gst_d3d11_screen_capture_prepare_shader (self)) { goto error; + } self->last_frame_no = -1; self->min_latency = self->max_latency = GST_CLOCK_TIME_NONE; + gst_d3d11_screen_capture_show_border (capture, self->show_border); + self->capture = capture; + + LeaveCriticalSection (&self->lock); + if (self->capture_api != capture_api) { + GST_INFO_OBJECT (self, "Updated capture api: %d", self->capture_api); + g_object_notify (G_OBJECT (self), "capture-api"); + } + return TRUE; +no_device: + { + LeaveCriticalSection (&self->lock); + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("D3D11 device is not available"), (nullptr)); + return FALSE; + } + error: { + gst_clear_object (&capture); + LeaveCriticalSection (&self->lock); GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("Failed to prepare capture object with given configuration, " - "monitor-index: %d, monitor-handle: %p", - self->monitor_index, self->monitor_handle), (nullptr)); + "monitor-index: %d, monitor-handle: %p, window-handle: %p", + self->monitor_index, self->monitor_handle, self->window_handle), + (nullptr)); return FALSE; } unsupported: { + gst_clear_object (&capture); + LeaveCriticalSection (&self->lock); GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, ("Failed to prepare capture object with given configuration, " "monitor-index: %d, monitor-handle: %p", @@ -807,6 +984,7 @@ static gboolean gst_d3d11_screen_capture_src_stop (GstBaseSrc * bsrc) { GstD3D11ScreenCaptureSrc *self = GST_D3D11_SCREEN_CAPTURE_SRC (bsrc); + GstD3D11CSLockGuard lk (&self->lock); if (self->pool) { gst_buffer_pool_set_active (self->pool, FALSE); @@ -831,6 +1009,9 @@ gst_d3d11_screen_capture_src_unlock (GstBaseSrc * bsrc) GstD3D11ScreenCaptureSrc *self = GST_D3D11_SCREEN_CAPTURE_SRC (bsrc); GST_OBJECT_LOCK (self); + if (self->capture) + gst_d3d11_screen_capture_unlock (self->capture); + if (self->clock_id) { GST_DEBUG_OBJECT (self, "Waking up waiting clock"); gst_clock_id_unschedule (self->clock_id); @@ -847,6 +1028,9 @@ gst_d3d11_screen_capture_src_unlock_stop (GstBaseSrc * bsrc) GstD3D11ScreenCaptureSrc *self = GST_D3D11_SCREEN_CAPTURE_SRC (bsrc); GST_OBJECT_LOCK (self); + if (self->capture) + gst_d3d11_screen_capture_unlock_stop (self->capture); + self->flushing = FALSE; GST_OBJECT_UNLOCK (self); @@ -1079,6 +1263,9 @@ again: break; } + if (ret != GST_FLOW_OK) + goto out; + if (!self->downstream_supports_d3d11) { ret = GST_BASE_SRC_CLASS (parent_class)->alloc (bsrc, offset, size, &sysmem_buf); diff --git a/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11winrtcapture.cpp b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11winrtcapture.cpp new file mode 100644 index 0000000000..7d6f0d1b9f --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11winrtcapture.cpp @@ -0,0 +1,992 @@ +/* + * GStreamer + * Copyright (C) 2022 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WINAPI_PARTITION_APP +#undef WINAPI_PARTITION_APP +#endif + +#define WINAPI_PARTITION_APP 1 + +#include "gstd3d11winrtcapture.h" +#include "gstd3d11pluginutils.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_WINMM +#include +#endif + +GST_DEBUG_CATEGORY_EXTERN (gst_d3d11_screen_capture_debug); +#define GST_CAT_DEFAULT gst_d3d11_screen_capture_debug + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Graphics; +using namespace ABI::Windows::Graphics::Capture; +using namespace ABI::Windows::Graphics::DirectX; +using namespace ABI::Windows::Graphics::DirectX::Direct3D11; +using namespace Windows::Graphics::DirectX::Direct3D11; + +static SRWLOCK capture_list_lock = SRWLOCK_INIT; +static GList *capture_list = nullptr; + +#define D3D11_WINRT_CAPTURE_PROP_NAME "gst-d3d11-winrt-capture" +#define WM_GST_D3D11_WINRT_CAPTURE_CLOSED (WM_USER + 1) + +typedef struct +{ + gboolean loaded; + + /* d3d11.dll */ + HRESULT (WINAPI * CreateDirect3D11DeviceFromDXGIDevice) (IDXGIDevice * + dxgi_device, IInspectable ** graphics_device); + + /* combase.dll */ + HRESULT (WINAPI * RoInitialize) (RO_INIT_TYPE init_type); + HRESULT (WINAPI * RoUninitialize) (void); + HRESULT (WINAPI * WindowsCreateString) (PCNZWCH source_string, + UINT32 length, HSTRING * string); + HRESULT (WINAPI * WindowsDeleteString) (HSTRING string); + HRESULT (WINAPI * RoGetActivationFactory) (HSTRING activatable_class_id, + REFIID iid, void ** factory); +} GstD3D11WinRTVTable; + +static GstD3D11WinRTVTable winrt_vtable = { FALSE, }; + +template < typename InterfaceType, PCNZWCH runtime_class_id > +static HRESULT +GstGetActivationFactory (InterfaceType ** factory) +{ + if (!gst_d3d11_winrt_capture_load_library ()) + return E_NOINTERFACE; + + HSTRING class_id_hstring; + HRESULT hr = winrt_vtable.WindowsCreateString (runtime_class_id, + wcslen (runtime_class_id), &class_id_hstring); + + if (FAILED (hr)) + return hr; + + hr = winrt_vtable.RoGetActivationFactory (class_id_hstring, + IID_PPV_ARGS (factory)); + + if (FAILED (hr)) { + winrt_vtable.WindowsDeleteString (class_id_hstring); + return hr; + } + + return winrt_vtable.WindowsDeleteString (class_id_hstring); +} + +#define CLOSE_COM(obj) G_STMT_START { \ + if (obj) { \ + ComPtr closable; \ + obj.As (&closable); \ + if (closable) \ + closable->Close (); \ + obj = nullptr; \ + } \ +} G_STMT_END + +struct GstD3D11WinRTCaptureInner +{ + ~GstD3D11WinRTCaptureInner() + { + if (item) + item->remove_Closed (closed_token); + + CLOSE_COM (session); + CLOSE_COM (pool); + CLOSE_COM (item); + CLOSE_COM (d3d_device); + } + + STDMETHODIMP + OnClosed (IGraphicsCaptureItem * item, IInspectable * args) + { + GST_WARNING ("Item %p got closed", this); + this->closed = true; + + return S_OK; + } + + ComPtr < IDirect3DDevice > d3d_device; + ComPtr < IGraphicsCaptureItem > item; + ComPtr < IDirect3D11CaptureFramePool > pool; + ComPtr < IGraphicsCaptureSession > session; + EventRegistrationToken closed_token; + + bool closed = false; +}; +/* *INDENT-ON* */ + +#define LOAD_SYMBOL(module,name,func) G_STMT_START { \ + if (!g_module_symbol (module, G_STRINGIFY (name), (gpointer *) &winrt_vtable.func)) { \ + GST_WARNING ("Failed to load '%s', %s", G_STRINGIFY (name), g_module_error()); \ + if (d3d11_module) \ + g_module_close (d3d11_module); \ + if (combase_module) \ + g_module_close (combase_module); \ + return; \ + } \ +} G_STMT_END + +gboolean +gst_d3d11_winrt_capture_load_library (void) +{ + static GModule *d3d11_module = nullptr; + static GModule *combase_module = nullptr; + + GST_D3D11_CALL_ONCE_BEGIN { + d3d11_module = g_module_open ("d3d11.dll", G_MODULE_BIND_LAZY); + /* Shouldn't happen... */ + if (!d3d11_module) + return; + + combase_module = g_module_open ("combase.dll", G_MODULE_BIND_LAZY); + if (!combase_module) { + g_module_close (d3d11_module); + return; + } + + LOAD_SYMBOL (d3d11_module, CreateDirect3D11DeviceFromDXGIDevice, + CreateDirect3D11DeviceFromDXGIDevice); + LOAD_SYMBOL (combase_module, RoInitialize, RoInitialize); + LOAD_SYMBOL (combase_module, RoUninitialize, RoUninitialize); + LOAD_SYMBOL (combase_module, WindowsCreateString, WindowsCreateString); + LOAD_SYMBOL (combase_module, WindowsDeleteString, WindowsDeleteString); + LOAD_SYMBOL (combase_module, RoGetActivationFactory, + RoGetActivationFactory); + + winrt_vtable.loaded = TRUE; + } + GST_D3D11_CALL_ONCE_END; + + return winrt_vtable.loaded; +} + +enum +{ + PROP_0, + PROP_D3D11_DEVICE, + PROP_MONITOR_HANDLE, + PROP_WINDOW_HANDLE, +}; + +struct _GstD3D11WinRTCapture +{ + GstD3D11ScreenCapture parent; + + GstD3D11Device *device; + GstD3D11WinRTCaptureInner *inner; + /* Reported by WGC API */ + SizeInt32 pool_size; + /* Actual texture resolution */ + UINT width; + UINT height; + + gboolean flushing; + boolean show_mouse; + boolean show_border; + + GThread *thread; + GMainContext *context; + GMainLoop *loop; + + CRITICAL_SECTION lock; + CONDITION_VARIABLE cond; + LARGE_INTEGER frequency; + + HMONITOR monitor_handle; + HWND window_handle; + + HWND hidden_window; +}; + +static void gst_d3d11_winrt_capture_constructed (GObject * object); +static void gst_d3d11_winrt_capture_dispose (GObject * object); +static void gst_d3d11_winrt_capture_finalize (GObject * object); +static void gst_d3d11_winrt_capture_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); + +static GstFlowReturn +gst_d3d11_winrt_capture_prepare (GstD3D11ScreenCapture * capture); +static gboolean +gst_d3d11_winrt_capture_get_size (GstD3D11ScreenCapture * capture, + guint * width, guint * height); +static gboolean +gst_d3d11_winrt_capture_get_colorimetry (GstD3D11ScreenCapture * capture, + GstVideoColorimetry * colorimetry); +static gboolean +gst_d3d11_winrt_capture_unlock (GstD3D11ScreenCapture * capture); +static gboolean +gst_d3d11_winrt_capture_unlock_stop (GstD3D11ScreenCapture * capture); +static void +gst_d3d11_winrt_capture_show_border (GstD3D11ScreenCapture * capture, + gboolean show); +static GstFlowReturn +gst_d3d11_winrt_capture_do_capture (GstD3D11ScreenCapture * capture, + GstD3D11Device * device, ID3D11Texture2D * texture, + ID3D11RenderTargetView * rtv, ID3D11VertexShader * vs, + ID3D11PixelShader * ps, ID3D11InputLayout * layout, + ID3D11SamplerState * sampler, ID3D11BlendState * blend, + D3D11_BOX * crop_box, gboolean draw_mouse); +static gpointer +gst_d3d11_winrt_capture_thread_func (GstD3D11WinRTCapture * self); + +#define gst_d3d11_winrt_capture_parent_class parent_class +G_DEFINE_TYPE (GstD3D11WinRTCapture, gst_d3d11_winrt_capture, + GST_TYPE_D3D11_SCREEN_CAPTURE); + +static void +gst_d3d11_winrt_capture_class_init (GstD3D11WinRTCaptureClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstD3D11ScreenCaptureClass *capture_class = + GST_D3D11_SCREEN_CAPTURE_CLASS (klass); + + gobject_class->constructed = gst_d3d11_winrt_capture_constructed; + gobject_class->dispose = gst_d3d11_winrt_capture_dispose; + gobject_class->finalize = gst_d3d11_winrt_capture_finalize; + gobject_class->set_property = gst_d3d11_winrt_capture_set_property; + + g_object_class_install_property (gobject_class, PROP_D3D11_DEVICE, + g_param_spec_object ("d3d11device", "D3D11 Device", + "GstD3D11Device object for operating", + GST_TYPE_D3D11_DEVICE, (GParamFlags) + (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (gobject_class, PROP_MONITOR_HANDLE, + g_param_spec_pointer ("monitor-handle", "Monitor Handle", + "A HMONITOR handle of monitor to capture", (GParamFlags) + (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (gobject_class, PROP_WINDOW_HANDLE, + g_param_spec_pointer ("window-handle", "Window Handle", + "A HWND handle of window to capture", (GParamFlags) + (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS))); + + capture_class->prepare = GST_DEBUG_FUNCPTR (gst_d3d11_winrt_capture_prepare); + capture_class->get_size = + GST_DEBUG_FUNCPTR (gst_d3d11_winrt_capture_get_size); + capture_class->get_colorimetry = + GST_DEBUG_FUNCPTR (gst_d3d11_winrt_capture_get_colorimetry); + capture_class->unlock = GST_DEBUG_FUNCPTR (gst_d3d11_winrt_capture_unlock); + capture_class->unlock_stop = + GST_DEBUG_FUNCPTR (gst_d3d11_winrt_capture_unlock_stop); + capture_class->show_border = + GST_DEBUG_FUNCPTR (gst_d3d11_winrt_capture_show_border); + capture_class->do_capture = + GST_DEBUG_FUNCPTR (gst_d3d11_winrt_capture_do_capture); +} + +static void +gst_d3d11_winrt_capture_init (GstD3D11WinRTCapture * self) +{ + InitializeCriticalSection (&self->lock); +} + +static void +gst_d3d11_winrt_capture_constructed (GObject * object) +{ + GstD3D11WinRTCapture *self = GST_D3D11_WINRT_CAPTURE (object); + GstD3D11CSLockGuard lk (&self->lock); + + self->context = g_main_context_new (); + self->loop = g_main_loop_new (self->context, FALSE); + self->thread = g_thread_new ("GstD3D11WinRTCapture", + (GThreadFunc) gst_d3d11_winrt_capture_thread_func, self); + while (!g_main_loop_is_running (self->loop)) + SleepConditionVariableCS (&self->cond, &self->lock, INFINITE); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gst_d3d11_winrt_capture_dispose (GObject * object) +{ + GstD3D11WinRTCapture *self = GST_D3D11_WINRT_CAPTURE (object); + + if (self->loop) + g_main_loop_quit (self->loop); + + g_clear_pointer (&self->thread, g_thread_join); + g_clear_pointer (&self->loop, g_main_loop_unref); + g_clear_pointer (&self->context, g_main_context_unref); + + gst_clear_object (&self->device); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_d3d11_winrt_capture_finalize (GObject * object) +{ + GstD3D11WinRTCapture *self = GST_D3D11_WINRT_CAPTURE (object); + + DeleteCriticalSection (&self->lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_d3d11_winrt_capture_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstD3D11WinRTCapture *self = GST_D3D11_WINRT_CAPTURE (object); + + switch (prop_id) { + case PROP_D3D11_DEVICE: + self->device = (GstD3D11Device *) g_value_dup_object (value); + break; + case PROP_MONITOR_HANDLE: + self->monitor_handle = (HMONITOR) g_value_get_pointer (value); + break; + case PROP_WINDOW_HANDLE: + self->window_handle = (HWND) g_value_get_pointer (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_d3d11_winrt_capture_running_cb (GstD3D11WinRTCapture * self) +{ + GstD3D11CSLockGuard lk (&self->lock); + WakeAllConditionVariable (&self->cond); + + return G_SOURCE_REMOVE; +} + +static void +gst_d3d11_winrt_configure (GstD3D11WinRTCapture * self) +{ + HRESULT hr; + GstD3D11Device *device = self->device; + ComPtr < ID3D10Multithread > multi_thread; + ComPtr < IGraphicsCaptureItemInterop > interop; + ID3D11Device *device_handle; + ComPtr < IDXGIDevice > dxgi_device; + ComPtr < IInspectable > inspectable; + ComPtr < IDirect3D11CaptureFramePoolStatics > pool_statics; + ComPtr < IDirect3D11CaptureFramePoolStatics2 > pool_statics2; + ComPtr < IGraphicsCaptureSession2 > session2; + ComPtr < IGraphicsCaptureSession3 > session3; + GstD3D11WinRTCaptureInner *inner = nullptr; + + device_handle = gst_d3d11_device_get_device_handle (device); + hr = device_handle->QueryInterface (IID_PPV_ARGS (&multi_thread)); + if (!gst_d3d11_result (hr, device)) { + GST_ERROR_OBJECT (self, "ID3D10Multithread interface is unavailable"); + return; + } + + multi_thread->SetMultithreadProtected (TRUE); + + hr = GstGetActivationFactory < IGraphicsCaptureItemInterop, + RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem > (&interop); + if (!gst_d3d11_result (hr, device)) { + GST_WARNING_OBJECT (self, "IGraphicsCaptureItemInterop is not available"); + return; + } + + inner = new GstD3D11WinRTCaptureInner (); + + if (self->monitor_handle) { + hr = interop->CreateForMonitor (self->monitor_handle, + IID_PPV_ARGS (&inner->item)); + } else if (self->window_handle) { + hr = interop->CreateForWindow (self->window_handle, + IID_PPV_ARGS (&inner->item)); + } else { + g_assert_not_reached (); + goto error; + } + + if (!gst_d3d11_result (hr, device)) { + GST_WARNING_OBJECT (self, "Could not create item"); + goto error; + } + + { + /* FIXME: This callback does not work for some reasons */ + auto closed_handler = Callback < ITypedEventHandler < GraphicsCaptureItem *, + IInspectable * >>(inner, &GstD3D11WinRTCaptureInner::OnClosed); + hr = inner->item->add_Closed (closed_handler.Get (), &inner->closed_token); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Could not install closed callback"); + goto error; + } + } + + hr = device_handle->QueryInterface (IID_PPV_ARGS (&dxgi_device)); + if (!gst_d3d11_result (hr, device)) { + GST_WARNING_OBJECT (self, "IDXGIDevice is not available"); + goto error; + } + + hr = winrt_vtable.CreateDirect3D11DeviceFromDXGIDevice (dxgi_device.Get (), + &inspectable); + if (!gst_d3d11_result (hr, device)) { + GST_WARNING_OBJECT (self, "CreateDirect3D11DeviceFromDXGIDevice failed"); + goto error; + } + + hr = inspectable.As (&inner->d3d_device); + if (!gst_d3d11_result (hr, device)) { + GST_WARNING_OBJECT (device, "IDirect3DDevice is not available"); + goto error; + } + + hr = GstGetActivationFactory < IDirect3D11CaptureFramePoolStatics, + RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool > + (&pool_statics); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, + "IDirect3D11CaptureFramePoolStatics is not available"); + goto error; + } + + hr = pool_statics.As (&pool_statics2); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, + "IDirect3D11CaptureFramePoolStatics2 is not available"); + goto error; + } + + hr = inner->item->get_Size (&self->pool_size); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Could not get item size"); + goto error; + } + + self->width = (UINT) self->pool_size.Width; + self->height = (UINT) self->pool_size.Height; + + hr = pool_statics2->CreateFreeThreaded (inner->d3d_device.Get (), + DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized, + 1, self->pool_size, &inner->pool); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Could not setup pool"); + goto error; + } + + hr = inner->pool->CreateCaptureSession (inner->item.Get (), &inner->session); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Could not create session"); + goto error; + } + + inner->session.As (&session2); + if (session2) + session2->put_IsCursorCaptureEnabled (FALSE); + + inner->session.As (&session3); + if (session3) + session3->put_IsBorderRequired (self->show_border); + + hr = inner->session->StartCapture (); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Could not start capture"); + goto error; + } + + self->inner = inner; + + return; + +error: + if (inner) + delete inner; +} + +static LRESULT CALLBACK +gst_d3d11_winrt_capture_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + GstD3D11WinRTCapture *self; + + if (msg == WM_CREATE) { + self = GST_D3D11_WINRT_CAPTURE (((LPCREATESTRUCTA) lparam)->lpCreateParams); + + SetPropA (hwnd, D3D11_WINRT_CAPTURE_PROP_NAME, self); + } else if (GetPropA (hwnd, D3D11_WINRT_CAPTURE_PROP_NAME) && + msg == WM_GST_D3D11_WINRT_CAPTURE_CLOSED) { + HANDLE handle = GetPropA (hwnd, D3D11_WINRT_CAPTURE_PROP_NAME); + + if (!GST_IS_D3D11_WINRT_CAPTURE (handle)) { + GST_WARNING ("%p is not d3d11window object", handle); + return DefWindowProcA (hwnd, msg, wparam, lparam); + } + + self = GST_D3D11_WINRT_CAPTURE (handle); + GST_INFO_OBJECT (self, "Target window got closed"); + GstD3D11CSLockGuard lk (&self->lock); + if (self->inner) + self->inner->closed = true; + WakeAllConditionVariable (&self->cond); + + return 0; + } + + return DefWindowProcA (hwnd, msg, wparam, lparam); +} + +static HWND +gst_d3d11_winrt_create_hidden_window (GstD3D11WinRTCapture * self) +{ + static SRWLOCK lock = SRWLOCK_INIT; + WNDCLASSEXA wc; + ATOM atom; + HINSTANCE inst = GetModuleHandle (nullptr); + + AcquireSRWLockExclusive (&lock); + atom = GetClassInfoExA (inst, "GstD3D11WinRTCapture", &wc); + if (atom == 0) { + ZeroMemory (&wc, sizeof (WNDCLASSEXA)); + + wc.cbSize = sizeof (WNDCLASSEXA); + wc.lpfnWndProc = gst_d3d11_winrt_capture_proc; + wc.hInstance = inst; + wc.style = CS_OWNDC; + wc.lpszClassName = "GstD3D11WinRTCapture"; + + atom = RegisterClassExA (&wc); + ReleaseSRWLockExclusive (&lock); + + if (atom == 0) { + GST_ERROR_OBJECT (self, "Failed to register window class 0x%x", + (guint) GetLastError ()); + return nullptr; + } + } else { + ReleaseSRWLockExclusive (&lock); + } + + return CreateWindowExA (0, "GstD3D11WinRTCapture", "GstD3D11WinRTCapture", + WS_POPUP, 0, 0, 1, 1, nullptr, nullptr, inst, self); +} + +static void CALLBACK +event_hook_func (HWINEVENTHOOK hook, DWORD event, HWND hwnd, LONG id_obj, + LONG id_child, DWORD id_event_thread, DWORD event_time) +{ + if (event != EVENT_OBJECT_DESTROY || id_obj != OBJID_WINDOW || + id_child != INDEXID_CONTAINER || !hwnd) { + return; + } + + GstD3D11SRWLockGuard lk (&capture_list_lock); + GList *iter; + + for (iter = capture_list; iter; iter = g_list_next (iter)) { + GstD3D11WinRTCapture *capture = GST_D3D11_WINRT_CAPTURE (iter->data); + GstD3D11CSLockGuard capture_lk (&capture->lock); + + if (capture->hidden_window && capture->window_handle == hwnd) { + PostMessageA (capture->hidden_window, WM_GST_D3D11_WINRT_CAPTURE_CLOSED, + 0, 0); + return; + } + } +} + +static gboolean +gst_d3d11_winrt_capture_msg_cb (GIOChannel * source, GIOCondition condition, + gpointer data) +{ + MSG msg; + + if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) + return G_SOURCE_CONTINUE; + + TranslateMessage (&msg); + DispatchMessage (&msg); + + return G_SOURCE_CONTINUE; +} + +static void +gst_d3d11_winrt_capture_weak_ref_notify (gpointer data, + GstD3D11WinRTCapture * self) +{ + GstD3D11SRWLockGuard lk (&capture_list_lock); + capture_list = g_list_remove (capture_list, self); +} + +static gpointer +gst_d3d11_winrt_capture_thread_func (GstD3D11WinRTCapture * self) +{ + GSource *source; + GSource *msg_source = nullptr; + GIOChannel *msg_io_channel = nullptr; + HWINEVENTHOOK hook = nullptr; +#if HAVE_WINMM + TIMECAPS time_caps; + guint timer_res = 0; + + if (timeGetDevCaps (&time_caps, sizeof (TIMECAPS)) == TIMERR_NOERROR) { + guint resolution; + + resolution = MIN (MAX (time_caps.wPeriodMin, 1), time_caps.wPeriodMax); + + if (timeBeginPeriod (resolution) != TIMERR_NOERROR) + timer_res = resolution; + } +#endif + + QueryPerformanceFrequency (&self->frequency); + + winrt_vtable.RoInitialize (RO_INIT_MULTITHREADED); + g_main_context_push_thread_default (self->context); + + source = g_idle_source_new (); + g_source_set_callback (source, + (GSourceFunc) gst_d3d11_winrt_capture_running_cb, self, nullptr); + g_source_attach (source, self->context); + g_source_unref (source); + + gst_d3d11_winrt_configure (self); + if (self->inner && self->window_handle) { + /* hold list of capture objects to send target window closed event */ + AcquireSRWLockExclusive (&capture_list_lock); + g_object_weak_ref (G_OBJECT (self), + (GWeakNotify) gst_d3d11_winrt_capture_weak_ref_notify, nullptr); + capture_list = g_list_append (capture_list, self); + ReleaseSRWLockExclusive (&capture_list_lock); + + self->hidden_window = gst_d3d11_winrt_create_hidden_window (self); + if (self->hidden_window) { + DWORD process_id, thread_id; + + thread_id = GetWindowThreadProcessId (self->window_handle, &process_id); + if (thread_id) { + hook = SetWinEventHook (EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY, + nullptr, event_hook_func, process_id, thread_id, + WINEVENT_OUTOFCONTEXT); + } + + msg_io_channel = + g_io_channel_win32_new_messages ((guintptr) self->hidden_window); + msg_source = g_io_create_watch (msg_io_channel, G_IO_IN); + g_source_set_callback (msg_source, + (GSourceFunc) gst_d3d11_winrt_capture_msg_cb, self, nullptr); + g_source_attach (msg_source, self->context); + } + } + + g_main_loop_run (self->loop); + + if (hook) + UnhookWinEvent (hook); + + EnterCriticalSection (&self->lock); + if (self->hidden_window) { + RemovePropA (self->hidden_window, D3D11_WINRT_CAPTURE_PROP_NAME); + DestroyWindow (self->hidden_window); + self->hidden_window = nullptr; + } + LeaveCriticalSection (&self->lock); + + if (msg_source) { + g_source_destroy (msg_source); + g_source_unref (msg_source); + } + + if (msg_io_channel) + g_io_channel_unref (msg_io_channel); + + if (self->inner) + delete self->inner; + self->inner = nullptr; + + g_main_context_pop_thread_default (self->context); + winrt_vtable.RoUninitialize (); + +#if HAVE_WINMM + if (timer_res != 0) + timeEndPeriod (timer_res); +#endif + + return nullptr; +} + +static GstFlowReturn +gst_d3d11_winrt_capture_prepare (GstD3D11ScreenCapture * capture) +{ + GstD3D11WinRTCapture *self = GST_D3D11_WINRT_CAPTURE (capture); + + g_assert (self->inner != nullptr); + + return GST_FLOW_OK; +} + +static gboolean +gst_d3d11_winrt_capture_get_size (GstD3D11ScreenCapture * capture, + guint * width, guint * height) +{ + GstD3D11WinRTCapture *self = GST_D3D11_WINRT_CAPTURE (capture); + + *width = self->width; + *height = self->height; + + return TRUE; +} + +static gboolean +gst_d3d11_winrt_capture_get_colorimetry (GstD3D11ScreenCapture * capture, + GstVideoColorimetry * colorimetry) +{ + return FALSE; +} + +static gboolean +gst_d3d11_winrt_capture_unlock (GstD3D11ScreenCapture * capture) +{ + GstD3D11WinRTCapture *self = GST_D3D11_WINRT_CAPTURE (capture); + GstD3D11CSLockGuard lk (&self->lock); + + self->flushing = TRUE; + WakeAllConditionVariable (&self->cond); + + return TRUE; +} + +static gboolean +gst_d3d11_winrt_capture_unlock_stop (GstD3D11ScreenCapture * capture) +{ + GstD3D11WinRTCapture *self = GST_D3D11_WINRT_CAPTURE (capture); + GstD3D11CSLockGuard lk (&self->lock); + + self->flushing = FALSE; + WakeAllConditionVariable (&self->cond); + + return TRUE; +} + +static void +gst_d3d11_winrt_capture_show_border (GstD3D11ScreenCapture * capture, + gboolean show) +{ + GstD3D11WinRTCapture *self = GST_D3D11_WINRT_CAPTURE (capture); + GstD3D11CSLockGuard lk (&self->lock); + + self->show_border = show; + if (self->inner->session) { + ComPtr < IGraphicsCaptureSession3 > session3; + self->inner->session.As (&session3); + + if (session3) + session3->put_IsBorderRequired (self->show_border); + } +} + +static GstFlowReturn +gst_d3d11_winrt_capture_do_capture (GstD3D11ScreenCapture * capture, + GstD3D11Device * device, ID3D11Texture2D * texture, + ID3D11RenderTargetView * rtv, ID3D11VertexShader * vs, + ID3D11PixelShader * ps, ID3D11InputLayout * layout, + ID3D11SamplerState * sampler, ID3D11BlendState * blend, + D3D11_BOX * crop_box, gboolean draw_mouse) +{ + GstD3D11WinRTCapture *self = GST_D3D11_WINRT_CAPTURE (capture); + GstD3D11WinRTCaptureInner *inner = self->inner; + ComPtr < IDirect3D11CaptureFrame > frame; + ComPtr < IDirect3DSurface > surface; + ComPtr < IDirect3DDxgiInterfaceAccess > access; + ComPtr < ID3D11Texture2D > captured_texture; + ID3D11DeviceContext *context_handle; + SizeInt32 size; + HRESULT hr; + LARGE_INTEGER now; + LONGLONG timeout; + D3D11_TEXTURE2D_DESC desc; + gboolean size_changed = FALSE; + + GstD3D11CSLockGuard lk (&self->lock); +again: + frame = nullptr; + surface = nullptr; + access = nullptr; + captured_texture = nullptr; + + if (inner->closed) { + GST_ERROR_OBJECT (self, "Item was closed"); + return GST_FLOW_ERROR; + } + + if (self->flushing) { + GST_INFO_OBJECT (self, "We are flushing"); + return GST_FLOW_FLUSHING; + } + + if ((draw_mouse && !self->show_mouse) || (!draw_mouse && self->show_mouse)) { + ComPtr < IGraphicsCaptureSession2 > session2; + self->show_mouse = draw_mouse; + + inner->session.As (&session2); + if (session2) { + hr = session2->put_IsCursorCaptureEnabled (draw_mouse); + if (!gst_d3d11_result (hr, self->device)) + GST_DEBUG_OBJECT (self, "Could not set IsCursorCaptureEnabled"); + } else { + GST_LOG_OBJECT (self, "IGraphicsCaptureSession2 is not available"); + } + } + + /* Magic number 5 sec timeout */ + QueryPerformanceCounter (&now); + timeout = now.QuadPart + 5 * self->frequency.QuadPart; + + do { + hr = inner->pool->TryGetNextFrame (&frame); + if (frame) + break; + + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Could not capture frame"); + return GST_FLOW_ERROR; + } + + SleepConditionVariableCS (&self->cond, &self->lock, 1); + QueryPerformanceCounter (&now); + } while (!inner->closed && !self->flushing && now.QuadPart < timeout); + + if (self->flushing) { + GST_INFO_OBJECT (self, "We are flushing"); + return GST_FLOW_FLUSHING; + } + + if (inner->closed) { + GST_WARNING_OBJECT (self, "Capture item was closed"); + return GST_FLOW_ERROR; + } + + if (!frame) { + GST_WARNING_OBJECT (self, "No frame available"); + return GST_D3D11_SCREEN_CAPTURE_FLOW_EXPECTED_ERROR; + } + + hr = frame->get_ContentSize (&size); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Could not get content size"); + return GST_FLOW_ERROR; + } + + if (size.Width != self->pool_size.Width || + size.Height != self->pool_size.Height) { + GST_DEBUG_OBJECT (self, "Size changed %dx%d -> %dx%d", + self->pool_size.Width, self->pool_size.Height, + size.Width, size.Height); + self->pool_size = size; + frame = nullptr; + hr = inner->pool->Recreate (inner->d3d_device.Get (), + DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized, + 1, self->pool_size); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Could not recreate"); + return GST_FLOW_ERROR; + } + + size_changed = TRUE; + goto again; + } + + hr = frame->get_Surface (&surface); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Could not get IDirect3DSurface"); + return GST_FLOW_ERROR; + } + + hr = surface.As (&access); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Could not get IDirect3DDxgiInterfaceAccess"); + return GST_FLOW_ERROR; + } + + hr = access->GetInterface (IID_PPV_ARGS (&captured_texture)); + if (!gst_d3d11_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Could not get texture from frame"); + return GST_FLOW_ERROR; + } + + /* XXX: actual texture size can be different from reported pool size */ + captured_texture->GetDesc (&desc); + if (desc.Width != self->width || desc.Height != self->height) { + GST_DEBUG_OBJECT (self, "Texture size changed %dx%d -> %dx%d", + self->width, self->height, desc.Width, desc.Height); + self->width = desc.Width; + self->height = desc.Height; + size_changed = TRUE; + } + + if (size_changed) + return GST_D3D11_SCREEN_CAPTURE_FLOW_SIZE_CHANGED; + + context_handle = gst_d3d11_device_get_device_context_handle (self->device); + GstD3D11DeviceLockGuard device_lk (self->device); + context_handle->CopySubresourceRegion (texture, 0, 0, 0, 0, + captured_texture.Get (), 0, crop_box); + + return GST_FLOW_OK; +} + +GstD3D11ScreenCapture * +gst_d3d11_winrt_capture_new (GstD3D11Device * device, HMONITOR monitor_handle, + HWND window_handle) +{ + GstD3D11WinRTCapture *self; + + if (window_handle && !IsWindow (window_handle)) { + GST_WARNING_OBJECT (device, "Not a valid window handle"); + return nullptr; + } + + if (!gst_d3d11_winrt_capture_load_library ()) + return nullptr; + + self = (GstD3D11WinRTCapture *) g_object_new (GST_TYPE_D3D11_WINRT_CAPTURE, + "d3d11device", device, "monitor-handle", (gpointer) monitor_handle, + "window-handle", (gpointer) window_handle, nullptr); + if (!self->inner) { + gst_clear_object (&self); + return nullptr; + } + + gst_object_ref_sink (self); + + return GST_D3D11_SCREEN_CAPTURE_CAST (self); +} + diff --git a/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11winrtcapture.h b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11winrtcapture.h new file mode 100644 index 0000000000..28a58598c8 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11winrtcapture.h @@ -0,0 +1,41 @@ +/* + * GStreamer + * Copyright (C) 2022 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include +#include "gstd3d11screencapture.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D11_WINRT_CAPTURE (gst_d3d11_winrt_capture_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D11WinRTCapture, gst_d3d11_winrt_capture, + GST, D3D11_WINRT_CAPTURE, GstD3D11ScreenCapture); + +gboolean gst_d3d11_winrt_capture_load_library (void); + +GstD3D11ScreenCapture * gst_d3d11_winrt_capture_new (GstD3D11Device * device, + HMONITOR monitor_handle, + HWND window_handle); + +G_END_DECLS + diff --git a/subprojects/gst-plugins-bad/sys/d3d11/meson.build b/subprojects/gst-plugins-bad/sys/d3d11/meson.build index 8712c43332..c4a140fff1 100644 --- a/subprojects/gst-plugins-bad/sys/d3d11/meson.build +++ b/subprojects/gst-plugins-bad/sys/d3d11/meson.build @@ -47,6 +47,26 @@ if d3d11_winapi_only_app and (not d3dcompiler_lib.found() or not runtimeobject_l subdir_done() endif +win11_sdk = cxx.compiles(''' + #include + #include + #include + #include, + #include + #include + #include + #include + using namespace Microsoft::WRL; + using namespace ABI::Windows::Graphics::Capture; + ComPtr pool_statics; + ComPtr pool_statics2; + ComPtr pool; + ComPtr session; + ComPtr session2; + ComPtr session3; + ''', + name: 'building with Windows 11 SDK') + # if build target is Windows 10 and WINAPI_PARTITION_APP is allowed, # we can build UWP only modules as well if d3d11_winapi_app @@ -67,6 +87,11 @@ if d3d11_winapi_desktop extra_args += ['-DHAVE_WINMM'] extra_dep += [winmm_lib] endif + + if win11_sdk + d3d11_sources += ['gstd3d11winrtcapture.cpp'] + extra_args += ['-DHAVE_WINRT_CAPTURE'] + endif endif # MinGW 32bits compiler seems to be complaining about redundant-decls