mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 13:41:48 +00:00
0afeacce92
Even if IDXGIOutput6 says current display colorspace is HDR, captured texture via IDXGIOutputDuplication::AcquireNextFrame() is converted frame by OS unless we use IDXGIOutput5::DuplicateOutput1() with DXGI_FORMAT_R16G16B16A16_FLOAT format, in order for captured frame to be scRGB color space. Then application should perform tonemap operation based on reported display white level, color primaries, etc. Since we don't have any tonemapping implementation, ignores colorimetry reported by IDXGIOutput6. Fixes: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/3128 Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5671>
1079 lines
32 KiB
C++
1079 lines
32 KiB
C++
/*
|
|
* GStreamer
|
|
* Copyright (C) 2022 Seungha Yang <seungha@centricular.com>
|
|
*
|
|
* 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 <gmodule.h>
|
|
#include <winstring.h>
|
|
#include <roapi.h>
|
|
#include <windows.graphics.capture.h>
|
|
#include <windows.graphics.capture.interop.h>
|
|
#include <windows.graphics.directx.direct3d11.h>
|
|
#include <windows.graphics.directx.direct3d11.interop.h>
|
|
#include <string.h>
|
|
#include <dwmapi.h>
|
|
|
|
#include <wrl.h>
|
|
|
|
#ifdef HAVE_WINMM
|
|
#include <mmsystem.h>
|
|
#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<IClosable> 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);
|
|
}
|