diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.cpp index 5f18305961..96fc967b25 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.cpp @@ -768,9 +768,6 @@ gst_d3d12_dxgi_capture_prepare (GstD3D12ScreenCapture * capture); static gboolean gst_d3d12_dxgi_capture_get_size (GstD3D12ScreenCapture * capture, guint * width, guint * height); -static GstFlowReturn -gst_d3d12_dxgi_capture_do_capture (GstD3D12ScreenCapture * capture, - GstBuffer * buffer, const D3D12_BOX * crop_box, gboolean draw_mouse); #define gst_d3d12_dxgi_capture_parent_class parent_class G_DEFINE_TYPE (GstD3D12DxgiCapture, gst_d3d12_dxgi_capture, @@ -786,8 +783,6 @@ gst_d3d12_dxgi_capture_class_init (GstD3D12DxgiCaptureClass * klass) capture_class->prepare = GST_DEBUG_FUNCPTR (gst_d3d12_dxgi_capture_prepare); capture_class->get_size = GST_DEBUG_FUNCPTR (gst_d3d12_dxgi_capture_get_size); - capture_class->do_capture = - GST_DEBUG_FUNCPTR (gst_d3d12_dxgi_capture_do_capture); } static void @@ -1570,8 +1565,8 @@ gst_d3d12_dxgi_capture_draw_mouse (GstD3D12DxgiCapture * self, return TRUE; } -static GstFlowReturn -gst_d3d12_dxgi_capture_do_capture (GstD3D12ScreenCapture * capture, +GstFlowReturn +gst_d3d12_dxgi_capture_do_capture (GstD3D12DxgiCapture * capture, GstBuffer * buffer, const D3D12_BOX * crop_box, gboolean draw_mouse) { auto self = GST_D3D12_DXGI_CAPTURE (capture); diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.h index 187a7bb812..6e7be61b32 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.h +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.h @@ -32,5 +32,10 @@ G_DECLARE_FINAL_TYPE (GstD3D12DxgiCapture, gst_d3d12_dxgi_capture, GstD3D12ScreenCapture * gst_d3d12_dxgi_capture_new (GstD3D12Device * device, HMONITOR monitor_handle); +GstFlowReturn gst_d3d12_dxgi_capture_do_capture (GstD3D12DxgiCapture * capture, + GstBuffer * buffer, + const D3D12_BOX * crop_box, + gboolean draw_mouse); + G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12graphicscapture.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12graphicscapture.cpp new file mode 100644 index 0000000000..34ed8a8832 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12graphicscapture.cpp @@ -0,0 +1,1342 @@ +/* GStreamer + * Copyright (C) 2024 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 "gstd3d12graphicscapture.h" +#include "gstd3d12pluginutils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_EXTERN (gst_d3d12_screen_capture_debug); +#define GST_CAT_DEFAULT gst_d3d12_screen_capture_debug + +#define CAPTURE_POOL_SIZE 2 + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +using namespace ABI::Windows::System; +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; + +typedef ABI::Windows::Foundation::__FITypedEventHandler_2_Windows__CGraphics__CCapture__CDirect3D11CaptureFramePool_IInspectable_t + IFrameArrivedHandler; + +typedef ABI::Windows::Foundation::__FITypedEventHandler_2_Windows__CGraphics__CCapture__CGraphicsCaptureItem_IInspectable_t + IItemClosedHandler; + +struct GraphicsCaptureVTable +{ + 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); + + /* user32.dll */ + DPI_AWARENESS_CONTEXT (WINAPI * SetThreadDpiAwarenessContext) (DPI_AWARENESS_CONTEXT context); + + /* coremessaging.dll */ + HRESULT (WINAPI * CreateDispatcherQueueController) (DispatcherQueueOptions options, + PDISPATCHERQUEUECONTROLLER * controller); +}; + +static GraphicsCaptureVTable g_vtable = { }; + +#define LOAD_SYMBOL(module,name,func) G_STMT_START { \ + if (!g_module_symbol (module, G_STRINGIFY (name), (gpointer *) &g_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_d3d12_graphics_capture_load_library (void) +{ + static GModule *d3d11_module = nullptr; + static GModule *combase_module = nullptr; + static GModule *user32_module = nullptr; + static GModule *coremessaging_module = nullptr; + + GST_D3D12_CALL_ONCE_BEGIN { + g_vtable.loaded = FALSE; + + d3d11_module = g_module_open ("d3d11.dll", G_MODULE_BIND_LAZY); + 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; + } + + coremessaging_module = + g_module_open ("coremessaging.dll", G_MODULE_BIND_LAZY); + if (!coremessaging_module) { + g_module_close (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); + LOAD_SYMBOL (coremessaging_module, CreateDispatcherQueueController, + CreateDispatcherQueueController); + + g_vtable.loaded = TRUE; + } GST_D3D12_CALL_ONCE_END; + + return g_vtable.loaded; +} + +template < typename InterfaceType, PCNZWCH runtime_class_id > +static HRESULT +GstGetActivationFactory (InterfaceType ** factory) +{ + if (!gst_d3d12_graphics_capture_load_library ()) + return E_NOINTERFACE; + + HSTRING class_id_hstring; + HRESULT hr = g_vtable.WindowsCreateString (runtime_class_id, + wcslen (runtime_class_id), &class_id_hstring); + + if (FAILED (hr)) + return hr; + + hr = g_vtable.RoGetActivationFactory (class_id_hstring, + IID_PPV_ARGS (factory)); + + if (FAILED (hr)) { + g_vtable.WindowsDeleteString (class_id_hstring); + return hr; + } + + return g_vtable.WindowsDeleteString (class_id_hstring); +} + +static gint64 +get_d3d11_texture_token (void) +{ + static gint64 token = 0; + + GST_D3D12_CALL_ONCE_BEGIN { + token = gst_d3d12_create_user_token (); + } GST_D3D12_CALL_ONCE_END; + + return token; +} + +struct SharedTextureData +{ + ComPtr device; + ComPtr shared_texture; +}; + +static void +shared_texture_data_free (SharedTextureData * data) +{ + data->shared_texture = nullptr; + data->device = nullptr; + delete data; +} + +class GraphicsCapture : public RuntimeClass, + FtmBase, IFrameArrivedHandler, IItemClosedHandler> +{ +public: + GraphicsCapture () + { + gst_video_info_init (&pool_info_); + fence_event_handle_ = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); + } + + virtual ~GraphicsCapture () + { + GST_INFO_OBJECT (obj_, "Fin"); + + if (d3d12_pool_) { + gst_buffer_pool_set_active (d3d12_pool_, FALSE); + gst_object_unref (d3d12_pool_); + } + + if (video_pool_) { + gst_buffer_pool_set_active (video_pool_, FALSE); + gst_object_unref (video_pool_); + } + + gst_clear_object (&device12_); + CloseHandle (fence_event_handle_); + } + + STDMETHODIMP + RuntimeClassInitialize (GstD3D12GraphicsCapture * obj, + GstD3D12Device * device12, ID3D11Device * device11, + IDirect3DDevice * d3d_device, + IDirect3D11CaptureFramePoolStatics * pool_statics, + IGraphicsCaptureItem * item, HWND window_handle) + { + obj_ = obj; + device12_ = (GstD3D12Device *) gst_object_ref (device12); + + device11->QueryInterface (IID_PPV_ARGS (&device11_)); + if (!device11_) { + GST_ERROR_OBJECT (obj_, "ID3D11Device5 interface unavailable"); + return E_FAIL; + } + + ComPtr context; + device11_->GetImmediateContext (&context); + context.As (&context_); + if (!context_) { + GST_ERROR_OBJECT (obj_, "ID3D11DeviceContext4 interface unavailable"); + return E_FAIL; + } + + auto hr = device11_->CreateFence (0, D3D11_FENCE_FLAG_SHARED, + IID_PPV_ARGS (&shared_fence11_)); + if (FAILED (hr)) { + GST_ERROR_OBJECT (obj_, "Couldn't create d3d11 fence"); + return E_FAIL; + } + + HANDLE fence_handle; + hr = shared_fence11_->CreateSharedHandle (nullptr, + GENERIC_ALL, nullptr, &fence_handle); + if (FAILED (hr)) { + GST_ERROR_OBJECT (obj_, "Couldn't create shared handle"); + return E_FAIL; + } + + auto device12_handle = gst_d3d12_device_get_device_handle (device12); + hr = device12_handle->OpenSharedHandle (fence_handle, + IID_PPV_ARGS (&shared_fence12_)); + CloseHandle (fence_handle); + if (!gst_d3d12_result (hr, device12)) { + GST_ERROR_OBJECT (obj_, "Couldn't open d3d12 fence"); + return E_FAIL; + } + + device_ = d3d_device; + item_ = item; + hwnd_ = window_handle; + + hr = item->get_Size (&frame_size_); + if (FAILED (hr)) { + GST_ERROR_OBJECT (obj, "Couldn't query item size"); + return hr; + } + + hr = item->add_Closed (this, &closed_token_); + if (FAILED (hr)) { + GST_ERROR_OBJECT (obj, "Couldn't install closed callback"); + return hr; + } + + hr = pool_statics->Create (d3d_device, + DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized, + CAPTURE_POOL_SIZE, frame_size_, &frame_pool_); + if (FAILED (hr)) { + GST_ERROR_OBJECT (obj, "Couldn't create frame pool"); + return hr; + } + + hr = frame_pool_->add_FrameArrived (this, &arrived_token_); + if (FAILED (hr)) { + GST_ERROR_OBJECT (obj, "Couldn't install FrameArrived callback"); + return hr; + } + + hr = frame_pool_->CreateCaptureSession (item, &session_); + if (FAILED (hr)) { + GST_ERROR_OBJECT (obj, "Couldn't create session"); + return hr; + } + + session_.As (&session2_); + if (session2_) + session2_->put_IsCursorCaptureEnabled (FALSE); + + session_.As (&session3_); + if (session3_) + session3_->put_IsBorderRequired (FALSE); + + hr = session_->StartCapture (); + if (FAILED (hr)) { + GST_ERROR_OBJECT (obj, "Couldn't start capture"); + return hr; + } + + return S_OK; + } + + IFACEMETHOD(Invoke) (IDirect3D11CaptureFramePool * pool, IInspectable * arg) + { + ComPtr < IDirect3D11CaptureFrame > frame; + + GST_LOG_OBJECT (obj_, "Frame arrived"); + + pool->TryGetNextFrame (&frame); + if (!frame) { + GST_WARNING_OBJECT (obj_, "No frame"); + return S_OK; + } + + SizeInt32 frame_size; + auto hr = frame->get_ContentSize (&frame_size); + if (FAILED (hr)) { + GST_WARNING_OBJECT (obj_, "Couldn't get content size"); + return S_OK; + } + + if (frame_size.Width != frame_size_.Width || + frame_size.Height != frame_size_.Height) { + GST_DEBUG_OBJECT (obj_, "Frame size changed %dx%d -> %dx%d", + frame_size_.Width, frame_size_.Height, + frame_size.Width, frame_size.Height); + std::lock_guard lk (lock_); + if (d3d12_pool_) { + gst_buffer_pool_set_active (d3d12_pool_, FALSE); + gst_clear_object (&d3d12_pool_); + } + + if (video_pool_) { + gst_buffer_pool_set_active (video_pool_, FALSE); + gst_clear_object (&video_pool_); + } + + frame_ = nullptr; + texture_ = nullptr; + staging_ = nullptr; + pool->Recreate (device_.Get (), + DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized, + CAPTURE_POOL_SIZE, frame_size); + frame_size_ = frame_size; + return S_OK; + } + + ComPtr < IDirect3DSurface > surface; + frame->get_Surface (&surface); + if (!surface) { + GST_WARNING_OBJECT (obj_, "IDirect3DSurface interface unavailable"); + return S_OK; + } + + ComPtr < IDirect3DDxgiInterfaceAccess > access; + surface.As (&access); + if (!access) { + GST_WARNING_OBJECT (obj_, + "IDirect3DDxgiInterfaceAccess interface unavailable"); + return S_OK; + } + + ComPtr < ID3D11Texture2D > texture; + access->GetInterface (IID_PPV_ARGS (&texture)); + if (!texture) { + GST_WARNING_OBJECT (obj_, + "ID3D11Texture2D interface unavailable"); + return S_OK; + } + + D3D11_TEXTURE2D_DESC desc; + texture->GetDesc (&desc); + + std::lock_guard lk (lock_); + crop_box_.left = 0; + crop_box_.top = 0; + crop_box_.right = desc.Width; + crop_box_.bottom = desc.Height; + crop_box_.front = 0; + crop_box_.back = 1; + texture_ = texture; + frame_ = frame; + + if (hwnd_ && client_only_) + updateClientBox (desc, crop_box_); + + cond_.notify_all (); + + return S_OK; + } + + IFACEMETHOD(Invoke) (IGraphicsCaptureItem * pool, IInspectable * arg) + { + GST_INFO_OBJECT (obj_, "Item closed"); + + std::lock_guard lk (lock_); + closed_ = true; + cond_.notify_all (); + + return S_OK; + } + + void SetCursorEnabled (boolean value) + { + if (session2_) + session2_->put_IsCursorCaptureEnabled (value); + } + + void SetBorderRequired (boolean value) + { + if (session3_) + session3_->put_IsBorderRequired (value); + } + + void SetClientOnly (bool value) + { + client_only_ = value; + } + + void GetSize (guint * width, guint * height) + { + std::lock_guard lk (lock_); + *width = MAX (1, (guint) frame_size_.Width); + *height = MAX (1, (guint) frame_size_.Height); + } + + GstFlowReturn GetD3D12Frame (const CaptureCropRect * crop_rect, + GstBuffer ** buffer, guint * width, guint * height) + { + std::unique_lock lk (lock_); + while (!frame_ && !flushing_ && !closed_) + cond_.wait (lk); + + if (flushing_) + return GST_FLOW_FLUSHING; + if (closed_) + return GST_FLOW_EOS; + + guint crop_w = crop_box_.right - crop_box_.left; + guint crop_h = crop_box_.bottom - crop_box_.top; + D3D12_BOX crop_box = crop_box_; + if (crop_rect->crop_x + crop_rect->crop_w > crop_w || + crop_rect->crop_y + crop_rect->crop_h > crop_h) { + /* Ignore this crop rect */ + } else { + if (crop_rect->crop_w) + crop_w = crop_rect->crop_w; + if (crop_rect->crop_h) + crop_h = crop_rect->crop_h; + + crop_box.left += crop_rect->crop_x; + crop_box.top += crop_rect->crop_y; + crop_box.right = crop_box.left + crop_w; + crop_box.bottom = crop_box.top + crop_h; + } + + if (!d3d12_pool_ || pool_info_.width != crop_w || + pool_info_.height != crop_h) { + GST_DEBUG_OBJECT (obj_, "Size changed, recrate buffer pool"); + + if (d3d12_pool_) { + gst_buffer_pool_set_active (d3d12_pool_, FALSE); + gst_clear_object (&d3d12_pool_); + } + + gst_video_info_set_format (&pool_info_, GST_VIDEO_FORMAT_BGRA, + crop_w, crop_h); + d3d12_pool_ = gst_d3d12_buffer_pool_new (device12_); + if (!d3d12_pool_) { + GST_ERROR_OBJECT (obj_, "Couldn't create buffer pool"); + return GST_FLOW_ERROR; + } + + auto caps = gst_video_info_to_caps (&pool_info_); + auto config = gst_buffer_pool_get_config (d3d12_pool_); + gst_buffer_pool_config_set_params (config, caps, pool_info_.size, 0, 0); + gst_caps_unref (caps); + + auto params = gst_d3d12_allocation_params_new (device12_, &pool_info_, + GST_D3D12_ALLOCATION_FLAG_DEFAULT, + D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS | + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET); + gst_d3d12_allocation_params_set_heap_flags (params, + D3D12_HEAP_FLAG_SHARED); + gst_buffer_pool_config_set_d3d12_allocation_params (config, params); + gst_d3d12_allocation_params_free (params); + if (!gst_buffer_pool_set_config (d3d12_pool_, config)) { + GST_ERROR_OBJECT (obj_, "Couldn't set buffer pool config"); + gst_clear_object (&d3d12_pool_); + return GST_FLOW_ERROR; + } + + if (!gst_buffer_pool_set_active (d3d12_pool_, TRUE)) { + GST_ERROR_OBJECT (obj_, "Couldn't activate pool"); + gst_clear_object (&d3d12_pool_); + return GST_FLOW_ERROR; + } + } + + GstBuffer *outbuf = nullptr; + gst_buffer_pool_acquire_buffer (d3d12_pool_, &outbuf, nullptr); + if (!outbuf) { + GST_ERROR_OBJECT (obj_, "Couldn't acquire buffer"); + return GST_FLOW_ERROR; + } + + GstMapInfo map_info; + auto mem = gst_buffer_peek_memory (outbuf, 0); + auto dmem = GST_D3D12_MEMORY_CAST (mem); + GST_MEMORY_FLAG_UNSET (dmem, GST_D3D12_MEMORY_TRANSFER_NEED_UPLOAD); + gst_d3d12_memory_sync (dmem); + SharedTextureData *shared_data = (SharedTextureData *) + gst_d3d12_memory_get_token_data (dmem, + get_d3d11_texture_token ()); + + if (!shared_data) { + HANDLE shared_handle = nullptr; + if (!gst_d3d12_memory_get_nt_handle (dmem, &shared_handle)) { + GST_ERROR_OBJECT (obj_, "Couldn't get shared handle"); + gst_buffer_unref (outbuf); + return GST_FLOW_ERROR; + } + + ComPtr shared_texture; + auto hr = device11_->OpenSharedResource1 (shared_handle, + IID_PPV_ARGS (&shared_texture)); + if (FAILED (hr)) { + GST_ERROR_OBJECT (obj_, "Couldn't open shared d3d11 texture"); + gst_buffer_unref (outbuf); + return GST_FLOW_ERROR; + } + + shared_data = new SharedTextureData (); + shared_data->shared_texture = shared_texture; + shared_data->device = device11_; + + gst_d3d12_memory_set_token_data (dmem, get_d3d11_texture_token (), + shared_data, (GDestroyNotify) shared_texture_data_free); + } + + if (!gst_memory_map (mem, + &map_info, (GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D12))) { + GST_ERROR_OBJECT (obj_, "Couldn't map memory"); + gst_buffer_unref (outbuf); + return GST_FLOW_ERROR; + } + + context_->CopySubresourceRegion (shared_data->shared_texture.Get (), + 0, 0, 0, 0, texture_.Get (), 0, (const D3D11_BOX *) &crop_box); + fence_val_++; + context_->Signal (shared_fence11_.Get (), fence_val_); + gst_memory_unmap (mem, &map_info); + + auto completed = shared_fence12_->GetCompletedValue (); + if (completed < fence_val_) { + auto hr = shared_fence12_->SetEventOnCompletion (fence_val_, + fence_event_handle_); + if (!gst_d3d12_result (hr, device12_)) { + GST_ERROR_OBJECT (obj_, "SetEventOnCompletion failed"); + gst_buffer_unref (outbuf); + return GST_FLOW_ERROR; + } + + WaitForSingleObject (fence_event_handle_, INFINITE); + } + + *width = crop_w; + *height = crop_h; + *buffer = outbuf; + + return GST_FLOW_OK; + } + + GstFlowReturn GetVideoFrame (const CaptureCropRect * crop_rect, + GstBuffer ** buffer, guint * width, guint * height) + { + std::unique_lock lk (lock_); + while (!frame_ && !flushing_ && !closed_) + cond_.wait (lk); + + if (flushing_) + return GST_FLOW_FLUSHING; + if (closed_) + return GST_FLOW_EOS; + + guint crop_w = crop_box_.right - crop_box_.left; + guint crop_h = crop_box_.bottom - crop_box_.top; + D3D12_BOX crop_box = crop_box_; + if (crop_rect->crop_x + crop_rect->crop_w > crop_w || + crop_rect->crop_y + crop_rect->crop_h > crop_h) { + /* Ignore this crop rect */ + } else { + if (crop_rect->crop_w) + crop_w = crop_rect->crop_w; + if (crop_rect->crop_h) + crop_h = crop_rect->crop_h; + + crop_box.left += crop_rect->crop_x; + crop_box.top += crop_rect->crop_y; + crop_box.right = crop_box.left + crop_w; + crop_box.bottom = crop_box.top + crop_h; + } + + if (!video_pool_ || pool_info_.width != crop_w || + pool_info_.height != crop_h) { + GST_DEBUG_OBJECT (obj_, "Size changed, recrate buffer pool"); + + if (video_pool_) { + gst_buffer_pool_set_active (video_pool_, FALSE); + gst_clear_object (&video_pool_); + } + + staging_ = nullptr; + + D3D11_TEXTURE2D_DESC desc = { }; + desc.Width = crop_w; + desc.Height = crop_h; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.ArraySize = 1; + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + auto hr = device11_->CreateTexture2D (&desc, nullptr, &staging_); + if (FAILED (hr)) { + GST_ERROR_OBJECT (obj_, "Couldn't create staging texture"); + return GST_FLOW_ERROR; + } + + gst_video_info_set_format (&pool_info_, GST_VIDEO_FORMAT_BGRA, + crop_w, crop_h); + video_pool_ = gst_video_buffer_pool_new (); + if (!video_pool_) { + GST_ERROR_OBJECT (obj_, "Couldn't create buffer pool"); + return GST_FLOW_ERROR; + } + + auto caps = gst_video_info_to_caps (&pool_info_); + auto config = gst_buffer_pool_get_config (video_pool_); + gst_buffer_pool_config_set_params (config, caps, pool_info_.size, 0, 0); + gst_caps_unref (caps); + + if (!gst_buffer_pool_set_config (video_pool_, config)) { + GST_ERROR_OBJECT (obj_, "Couldn't set buffer pool config"); + gst_clear_object (&video_pool_); + return GST_FLOW_ERROR; + } + + if (!gst_buffer_pool_set_active (video_pool_, TRUE)) { + GST_ERROR_OBJECT (obj_, "Couldn't activate pool"); + gst_clear_object (&video_pool_); + return GST_FLOW_ERROR; + } + } + + context_->CopySubresourceRegion (staging_.Get (), 0, 0, 0, 0, + texture_.Get (), 0, (const D3D11_BOX *) &crop_box); + + D3D11_MAPPED_SUBRESOURCE mapped_resource; + auto hr = context_->Map (staging_.Get (), 0, D3D11_MAP_READ, + 0, &mapped_resource); + if (FAILED (hr)) { + GST_ERROR_OBJECT (obj_, "Couldn't map staging texture"); + return GST_FLOW_ERROR; + } + + GstBuffer *outbuf = nullptr; + gst_buffer_pool_acquire_buffer (video_pool_, &outbuf, nullptr); + if (!outbuf) { + GST_ERROR_OBJECT (obj_, "Couldn't acquire buffer"); + context_->Unmap (staging_.Get (), 0); + return GST_FLOW_ERROR; + } + + GstVideoFrame vframe; + if (!gst_video_frame_map (&vframe, &pool_info_, outbuf, GST_MAP_WRITE)) { + GST_ERROR_OBJECT (obj_, "Couldn't map video frame"); + context_->Unmap (staging_.Get (), 0); + gst_buffer_unref (outbuf); + return GST_FLOW_ERROR; + } + + guint8 *dst = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&vframe, 0); + guint8 *src = (guint8 *) mapped_resource.pData; + guint width_in_bytes = GST_VIDEO_FRAME_COMP_PSTRIDE (&vframe, 0) + * GST_VIDEO_FRAME_COMP_WIDTH (&vframe, 0); + for (guint i = 0; i < GST_VIDEO_FRAME_HEIGHT (&vframe); i++) { + memcpy (dst, src, width_in_bytes); + dst += GST_VIDEO_FRAME_PLANE_STRIDE (&vframe, 0); + src += mapped_resource.RowPitch; + } + gst_video_frame_unmap (&vframe); + context_->Unmap (staging_.Get (), 0); + + *width = crop_w; + *height = crop_h; + *buffer = outbuf; + + return GST_FLOW_OK; + } + + void SetFlushing (bool flushing) + { + std::lock_guard lk (lock_); + flushing_ = flushing; + cond_.notify_all (); + } + + void Close () + { + if (item_) + item_->remove_Closed (closed_token_); + + if (frame_pool_) + frame_pool_->remove_FrameArrived (arrived_token_); + + texture_ = nullptr; + frame_ = nullptr; + staging_ = nullptr; + + if (session_) { + ComPtr closable; + session_.As (&closable); + if (closable) + closable->Close (); + } + + if (frame_pool_) { + ComPtr closable; + frame_pool_.As (&closable); + if (closable) + closable->Close (); + } + + if (item_) { + ComPtr closable; + item_.As (&closable); + if (closable) + closable->Close (); + } + } + +private: + bool updateClientBox (const D3D11_TEXTURE2D_DESC & desc, D3D12_BOX & box) + { + if (IsIconic (hwnd_)) + return false; + + RECT client_rect; + if (!GetClientRect (hwnd_, &client_rect)) + return false; + + if (client_rect.right <= 0 || client_rect.bottom <= 0) + return false; + + RECT bound_rect; + auto hr = DwmGetWindowAttribute (hwnd_, + DWMWA_EXTENDED_FRAME_BOUNDS, &bound_rect, sizeof (RECT)); + if (FAILED (hr)) + return false; + + POINT client_pos = { }; + if (!ClientToScreen (hwnd_, &client_pos)) + return false; + + UINT left = 0; + if (client_pos.x > bound_rect.left) + left = client_pos.x - bound_rect.left; + + UINT width = 1; + if (desc.Width > left) { + width = desc.Width - left; + if (width > client_rect.right) + width = client_rect.right; + } + + UINT right = left + width; + if (right > desc.Width) + return false; + + UINT top = 0; + if (client_pos.y > bound_rect.top) + top = client_pos.y - bound_rect.top; + + UINT height = 1; + if (desc.Height > top) { + height = desc.Height - top; + if (height > client_rect.bottom) + height = client_rect.bottom; + } + + UINT bottom = top + height; + if (bottom > desc.Height) + return false; + + box.left = left; + box.top = top; + box.right = right; + box.bottom = bottom; + box.front = 0; + box.back = 1; + + return true; + } + +private: + GstD3D12GraphicsCapture *obj_ = nullptr; + HWND hwnd_ = nullptr; + GstD3D12Device *device12_ = nullptr; + GstVideoInfo pool_info_; + GstBufferPool *d3d12_pool_ = nullptr; + GstBufferPool *video_pool_ = nullptr; + ComPtr device11_; + ComPtr context_; + ComPtr device_; + ComPtr item_; + ComPtr frame_pool_; + ComPtr session_; + ComPtr session2_; + ComPtr session3_; + ComPtr frame_; + ComPtr texture_; + ComPtr staging_; + SizeInt32 frame_size_ = { }; + EventRegistrationToken arrived_token_ = { }; + EventRegistrationToken closed_token_ = { }; + std::mutex lock_; + std::condition_variable cond_; + bool closed_ = false; + bool flushing_ = false; + std::atomic client_only_ = { false }; + D3D12_BOX crop_box_ = { }; + UINT64 fence_val_ = 0; + ComPtr shared_fence11_; + ComPtr shared_fence12_; + HANDLE fence_event_handle_; +}; + +class AsyncWaiter : public RuntimeClass, + FtmBase, IAsyncActionCompletedHandler> +{ +public: + STDMETHODIMP + RuntimeClassInitialize (HANDLE event_handle) + { + event_handle_ = event_handle; + return S_OK; + } + + STDMETHOD (Invoke) (IAsyncAction * action, AsyncStatus status) + { + SetEvent (event_handle_); + + return S_OK; + } + +private: + HANDLE event_handle_; +}; + +enum LoopState +{ + LOOP_STATE_INIT, + LOOP_STATE_RUNNING, + LOOP_STATE_STOPPED, +}; + +struct GstD3D12GraphicsCapturePrivate +{ + GstD3D12GraphicsCapturePrivate () + { + shutdown_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); + } + + ~GstD3D12GraphicsCapturePrivate () + { + SetEvent (shutdown_handle); + g_clear_pointer (&loop_thread, g_thread_join); + + CloseHandle (shutdown_handle); + gst_clear_object (&device); + } + + GstD3D12Device *device = nullptr; + ComPtr d3d11_device; + ComPtr capture; + HMONITOR monitor_handle = nullptr; + HWND window_handle = nullptr; + HWND hidden_hwnd = nullptr; + HANDLE shutdown_handle; + GThread *loop_thread = nullptr; + LoopState loop_state = LOOP_STATE_INIT; + std::mutex loop_lock; + std::condition_variable loop_cond; +}; +/* *INDENT-ON* */ + +struct _GstD3D12GraphicsCapture +{ + GstObject parent; + + GstD3D12GraphicsCapturePrivate *priv; +}; + +static void gst_d3d12_graphics_capture_finalize (GObject * object); + +static GstFlowReturn +gst_d3d12_graphics_capture_prepare (GstD3D12ScreenCapture * capture); +static gboolean +gst_d3d12_graphics_capture_get_size (GstD3D12ScreenCapture * capture, + guint * width, guint * height); +static gboolean +gst_d3d12_graphics_capture_unlock (GstD3D12ScreenCapture * capture); +static gboolean +gst_d3d12_graphics_capture_unlock_stop (GstD3D12ScreenCapture * capture); + +#define gst_d3d12_graphics_capture_parent_class parent_class +G_DEFINE_TYPE (GstD3D12GraphicsCapture, + gst_d3d12_graphics_capture, GST_TYPE_D3D12_SCREEN_CAPTURE); + +static void +gst_d3d12_graphics_capture_class_init (GstD3D12GraphicsCaptureClass * klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + auto capture_class = GST_D3D12_SCREEN_CAPTURE_CLASS (klass); + + object_class->finalize = gst_d3d12_graphics_capture_finalize; + + capture_class->prepare = + GST_DEBUG_FUNCPTR (gst_d3d12_graphics_capture_prepare); + capture_class->get_size = + GST_DEBUG_FUNCPTR (gst_d3d12_graphics_capture_get_size); + capture_class->unlock = GST_DEBUG_FUNCPTR (gst_d3d12_graphics_capture_unlock); + capture_class->unlock_stop = + GST_DEBUG_FUNCPTR (gst_d3d12_graphics_capture_unlock_stop); +} + +static void +gst_d3d12_graphics_capture_init (GstD3D12GraphicsCapture * self) +{ + self->priv = new GstD3D12GraphicsCapturePrivate (); +} + +static void +gst_d3d12_graphics_capture_finalize (GObject * object) +{ + auto self = GST_D3D12_GRAPHICS_CAPTURE (object); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +open_device (GstD3D12GraphicsCapture * self, IDirect3DDevice ** d3d_device) +{ + auto priv = self->priv; + + static const D3D_FEATURE_LEVEL feature_levels[] = { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + }; + + auto adapter = gst_d3d12_device_get_adapter_handle (priv->device); + auto hr = D3D11CreateDevice (adapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, feature_levels, + G_N_ELEMENTS (feature_levels), D3D11_SDK_VERSION, + &priv->d3d11_device, nullptr, nullptr); + + if (FAILED (hr)) { + hr = D3D11CreateDevice (adapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, &feature_levels[1], + G_N_ELEMENTS (feature_levels) - 1, D3D11_SDK_VERSION, + &priv->d3d11_device, nullptr, nullptr); + } + + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't create d3d11 device"); + return FALSE; + } + + ComPtr < ID3D10Multithread > multi_thread; + priv->d3d11_device.As (&multi_thread); + if (multi_thread) + multi_thread->SetMultithreadProtected (TRUE); + + ComPtr < IDXGIDevice > dxgi_device; + hr = priv->d3d11_device.As (&dxgi_device); + if (FAILED (hr)) { + GST_WARNING_OBJECT (self, "IDXGIDevice interface unavailable"); + return FALSE; + } + + ComPtr < IInspectable > inspectable; + hr = g_vtable.CreateDirect3D11DeviceFromDXGIDevice (dxgi_device.Get (), + &inspectable); + if (FAILED (hr)) { + GST_WARNING_OBJECT (self, "CreateDirect3D11DeviceFromDXGIDevice failed"); + return FALSE; + } + + ComPtr < IDirect3DDevice > device; + hr = inspectable.As (&device); + if (FAILED (hr)) { + GST_WARNING_OBJECT (self, "IDirect3DDevice interface unavailable"); + return FALSE; + } + + *d3d_device = device.Detach (); + + return TRUE; +} + +static gboolean +create_session (GstD3D12GraphicsCapture * self, IDirect3DDevice * d3d_device) +{ + auto priv = self->priv; + auto capture = priv->capture; + + ComPtr < IGraphicsCaptureItemInterop > interop; + auto hr = GstGetActivationFactory < IGraphicsCaptureItemInterop, + RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem > (&interop); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "IGraphicsCaptureItemInterop is not available"); + return FALSE; + } + + ComPtr < IGraphicsCaptureItem > item; + if (priv->monitor_handle) { + hr = interop->CreateForMonitor (priv->monitor_handle, IID_PPV_ARGS (&item)); + } else { + hr = interop->CreateForWindow (priv->window_handle, IID_PPV_ARGS (&item)); + } + + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't create item"); + return FALSE; + } + + ComPtr < IDirect3D11CaptureFramePoolStatics > pool_statics; + hr = GstGetActivationFactory < IDirect3D11CaptureFramePoolStatics, + RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool > + (&pool_statics); + if (FAILED (hr)) { + GST_WARNING_OBJECT (self, + "IDirect3D11CaptureFramePoolStatics is unavailable"); + return FALSE; + } + + hr = MakeAndInitialize < GraphicsCapture > (&priv->capture, + self, priv->device, priv->d3d11_device.Get (), d3d_device, + pool_statics.Get (), item.Get (), priv->window_handle); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't initialize capture object"); + return FALSE; + } + + return TRUE; +} + +static gpointer +gst_d3d11_graphics_thread_func (GstD3D12GraphicsCapture * self) +{ + HRESULT hr; + ComPtr < IDispatcherQueueController > queue_ctrl; + auto priv = self->priv; + HANDLE event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); + ComPtr < IDirect3DDevice > d3d_device; + HANDLE waitables[] = { priv->shutdown_handle, event_handle }; + ComPtr < AsyncWaiter > async_waiter; + ComPtr < IAsyncAction > shutdown_action; + + GST_INFO_OBJECT (self, "Entering loop thread"); + + g_vtable.SetThreadDpiAwarenessContext + (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + + g_vtable.RoInitialize (RO_INIT_MULTITHREADED); + + hr = MakeAndInitialize < AsyncWaiter > (&async_waiter, event_handle); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't create async waiter"); + goto out; + } + + DispatcherQueueOptions queue_opt; + queue_opt.dwSize = sizeof (DispatcherQueueOptions); + queue_opt.threadType = DQTYPE_THREAD_CURRENT; + queue_opt.apartmentType = DQTAT_COM_NONE; + + hr = g_vtable.CreateDispatcherQueueController (queue_opt, &queue_ctrl); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't create dispatcher queue controller"); + goto out; + } + + if (!open_device (self, &d3d_device)) { + GST_ERROR_OBJECT (self, "Couldn't open device"); + goto out; + } + + if (!create_session (self, d3d_device.Get ())) { + GST_ERROR_OBJECT (self, "Couldn't open session"); + goto out; + } + + { + std::lock_guard < std::mutex > lk (priv->loop_lock); + priv->loop_state = LOOP_STATE_RUNNING; + priv->loop_cond.notify_all (); + } + + while (true) { + MSG msg; + while (PeekMessage (&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + + auto wait_ret = MsgWaitForMultipleObjects (G_N_ELEMENTS (waitables), + waitables, FALSE, INFINITE, QS_ALLINPUT); + + if (wait_ret == WAIT_OBJECT_0) { + GST_DEBUG_OBJECT (self, "Begin shutdown"); + priv->capture->Close (); + hr = queue_ctrl->ShutdownQueueAsync (&shutdown_action); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Shutdown failed"); + break; + } + + hr = shutdown_action->put_Completed (async_waiter.Get ()); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't install shutdown callback"); + break; + } + } else if (wait_ret == WAIT_OBJECT_0 + 1) { + GST_DEBUG_OBJECT (self, "Shutdown completed"); + break; + } else if (wait_ret != WAIT_OBJECT_0 + G_N_ELEMENTS (waitables)) { + GST_ERROR_OBJECT (self, "Unexpected wait return %u", (guint) wait_ret); + break; + } + } + +out: + { + std::lock_guard < std::mutex > lk (priv->loop_lock); + priv->loop_state = LOOP_STATE_STOPPED; + priv->loop_cond.notify_all (); + } + + priv->capture = nullptr; + queue_ctrl = nullptr; + shutdown_action = nullptr; + async_waiter = nullptr; + + if (d3d_device) { + ComPtr < IClosable > closable; + d3d_device.As (&closable); + if (closable) + closable->Close (); + + closable = nullptr; + d3d_device = nullptr; + } + + CloseHandle (event_handle); + + g_vtable.RoUninitialize (); + + GST_INFO_OBJECT (self, "Leaving loop thread"); + + return nullptr; +} + +static GstFlowReturn +gst_d3d12_graphics_capture_prepare (GstD3D12ScreenCapture * capture) +{ + return GST_FLOW_OK; +} + +static gboolean +gst_d3d12_graphics_capture_get_size (GstD3D12ScreenCapture * capture, + guint * width, guint * height) +{ + auto self = GST_D3D12_GRAPHICS_CAPTURE (capture); + auto priv = self->priv; + priv->capture->GetSize (width, height); + + return TRUE; +} + +static gboolean +gst_d3d12_graphics_capture_unlock (GstD3D12ScreenCapture * capture) +{ + auto self = GST_D3D12_GRAPHICS_CAPTURE (capture); + auto priv = self->priv; + priv->capture->SetFlushing (true); + + return TRUE; +} + +static gboolean +gst_d3d12_graphics_capture_unlock_stop (GstD3D12ScreenCapture * capture) +{ + auto self = GST_D3D12_GRAPHICS_CAPTURE (capture); + auto priv = self->priv; + priv->capture->SetFlushing (false); + + return TRUE; +} + +GstD3D12ScreenCapture * +gst_d3d12_graphics_capture_new (GstD3D12Device * device, HWND window_handle, + HMONITOR monitor_handle) +{ + g_return_val_if_fail (device, nullptr); + + if (!gst_d3d12_graphics_capture_load_library ()) { + GST_WARNING ("Couldn't load library"); + return nullptr; + } + + if (window_handle && !IsWindow (window_handle)) { + GST_ERROR ("%p is not a valid HWND", window_handle); + return nullptr; + } + + auto self = (GstD3D12GraphicsCapture *) + g_object_new (GST_TYPE_D3D12_GRAPHICS_CAPTURE, nullptr); + gst_object_ref_sink (self); + + auto priv = self->priv; + priv->device = (GstD3D12Device *) gst_object_ref (device); + priv->window_handle = window_handle; + priv->monitor_handle = monitor_handle; + + priv->loop_thread = g_thread_new ("GstD3D12GraphicsCapture", + (GThreadFunc) gst_d3d11_graphics_thread_func, self); + + bool configured = false; + { + std::unique_lock < std::mutex > lk (priv->loop_lock); + while (priv->loop_state == LOOP_STATE_INIT) + priv->loop_cond.wait (lk); + + if (priv->loop_state == LOOP_STATE_RUNNING) + configured = true; + } + + if (!configured) + gst_clear_object (&self); + + return (GstD3D12ScreenCapture *) self; +} + +void +gst_d3d12_graphics_capture_show_border (GstD3D12GraphicsCapture * capture, + gboolean show) +{ + auto priv = capture->priv; + priv->capture->SetBorderRequired (show); +} + +void +gst_d3d12_graphics_capture_show_cursor (GstD3D12GraphicsCapture * capture, + gboolean show) +{ + auto priv = capture->priv; + priv->capture->SetCursorEnabled (show); +} + +void +gst_d3d12_graphics_capture_set_client_only (GstD3D12GraphicsCapture * capture, + gboolean client_only) +{ + auto priv = capture->priv; + priv->capture->SetClientOnly (client_only); +} + +GstFlowReturn +gst_d3d12_graphics_capture_do_capture (GstD3D12GraphicsCapture * capture, + gboolean is_d3d12, const CaptureCropRect * crop_rect, GstBuffer ** buffer, + guint * width, guint * height) +{ + auto priv = capture->priv; + + if (is_d3d12) + return priv->capture->GetD3D12Frame (crop_rect, buffer, width, height); + + return priv->capture->GetVideoFrame (crop_rect, buffer, width, height); +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12graphicscapture.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12graphicscapture.h new file mode 100644 index 0000000000..9c500f30dd --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12graphicscapture.h @@ -0,0 +1,54 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include "gstd3d12screencapture.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_GRAPHICS_CAPTURE (gst_d3d12_graphics_capture_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12GraphicsCapture, gst_d3d12_graphics_capture, + GST, D3D12_GRAPHICS_CAPTURE, GstD3D12ScreenCapture); + +gboolean gst_d3d12_graphics_capture_load_library (void); + +GstD3D12ScreenCapture * gst_d3d12_graphics_capture_new (GstD3D12Device * device, + HWND window_handle, + HMONITOR monitor_handle); + +void gst_d3d12_graphics_capture_show_border (GstD3D12GraphicsCapture * capture, + gboolean show); + +void gst_d3d12_graphics_capture_show_cursor (GstD3D12GraphicsCapture * capture, + gboolean show); + +void gst_d3d12_graphics_capture_set_client_only (GstD3D12GraphicsCapture * capture, + gboolean client_only); + +GstFlowReturn gst_d3d12_graphics_capture_do_capture (GstD3D12GraphicsCapture * capture, + gboolean is_d3d12, + const CaptureCropRect * crop_rect, + GstBuffer ** buffer, + guint * width, + guint * height); + +G_END_DECLS + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.cpp index 52e4e05d72..569239c6da 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.cpp @@ -99,31 +99,6 @@ gst_d3d12_screen_capture_unlock_stop (GstD3D12ScreenCapture * capture) return TRUE; } -void -gst_d3d12_screen_capture_show_border (GstD3D12ScreenCapture * capture, - gboolean show) -{ - g_return_if_fail (GST_IS_D3D12_SCREEN_CAPTURE (capture)); - - auto klass = GST_D3D12_SCREEN_CAPTURE_GET_CLASS (capture); - - if (klass->show_border) - klass->show_border (capture, show); -} - -GstFlowReturn -gst_d3d12_screen_capture_do_capture (GstD3D12ScreenCapture * capture, - GstBuffer * buffer, const D3D12_BOX * crop_box, gboolean draw_mouse) -{ - g_return_val_if_fail (GST_IS_D3D12_SCREEN_CAPTURE (capture), GST_FLOW_ERROR); - g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR); - - auto klass = GST_D3D12_SCREEN_CAPTURE_GET_CLASS (capture); - g_assert (klass->do_capture); - - return klass->do_capture (capture, buffer, crop_box, draw_mouse); -} - HRESULT gst_d3d12_screen_capture_find_output_for_monitor (HMONITOR monitor, IDXGIAdapter1 ** adapter, IDXGIOutput ** output) diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.h index dd76730d6b..ca3fd0c425 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.h +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.h @@ -40,6 +40,14 @@ typedef struct _GstD3D12ScreenCaptureClass GstD3D12ScreenCaptureClass; #define GST_D3D12_SCREEN_CAPTURE_FLOW_SIZE_CHANGED GST_FLOW_CUSTOM_SUCCESS_1 #define GST_D3D12_SCREEN_CAPTURE_FLOW_UNSUPPORTED GST_FLOW_CUSTOM_ERROR +struct CaptureCropRect +{ + guint crop_x; + guint crop_y; + guint crop_w; + guint crop_h; +}; + struct _GstD3D12ScreenCapture { GstObject parent; @@ -58,14 +66,6 @@ struct _GstD3D12ScreenCaptureClass gboolean (*unlock) (GstD3D12ScreenCapture * capture); gboolean (*unlock_stop) (GstD3D12ScreenCapture * capture); - - void (*show_border) (GstD3D12ScreenCapture * capture, - gboolean show); - - GstFlowReturn (*do_capture) (GstD3D12ScreenCapture * capture, - GstBuffer * buffer, - const D3D12_BOX * crop_box, - gboolean draw_mouse); }; GType gst_d3d12_screen_capture_get_type (void); @@ -80,14 +80,6 @@ gboolean gst_d3d12_screen_capture_unlock (GstD3D12ScreenCapture * ca gboolean gst_d3d12_screen_capture_unlock_stop (GstD3D12ScreenCapture * capture); -void gst_d3d12_screen_capture_show_border (GstD3D12ScreenCapture * capture, - gboolean show); - -GstFlowReturn gst_d3d12_screen_capture_do_capture (GstD3D12ScreenCapture * capture, - GstBuffer * buffer, - const D3D12_BOX * crop_box, - gboolean draw_mouse); - HRESULT gst_d3d12_screen_capture_find_output_for_monitor (HMONITOR monitor, IDXGIAdapter1 ** adapter, IDXGIOutput ** output); diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.cpp index 127f60f2d0..4925b561d1 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.cpp @@ -36,6 +36,7 @@ #include "gstd3d12screencapturesrc.h" #include "gstd3d12dxgicapture.h" +#include "gstd3d12graphicscapture.h" #include "gstd3d12pluginutils.h" #include #include @@ -58,10 +59,102 @@ enum PROP_CROP_Y, PROP_CROP_WIDTH, PROP_CROP_HEIGHT, + PROP_WINDOW_HANDLE, + PROP_SHOW_BORDER, + PROP_CAPTURE_API, + PROP_ADAPTER, + PROP_WINDOW_CAPTURE_MODE, }; +enum GstD3D12ScreenCaptureAPI +{ + GST_D3D12_SCREEN_CAPTURE_API_DXGI, + GST_D3D12_SCREEN_CAPTURE_API_WGC, +}; + +enum GstD3D12WindowCaptureMode +{ + GST_D3D12_WINDOW_CAPTURE_DEFAULT, + GST_D3D12_WINDOW_CAPTURE_CLIENT, +}; + +/** + * GstD3D11ScreenCaptureAPI: + * + * Since: 1.26 + */ +#define GST_TYPE_D3D12_SCREEN_CAPTURE_API (gst_d3d12_screen_capture_api_get_type()) +static GType +gst_d3d12_screen_capture_api_get_type (void) +{ + static GType api_type = 0; + + GST_D3D12_CALL_ONCE_BEGIN { + static const GEnumValue api_types[] = { + /** + * GstD3D12ScreenCaptureAPI::dxgi: + * + * Since: 1.26 + */ + {GST_D3D12_SCREEN_CAPTURE_API_DXGI, "DXGI Desktop Duplication", "dxgi"}, + + /** + * GstD3D12ScreenCaptureAPI::wgc: + * + * Since: 1.26 + */ + {GST_D3D12_SCREEN_CAPTURE_API_WGC, "Windows Graphics Capture", "wgc"}, + {0, nullptr, nullptr}, + }; + + api_type = g_enum_register_static ("GstD3D12ScreenCaptureAPI", api_types); + } GST_D3D12_CALL_ONCE_END; + + return api_type; +} + +/** + * GstD3D12WindowCaptureMode: + * + * Since: 1.26 + */ +#define GST_TYPE_D3D12_WINDOW_CAPTURE_MODE (gst_d3d11_window_capture_mode_get_type()) +static GType +gst_d3d11_window_capture_mode_get_type (void) +{ + static GType type = 0; + + GST_D3D12_CALL_ONCE_BEGIN { + static const GEnumValue hwnd_modes[] = { + /** + * GstD3D12WindowCaptureMode::default: + * + * Since: 1.26 + */ + {GST_D3D12_WINDOW_CAPTURE_DEFAULT, + "Capture entire window area", "default"}, + + /** + * GstD3D12WindowCaptureMode::client: + * + * Since: 1.26 + */ + {GST_D3D12_WINDOW_CAPTURE_CLIENT, "Capture client area", "client"}, + {0, nullptr, nullptr}, + }; + + type = g_enum_register_static ("GstD3D12WindowCaptureMode", hwnd_modes); + } GST_D3D12_CALL_ONCE_END; + + return type; +} + #define DEFAULT_MONITOR_INDEX -1 #define DEFAULT_SHOW_CURSOR FALSE +#define DEFAULT_SHOW_BORDER FALSE +#define DEFAULT_CAPTURE_API GST_D3D12_SCREEN_CAPTURE_API_DXGI +#define DEFAULT_ADAPTER -1 +#define DEFAULT_WINDOW_CAPTURE_MODE GST_D3D12_WINDOW_CAPTURE_DEFAULT static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, @@ -92,15 +185,18 @@ struct GstD3D12ScreenCaptureSrcPrivate GstBufferPool *pool = nullptr; gint64 adapter_luid = 0; + gint adapter_index = DEFAULT_ADAPTER; gint monitor_index = DEFAULT_MONITOR_INDEX; HMONITOR monitor_handle = nullptr; + HWND window_handle = nullptr; gboolean show_cursor = DEFAULT_SHOW_CURSOR; + gboolean show_border = DEFAULT_SHOW_BORDER; - guint crop_x = 0; - guint crop_y = 0; - guint crop_w = 0; - guint crop_h = 0; + CaptureCropRect crop_rect = { }; D3D12_BOX crop_box = { }; + GstD3D12ScreenCaptureAPI capture_api = DEFAULT_CAPTURE_API; + GstD3D12ScreenCaptureAPI selected_capture_api = DEFAULT_CAPTURE_API; + GstD3D12WindowCaptureMode hwnd_capture_mode = DEFAULT_WINDOW_CAPTURE_MODE; gboolean flushing = FALSE; GstClockTime latency = GST_CLOCK_TIME_NONE; @@ -206,6 +302,84 @@ gst_d3d12_screen_capture_src_class_init (GstD3D12ScreenCaptureSrcClass * klass) 0, G_MAXUINT, 0, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + if (gst_d3d12_graphics_capture_load_library ()) { + /** + * GstD3D12ScreenCaptureSrc:window-handle: + * + * HWND window handle to capture + * + * Since: 1.26 + */ + g_object_class_install_property (object_class, PROP_WINDOW_HANDLE, + g_param_spec_uint64 ("window-handle", "Window Handle", + "A HWND handle of window to capture", + 0, G_MAXUINT64, 0, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_STATIC_STRINGS))); + + /** + * GstD3D12ScreenCaptureSrc:show-border: + * + * Show border lines to capture area when WGC mode is selected. + * This feature requires Windows11 or newer + * + * Since: 1.26 + */ + g_object_class_install_property (object_class, PROP_SHOW_BORDER, + g_param_spec_boolean ("show-border", "Show Border", + "Show border lines to capture area when WGC mode is selected", + DEFAULT_SHOW_BORDER, + (GParamFlags) (GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_READWRITE + | G_PARAM_STATIC_STRINGS))); + + /** + * GstD3D12ScreenCaptureSrc:capture-api: + * + * Capture API to use + * + * Since: 1.26 + */ + g_object_class_install_property (object_class, PROP_CAPTURE_API, + g_param_spec_enum ("capture-api", "Capture API", "Capture API to use", + GST_TYPE_D3D12_SCREEN_CAPTURE_API, + DEFAULT_CAPTURE_API, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_STATIC_STRINGS))); + gst_type_mark_as_plugin_api (GST_TYPE_D3D12_SCREEN_CAPTURE_API, + (GstPluginAPIFlags) 0); + + /** + * GstD3D12ScreenCaptureSrc:adapter: + * + * DXGI Adapter index for creating device when WGC mode is selected + * + * Since: 1.26 + */ + g_object_class_install_property (object_class, PROP_ADAPTER, + g_param_spec_int ("adapter", "Adapter", + "DXGI Adapter index for creating device when WGC mode is selected " + "(-1 for default)", + -1, G_MAXINT32, DEFAULT_ADAPTER, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_STATIC_STRINGS))); + + /** + * GstD3D12ScreenCaptureSrc:window-capture-mode: + * + * Window capture mode to use + * + * Since: 1.26 + */ + g_object_class_install_property (object_class, PROP_WINDOW_CAPTURE_MODE, + g_param_spec_enum ("window-capture-mode", "Window Capture Mode", + "Window capture mode to use if \"window-handle\" is set", + GST_TYPE_D3D12_WINDOW_CAPTURE_MODE, DEFAULT_WINDOW_CAPTURE_MODE, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_STATIC_STRINGS))); + gst_type_mark_as_plugin_api (GST_TYPE_D3D12_WINDOW_CAPTURE_MODE, + (GstPluginAPIFlags) 0); + } + element_class->provide_clock = GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_provide_clock); element_class->set_context = @@ -282,16 +456,43 @@ gst_d3d12_screen_capture_src_set_property (GObject * object, guint prop_id, priv->show_cursor = g_value_get_boolean (value); break; case PROP_CROP_X: - priv->crop_x = g_value_get_uint (value); + priv->crop_rect.crop_x = g_value_get_uint (value); break; case PROP_CROP_Y: - priv->crop_y = g_value_get_uint (value); + priv->crop_rect.crop_y = g_value_get_uint (value); break; case PROP_CROP_WIDTH: - priv->crop_w = g_value_get_uint (value); + priv->crop_rect.crop_w = g_value_get_uint (value); break; case PROP_CROP_HEIGHT: - priv->crop_h = g_value_get_uint (value); + priv->crop_rect.crop_h = g_value_get_uint (value); + break; + case PROP_WINDOW_HANDLE: + priv->window_handle = (HWND) g_value_get_uint64 (value); + break; + case PROP_SHOW_BORDER: + priv->show_border = g_value_get_boolean (value); + if (priv->capture && + priv->capture_api == GST_D3D12_SCREEN_CAPTURE_API_WGC) { + auto wgc = GST_D3D12_GRAPHICS_CAPTURE (priv->capture); + gst_d3d12_graphics_capture_show_border (wgc, priv->show_border); + } + break; + case PROP_CAPTURE_API: + priv->capture_api = (GstD3D12ScreenCaptureAPI) g_value_get_enum (value); + break; + case PROP_ADAPTER: + priv->adapter_index = g_value_get_int (value); + break; + case PROP_WINDOW_CAPTURE_MODE: + priv->hwnd_capture_mode = + (GstD3D12WindowCaptureMode) g_value_get_enum (value); + if (priv->capture && + priv->capture_api == GST_D3D12_SCREEN_CAPTURE_API_WGC) { + auto wgc = GST_D3D12_GRAPHICS_CAPTURE (priv->capture); + gst_d3d12_graphics_capture_set_client_only (wgc, + priv->hwnd_capture_mode == GST_D3D12_WINDOW_CAPTURE_CLIENT); + } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -318,16 +519,31 @@ gst_d3d12_screen_capture_src_get_property (GObject * object, guint prop_id, g_value_set_boolean (value, priv->show_cursor); break; case PROP_CROP_X: - g_value_set_uint (value, priv->crop_x); + g_value_set_uint (value, priv->crop_rect.crop_x); break; case PROP_CROP_Y: - g_value_set_uint (value, priv->crop_y); + g_value_set_uint (value, priv->crop_rect.crop_y); break; case PROP_CROP_WIDTH: - g_value_set_uint (value, priv->crop_w); + g_value_set_uint (value, priv->crop_rect.crop_w); break; case PROP_CROP_HEIGHT: - g_value_set_uint (value, priv->crop_h); + g_value_set_uint (value, priv->crop_rect.crop_h); + break; + case PROP_WINDOW_HANDLE: + g_value_set_uint64 (value, (guint64) priv->window_handle); + break; + case PROP_SHOW_BORDER: + g_value_set_boolean (value, priv->show_border); + break; + case PROP_CAPTURE_API: + g_value_set_enum (value, priv->capture_api); + break; + case PROP_ADAPTER: + g_value_set_int (value, priv->adapter_index); + break; + case PROP_WINDOW_CAPTURE_MODE: + g_value_set_enum (value, priv->hwnd_capture_mode); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -370,8 +586,8 @@ gst_d3d12_screen_capture_src_get_crop_box (GstD3D12ScreenCaptureSrc * self, gst_d3d12_screen_capture_get_size (priv->capture, &screen_width, &screen_height); - if ((priv->crop_x + priv->crop_w) > screen_width || - (priv->crop_y + priv->crop_h) > screen_height) { + if ((priv->crop_rect.crop_x + priv->crop_rect.crop_w) > screen_width || + (priv->crop_rect.crop_y + priv->crop_rect.crop_h) > screen_height) { GST_WARNING ("Capture region outside of the screen bounds; ignoring."); box.left = 0; @@ -379,10 +595,12 @@ gst_d3d12_screen_capture_src_get_crop_box (GstD3D12ScreenCaptureSrc * self, box.right = screen_width; box.bottom = screen_height; } else { - box.left = priv->crop_x; - box.top = priv->crop_y; - box.right = priv->crop_w ? (priv->crop_x + priv->crop_w) : screen_width; - box.bottom = priv->crop_h ? (priv->crop_y + priv->crop_h) : screen_height; + box.left = priv->crop_rect.crop_x; + box.top = priv->crop_rect.crop_y; + box.right = priv->crop_rect.crop_w ? + (priv->crop_rect.crop_x + priv->crop_rect.crop_w) : screen_width; + box.bottom = priv->crop_rect.crop_h ? + (priv->crop_rect.crop_y + priv->crop_rect.crop_h) : screen_height; } } @@ -399,9 +617,13 @@ gst_d3d12_screen_capture_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter) return gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (bsrc)); } - gst_d3d12_screen_capture_src_get_crop_box (self, priv->crop_box); - width = priv->crop_box.right - priv->crop_box.left; - height = priv->crop_box.bottom - priv->crop_box.top; + if (priv->selected_capture_api == GST_D3D12_SCREEN_CAPTURE_API_WGC) { + gst_d3d12_screen_capture_get_size (priv->capture, &width, &height); + } else { + gst_d3d12_screen_capture_src_get_crop_box (self, priv->crop_box); + width = priv->crop_box.right - priv->crop_box.left; + height = priv->crop_box.bottom - priv->crop_box.top; + } auto caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (bsrc)); caps = gst_caps_make_writable (caps); @@ -617,43 +839,63 @@ gst_d3d12_screen_capture_src_start (GstBaseSrc * bsrc) GstFlowReturn ret; HMONITOR monitor = priv->monitor_handle; ComPtr < IDXGIAdapter1 > adapter; - DXGI_ADAPTER_DESC desc; GstD3D12ScreenCapture *capture = nullptr; HRESULT hr; + GstD3D12ScreenCaptureAPI requested_api; - if (monitor) { - hr = gst_d3d12_screen_capture_find_output_for_monitor (monitor, - &adapter, nullptr); - } else if (priv->monitor_index < 0) { - hr = gst_d3d12_screen_capture_find_primary_monitor (&monitor, - &adapter, nullptr); + std::unique_lock < std::recursive_mutex > lk (priv->lock); + requested_api = priv->capture_api; + if (priv->window_handle) { + priv->selected_capture_api = GST_D3D12_SCREEN_CAPTURE_API_WGC; } else { - hr = gst_d3d12_screen_capture_find_nth_monitor (priv->monitor_index, - &monitor, &adapter, nullptr); + priv->selected_capture_api = requested_api; + + if (monitor) { + hr = gst_d3d12_screen_capture_find_output_for_monitor (monitor, + &adapter, nullptr); + } else if (priv->monitor_index < 0) { + hr = gst_d3d12_screen_capture_find_primary_monitor (&monitor, + &adapter, nullptr); + } else { + hr = gst_d3d12_screen_capture_find_nth_monitor (priv->monitor_index, + &monitor, &adapter, nullptr); + } + + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't get monitor handle"); + goto error; + } } - if (FAILED (hr)) { - GST_ERROR_OBJECT (self, "Couldn't get monitor handle"); - goto error; - } + if (priv->selected_capture_api == GST_D3D12_SCREEN_CAPTURE_API_DXGI) { + DXGI_ADAPTER_DESC desc; + adapter->GetDesc (&desc); - adapter->GetDesc (&desc); - { - std::lock_guard < std::recursive_mutex > lk (priv->lock); priv->adapter_luid = gst_d3d12_luid_to_int64 (&desc.AdapterLuid); gst_clear_object (&self->device); gst_d3d12_ensure_element_data_for_adapter_luid (GST_ELEMENT_CAST (self), priv->adapter_luid, &self->device); + } else { + gst_clear_object (&self->device); + gst_d3d12_ensure_element_data (GST_ELEMENT_CAST (self), + priv->adapter_index, &self->device); } if (!self->device) { + lk.unlock (); GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("D3D12 device is not available"), (nullptr)); return FALSE; } - capture = gst_d3d12_dxgi_capture_new (self->device, monitor); + if (priv->selected_capture_api == GST_D3D12_SCREEN_CAPTURE_API_WGC) { + capture = gst_d3d12_graphics_capture_new (self->device, + priv->window_handle, monitor); + } else { + capture = gst_d3d12_dxgi_capture_new (self->device, monitor); + } + if (!capture) { GST_ERROR_OBJECT (self, "Couldn't create capture object"); goto error; @@ -666,6 +908,17 @@ gst_d3d12_screen_capture_src_start (GstBaseSrc * bsrc) case GST_FLOW_OK: break; case GST_D3D12_SCREEN_CAPTURE_FLOW_UNSUPPORTED: + if (priv->selected_capture_api == GST_D3D12_SCREEN_CAPTURE_API_DXGI) { + gst_clear_object (&capture); + GST_WARNING_OBJECT (self, "DXGI capture is not available"); + capture = gst_d3d12_graphics_capture_new (self->device, + nullptr, monitor); + if (capture) { + priv->selected_capture_api = GST_D3D12_SCREEN_CAPTURE_API_WGC; + GST_INFO_OBJECT (self, "Fallback to Windows Graphics Capture"); + break; + } + } goto unsupported; default: goto error; @@ -675,11 +928,26 @@ gst_d3d12_screen_capture_src_start (GstBaseSrc * bsrc) priv->latency = GST_CLOCK_TIME_NONE; priv->capture = capture; + if (priv->selected_capture_api == GST_D3D12_SCREEN_CAPTURE_API_WGC) { + auto wgc = GST_D3D12_GRAPHICS_CAPTURE (priv->capture); + gst_d3d12_graphics_capture_show_cursor (wgc, priv->show_cursor); + gst_d3d12_graphics_capture_show_border (wgc, priv->show_border); + gst_d3d12_graphics_capture_set_client_only (wgc, + priv->hwnd_capture_mode == GST_D3D12_WINDOW_CAPTURE_CLIENT); + } + + if (priv->capture_api != priv->selected_capture_api) { + priv->capture_api = priv->selected_capture_api; + lk.unlock (); + g_object_notify (G_OBJECT (self), "capture-api"); + } + return TRUE; error: { gst_clear_object (&capture); + lk.unlock (); GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("Failed to prepare capture object with given configuration, " "monitor-index: %d, monitor-handle: %p", @@ -690,6 +958,7 @@ error: unsupported: { gst_clear_object (&capture); + lk.unlock (); GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, ("Failed to prepare capture object with given configuration, " "monitor-index: %d, monitor-handle: %p", @@ -710,6 +979,7 @@ gst_d3d12_screen_capture_src_stop (GstBaseSrc * bsrc) gst_clear_object (&priv->pool); } + std::lock_guard < std::recursive_mutex > lk (priv->lock); gst_clear_object (&priv->capture); gst_clear_object (&self->device); @@ -723,10 +993,14 @@ gst_d3d12_screen_capture_src_unlock (GstBaseSrc * bsrc) auto priv = self->priv; std::lock_guard < std::mutex > lk (priv->flush_lock); + if (priv->capture) + gst_d3d12_screen_capture_unlock (priv->capture); + if (priv->clock_id) { GST_DEBUG_OBJECT (self, "Waking up waiting clock"); gst_clock_id_unschedule (priv->clock_id); } + priv->flushing = TRUE; return TRUE; @@ -739,6 +1013,9 @@ gst_d3d12_screen_capture_src_unlock_stop (GstBaseSrc * bsrc) auto priv = self->priv; std::lock_guard < std::mutex > lk (priv->flush_lock); + if (priv->capture) + gst_d3d12_screen_capture_unlock_stop (priv->capture); + priv->flushing = FALSE; return TRUE; @@ -777,8 +1054,142 @@ gst_d3d12_screen_capture_src_src_query (GstBaseSrc * bsrc, GstQuery * query) } static GstFlowReturn -gst_d3d12_screen_capture_src_create (GstBaseSrc * bsrc, guint64 offset, - guint size, GstBuffer ** buf) +gst_d3d12_screen_capture_src_wgc_capture (GstBaseSrc * bsrc, + guint64 offset, guint size, GstBuffer ** buf) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (bsrc); + auto priv = self->priv; + GstClockTime base_time; + GstClockTime next_capture_ts; + GstClockTime dur; + guint64 next_frame_no; + + auto fps_n = GST_VIDEO_INFO_FPS_N (&priv->video_info); + auto fps_d = GST_VIDEO_INFO_FPS_D (&priv->video_info); + + if (fps_n <= 0 || fps_d <= 0) + return GST_FLOW_NOT_NEGOTIATED; + + auto clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); + { + std::unique_lock < std::mutex > lk (priv->flush_lock); + if (priv->flushing) { + gst_clear_object (&clock); + return GST_FLOW_FLUSHING; + } + + base_time = GST_ELEMENT_CAST (self)->base_time; + next_capture_ts = gst_clock_get_time (clock); + next_capture_ts -= base_time; + + next_frame_no = gst_util_uint64_scale (next_capture_ts, + fps_n, GST_SECOND * fps_d); + + if (next_frame_no == priv->last_frame_no) { + GstClockID id; + GstClockReturn clock_ret; + + /* Need to wait for the next frame */ + next_frame_no += 1; + + /* Figure out what the next frame time is */ + next_capture_ts = gst_util_uint64_scale (next_frame_no, + fps_d * GST_SECOND, fps_n); + + id = gst_clock_new_single_shot_id (GST_ELEMENT_CLOCK (self), + next_capture_ts + base_time); + priv->clock_id = id; + + /* release the object lock while waiting */ + lk.unlock (); + + GST_LOG_OBJECT (self, "Waiting for next frame time %" GST_TIME_FORMAT, + GST_TIME_ARGS (next_capture_ts)); + clock_ret = gst_clock_id_wait (id, nullptr); + lk.lock (); + + gst_clock_id_unref (id); + priv->clock_id = nullptr; + + if (clock_ret == GST_CLOCK_UNSCHEDULED) { + /* Got woken up by the unlock function */ + gst_clear_object (&clock); + return GST_FLOW_FLUSHING; + } + /* Duration is a complete 1/fps frame duration */ + dur = gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n); + } else { + GstClockTime next_frame_ts; + + GST_LOG_OBJECT (self, "No need to wait for next frame time %" + GST_TIME_FORMAT " next frame = %" G_GINT64_FORMAT " prev = %" + G_GINT64_FORMAT, GST_TIME_ARGS (next_capture_ts), next_frame_no, + priv->last_frame_no); + + next_frame_ts = gst_util_uint64_scale (next_frame_no + 1, + fps_d * GST_SECOND, fps_n); + /* Frame duration is from now until the next expected capture time */ + dur = next_frame_ts - next_capture_ts; + } + } + gst_clear_object (&clock); + + priv->last_frame_no = next_frame_no; + + auto wgc = GST_D3D12_GRAPHICS_CAPTURE (priv->capture); + GstBuffer *buffer = nullptr; + guint width, height; + CaptureCropRect crop_rect; + + { + std::lock_guard < std::recursive_mutex > lk (priv->lock); + crop_rect = priv->crop_rect; + } + + auto ret = gst_d3d12_graphics_capture_do_capture (wgc, + priv->downstream_supports_d3d12, &crop_rect, &buffer, &width, &height); + if (ret != GST_FLOW_OK) + return ret; + + if (width != priv->video_info.width || height != priv->video_info.height) { + gst_video_info_set_format (&priv->video_info, GST_VIDEO_FORMAT_BGRA, + width, height); + priv->video_info.fps_n = fps_n; + priv->video_info.fps_d = fps_d; + + auto caps = gst_pad_get_current_caps (bsrc->srcpad); + if (!caps || gst_caps_is_any (caps)) { + GST_ERROR_OBJECT (self, "Couldn't get current caps"); + gst_clear_caps (&caps); + gst_buffer_unref (buffer); + return GST_FLOW_ERROR; + } + + caps = gst_caps_make_writable (caps); + gst_caps_set_simple (caps, "width", G_TYPE_INT, width, "height", G_TYPE_INT, + height, nullptr); + + auto caps_ret = gst_base_src_set_caps (bsrc, caps); + gst_caps_unref (caps); + if (!caps_ret) { + GST_ERROR_OBJECT (self, "Couldn't update caps"); + gst_buffer_unref (buffer); + return GST_FLOW_ERROR; + } + } + + GST_BUFFER_DTS (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_PTS (buffer) = next_capture_ts; + GST_BUFFER_DURATION (buffer) = dur; + + *buf = buffer; + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_d3d12_screen_capture_src_dxgi_capture (GstBaseSrc * bsrc, + guint64 offset, guint size, GstBuffer ** buf) { auto self = GST_D3D12_SCREEN_CAPTURE_SRC (bsrc); auto priv = self->priv; @@ -795,11 +1206,7 @@ gst_d3d12_screen_capture_src_create (GstBaseSrc * bsrc, guint64 offset, GstBuffer *buffer = nullptr; D3D12_BOX crop_box; - if (!priv->capture) { - GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, - ("Couldn't configure capture object"), (nullptr)); - return GST_FLOW_NOT_NEGOTIATED; - } + auto dxgi_capture = GST_D3D12_DXGI_CAPTURE (priv->capture); fps_n = GST_VIDEO_INFO_FPS_N (&priv->video_info); fps_d = GST_VIDEO_INFO_FPS_D (&priv->video_info); @@ -906,7 +1313,7 @@ again: } auto before_capture = gst_util_get_timestamp (); - ret = gst_d3d12_screen_capture_do_capture (priv->capture, buffer, + ret = gst_d3d12_dxgi_capture_do_capture (dxgi_capture, buffer, &crop_box, draw_mouse); switch (ret) { @@ -1004,3 +1411,22 @@ again: return GST_FLOW_OK; } + +static GstFlowReturn +gst_d3d12_screen_capture_src_create (GstBaseSrc * bsrc, guint64 offset, + guint size, GstBuffer ** buf) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (bsrc); + auto priv = self->priv; + + if (!priv->capture) { + GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, + ("Couldn't configure capture object"), (nullptr)); + return GST_FLOW_NOT_NEGOTIATED; + } + + if (priv->selected_capture_api == GST_D3D12_SCREEN_CAPTURE_API_DXGI) + return gst_d3d12_screen_capture_src_dxgi_capture (bsrc, offset, size, buf); + + return gst_d3d12_screen_capture_src_wgc_capture (bsrc, offset, size, buf); +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/meson.build b/subprojects/gst-plugins-bad/sys/d3d12/meson.build index 00db92808a..10b0f9f800 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/meson.build +++ b/subprojects/gst-plugins-bad/sys/d3d12/meson.build @@ -20,6 +20,7 @@ d3d12_sources = [ 'gstd3d12encoderbufferpool.cpp', 'gstd3d12fencedatapool.cpp', 'gstd3d12format.cpp', + 'gstd3d12graphicscapture.cpp', 'gstd3d12h264dec.cpp', 'gstd3d12h264enc.cpp', 'gstd3d12h265dec.cpp', @@ -64,6 +65,7 @@ d3d12_lib = cc.find_library('d3d12', required : d3d12_option) d3d11_lib = cc.find_library('d3d11', required : d3d12_option) d2d_dep = cc.find_library('d2d1', required: d3d12_option) dxgi_lib = cc.find_library('dxgi', required : d3d12_option) +dwmapi_lib = cc.find_library('dwmapi', required: d3d12_option) dx_headers_dep = dependency('DirectX-Headers', version: '>= 1.611', allow_fallback: true, @@ -71,13 +73,40 @@ dx_headers_dep = dependency('DirectX-Headers', dxc = find_program('dxc', required : d3d12_option) if not gstdxva_dep.found() or not d3d12_lib.found() or not dxgi_lib.found() \ - or not dx_headers_dep.found() or not dxc.found() or not d2d_dep.found() + or not dx_headers_dep.found() or not dxc.found() or not d2d_dep.found() \ + or not dwmapi_lib.found() if d3d12_option.enabled() error('The d3d12 was enabled explicitly, but required dependencies were not found.') endif subdir_done() endif +have_wgc = cxx.compiles(''' + #include + #include + #include + #include, + #include + #include + #include + #include + using namespace Microsoft::WRL; + using namespace ABI::Windows::Graphics::Capture; + ComPtr pool_statics; + ComPtr pool; + ComPtr session; + ComPtr session2; + ComPtr session3; + ''', + name: 'Windows Graphics Capture support in Windows SDK') + +if not have_wgc + if d3d12_option.enabled() + error('Windows SDK does not support Windows Graphics Capture') + endif + subdir_done() +endif + d3d12_headers = [ 'dxgi1_6.h', 'd3d11.h', @@ -114,9 +143,9 @@ gstd3d12 = library('gstd3d12', c_args : gst_plugins_bad_args + extra_args, cpp_args: gst_plugins_bad_args + extra_args, include_directories : [configinc], - dependencies : [gstbase_dep, gstvideo_dep, gstcodecs_dep, + dependencies : [gstbase_dep, gstvideo_dep, gstcodecs_dep, gmodule_dep, gstdxva_dep, d3d12_lib, d3d11_lib, d2d_dep, dxgi_lib, - dx_headers_dep], + dx_headers_dep, dwmapi_lib], install : true, install_dir : plugins_install_dir, )