/* * 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 #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); DPI_AWARENESS_CONTEXT (WINAPI * SetThreadDpiAwarenessContext) (DPI_AWARENESS_CONTEXT context); } 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() { 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; 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); \ if (user32_module) \ g_module_close (user32_module); \ return; \ } \ } G_STMT_END gboolean gst_d3d11_winrt_capture_load_library (void) { static GModule *d3d11_module = nullptr; static GModule *combase_module = nullptr; static GModule *user32_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; } user32_module = g_module_open ("user32.dll", G_MODULE_BIND_LAZY); if (!user32_module) { g_module_close (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); LOAD_SYMBOL (user32_module, SetThreadDpiAwarenessContext, SetThreadDpiAwarenessContext); 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, PROP_CLIENT_ONLY, }; struct _GstD3D11WinRTCapture { GstD3D11ScreenCapture parent; GstD3D11Device *device; GstD3D11WinRTCaptureInner *inner; /* Reported by WGC API */ SizeInt32 pool_size; /* Actual texture resolution */ UINT width; UINT height; UINT capture_width; UINT capture_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; gboolean client_only; 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_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, ShaderResource * resource, 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))); g_object_class_install_property (gobject_class, PROP_CLIENT_ONLY, g_param_spec_boolean ("client-only", "Client Only", "Captures only client area", FALSE, (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->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; case PROP_CLIENT_ONLY: self->client_only = g_value_get_boolean (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; } 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 = self->capture_width = (UINT) self->pool_size.Width; self->height = self->capture_height = (UINT) self->pool_size.Height; if (self->window_handle && self->client_only) { RECT rect; if (!GetClientRect (self->window_handle, &rect)) { GST_ERROR_OBJECT (self, "Could not get client rect"); goto error; } self->capture_width = rect.right - rect.left; self->capture_height = rect.bottom - rect.top; self->capture_width = MAX (self->capture_width, 1); self->capture_height = MAX (self->capture_height, 1); GST_DEBUG_OBJECT (self, "Client rect %d:%d:%d:%d, pool size %dx%d", rect.left, rect.top, rect.right, rect.bottom, self->width, self->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 winrt_vtable.SetThreadDpiAwarenessContext (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); 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->capture_width; *height = self->capture_height; return TRUE; } 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, ShaderResource * resource, 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; D3D11_BOX box = *crop_box; 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; if (!self->window_handle || !self->client_only) { self->capture_width = self->width; self->capture_height = self->capture_height; } size_changed = TRUE; } if (self->window_handle && self->client_only) { RECT client_rect, bound_rect; POINT client_pos = { 0, }; UINT width, height; DPI_AWARENESS_CONTEXT prev; BOOL ret; prev = winrt_vtable.SetThreadDpiAwarenessContext (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); ret = GetClientRect (self->window_handle, &client_rect) && DwmGetWindowAttribute (self->window_handle, DWMWA_EXTENDED_FRAME_BOUNDS, &bound_rect, sizeof (RECT)) == S_OK && ClientToScreen (self->window_handle, &client_pos); if (prev) winrt_vtable.SetThreadDpiAwarenessContext (prev); if (!ret) { GST_ERROR_OBJECT (self, "Could not get client rect"); return GST_FLOW_ERROR; } width = client_rect.right - client_rect.left; height = client_rect.bottom - client_rect.top; width = MAX (width, 1); height = MAX (height, 1); if (self->capture_width != width || self->capture_height != height) { GST_DEBUG_OBJECT (self, "Client rect size changed %dx%d -> %dx%d", self->capture_width, self->capture_height, width, height); self->capture_width = width; self->capture_height = height; size_changed = TRUE; } else { UINT x_offset = 0; UINT y_offset = 0; GST_LOG_OBJECT (self, "bound-rect: %d:%d:%d:%d, " "client-rect: %d:%d:%d:%d, client-upper-left: %d:%d", bound_rect.left, bound_rect.top, bound_rect.right, bound_rect.bottom, client_rect.left, client_rect.top, client_rect.right, client_rect.bottom, client_pos.x, client_pos.y); if (client_pos.x > bound_rect.left) x_offset = client_pos.x - bound_rect.left; if (client_pos.y > bound_rect.top) y_offset = client_pos.y - bound_rect.top; box.left += x_offset; box.top += y_offset; box.right += x_offset; box.bottom += y_offset; /* left and top is inclusive */ box.left = MIN (desc.Width - 1, box.left); box.top = MIN (desc.Height - 1, box.top); box.right = MIN (desc.Width, box.right); box.bottom = MIN (desc.Height, box.bottom); } } 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, &box); return GST_FLOW_OK; } GstD3D11ScreenCapture * gst_d3d11_winrt_capture_new (GstD3D11Device * device, HMONITOR monitor_handle, HWND window_handle, gboolean client_only) { 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, "client-only", client_only, nullptr); if (!self->inner) { gst_clear_object (&self); return nullptr; } gst_object_ref_sink (self); return GST_D3D11_SCREEN_CAPTURE_CAST (self); }