gstreamer/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11winrtcapture.cpp
Seungha Yang 0afeacce92 d3d11screencapturesrc: Fix wrong color with HDR enabled
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>
2023-11-16 19:00:24 +09:00

1080 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);
}