diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json index 00bdac3a91..6efdb911b0 100644 --- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json @@ -11686,6 +11686,125 @@ }, "rank": "none" }, + "d3d12screencapturesrc": { + "author": "Seungha Yang ", + "description": "Captures desktop screen", + "hierarchy": [ + "GstD3D12ScreenCaptureSrc", + "GstBaseSrc", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Source/Video", + "pad-templates": { + "src": { + "caps": "video/x-raw(memory:D3D12Memory):\n format: BGRA\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\npixel-aspect-ratio: 1/1\n colorimetry: sRGB\nvideo/x-raw:\n format: BGRA\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\npixel-aspect-ratio: 1/1\n colorimetry: sRGB\n", + "direction": "src", + "presence": "always" + } + }, + "properties": { + "crop-height": { + "blurb": "Height of screen capture area (0 = maximum)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "crop-width": { + "blurb": "Width of screen capture area (0 = maximum)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "crop-x": { + "blurb": "Horizontal coordinate of top left corner for the screen capture area", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "crop-y": { + "blurb": "Vertical coordinate of top left corner for the screen capture area", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "monitor-handle": { + "blurb": "A HMONITOR handle of monitor to capture", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "18446744073709551615", + "min": "0", + "mutable": "ready", + "readable": true, + "type": "guint64", + "writable": true + }, + "monitor-index": { + "blurb": "Zero-based index for monitor to capture (-1 = primary monitor)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "-1", + "max": "2147483647", + "min": "-1", + "mutable": "ready", + "readable": true, + "type": "gint", + "writable": true + }, + "show-cursor": { + "blurb": "Whether to show mouse cursor", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + } + }, + "rank": "none" + }, "d3d12testsrc": { "author": "Seungha Yang ", "description": "Creates a test video stream", diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.cpp new file mode 100644 index 0000000000..6fb9989b93 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.cpp @@ -0,0 +1,1752 @@ +/* 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. + */ + +/* + * The MIT License (MIT) + * + * Copyright (c) Microsoft Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d12dxgicapture.h" +#include "gstd3d12pluginutils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "PSMain_sample.h" +#include "VSMain_coord.h" + +#define _XM_NO_INTRINSICS_ +#include + +GST_DEBUG_CATEGORY_EXTERN (gst_d3d12_screen_capture_debug); +#define GST_CAT_DEFAULT gst_d3d12_screen_capture_debug + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +using namespace DirectX; + +/* List of GstD3D12DxgiCapture weakref */ +static std::mutex g_g_dupl_list_lock; +static GList *g_dupl_list = nullptr; + +/* Below implementation were taken from Microsoft sample + * https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/DXGIDesktopDuplication + */ + +/* List of expected error cases */ +/* These are the errors we expect from general Dxgi API due to a transition */ +static HRESULT SystemTransitionsExpectedErrors[] = { + DXGI_ERROR_DEVICE_REMOVED, + DXGI_ERROR_ACCESS_LOST, + static_cast(WAIT_ABANDONED), + S_OK +}; + +/* These are the errors we expect from IDXGIOutput1::DuplicateOutput + * due to a transition */ +static HRESULT CreateDuplicationExpectedErrors[] = { + DXGI_ERROR_DEVICE_REMOVED, + static_cast(E_ACCESSDENIED), + DXGI_ERROR_SESSION_DISCONNECTED, + S_OK +}; + +/* These are the errors we expect from IDXGIOutputDuplication methods + * due to a transition */ +static HRESULT FrameInfoExpectedErrors[] = { + DXGI_ERROR_DEVICE_REMOVED, + DXGI_ERROR_ACCESS_LOST, + S_OK +}; + +static GstFlowReturn +flow_return_from_hr (ID3D11Device * device, + HRESULT hr, HRESULT * expected_errors = nullptr) +{ + HRESULT translated_hr = hr; + + /* On an error check if the DX device is lost */ + if (device) { + HRESULT remove_reason = device->GetDeviceRemovedReason (); + + switch (remove_reason) { + case DXGI_ERROR_DEVICE_REMOVED: + case DXGI_ERROR_DEVICE_RESET: + case static_cast(E_OUTOFMEMORY): + /* Our device has been stopped due to an external event on the GPU so + * map them all to device removed and continue processing the condition + */ + translated_hr = DXGI_ERROR_DEVICE_REMOVED; + break; + case S_OK: + /* Device is not removed so use original error */ + break; + default: + /* Device is removed but not a error we want to remap */ + translated_hr = remove_reason; + break; + } + } + + /* Check if this error was expected or not */ + if (expected_errors) { + HRESULT* rst = expected_errors; + + while (*rst != S_OK) { + if (*rst == translated_hr) + return GST_D3D12_SCREEN_CAPTURE_FLOW_EXPECTED_ERROR; + + rst++; + } + } + + return GST_FLOW_ERROR; +} + +struct PtrInfo +{ + PtrInfo () + { + LastTimeStamp.QuadPart = 0; + position_info.Visible = FALSE; + } + + void buildMonochrom () + { + width_ = shape_info.Width; + height_ = shape_info.Height / 2; + stride_ = width_ * 4; + UINT pstride = 4; + auto size = height_ * stride_; + texture_.resize (size); + xor_texture_.resize (size); + + const BYTE black[] = { 0, 0, 0, 0xff }; + const BYTE white[] = { 0xff, 0xff, 0xff, 0xff }; + const BYTE transparent[] = { 0, 0, 0, 0 }; + + for (UINT row = 0; row < height_; row++) { + for (UINT col = 0; col < width_; col++) { + auto src_pos = (row * shape_info.Pitch) + (col / 8); + auto and_mask = (shape_buffer[src_pos] >> (7 - (col % 8))) & 0x1; + auto xor_mask = (shape_buffer[src_pos + size] >> (7 - (col % 8))) & 0x1; + auto dst_pos = (row * stride_) + (col * pstride); + + if (and_mask) { + memcpy (texture_.data () + dst_pos, + transparent, sizeof (transparent)); + + if (xor_mask) { + memcpy (xor_texture_.data () + dst_pos, white, sizeof (white)); + } else { + memcpy (xor_texture_.data () + dst_pos, transparent, + sizeof (transparent)); + } + } else { + memcpy (texture_.data () + dst_pos, black, sizeof (black)); + memcpy (xor_texture_.data () + dst_pos, transparent, + sizeof (transparent)); + } + } + } + } + + void buildMaskedColor () + { + width_ = shape_info.Width; + height_ = shape_info.Height; + stride_ = shape_info.Pitch; + + auto size = height_ * stride_; + UINT pstride = 4; + texture_.resize (size); + xor_texture_.resize (size); + memcpy (texture_.data (), shape_buffer.data (), size); + memcpy (xor_texture_.data (), shape_buffer.data (), size); + + for (UINT row = 0; row < height_; row++) { + for (UINT col = 0; col < width_; col++) { + auto mask_pos = row * stride_ + col * pstride + 3; + texture_[mask_pos] = shape_buffer[mask_pos] ? 0 : 0xff; + xor_texture_[mask_pos] = shape_buffer[mask_pos] ? 0xff : 0; + } + } + } + + void BuildTexture () + { + texture_.clear (); + xor_texture_.clear (); + width_ = 0; + height_ = 0; + stride_ = 0; + + switch (shape_info.Type) { + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + { + width_ = shape_info.Width; + height_ = shape_info.Height; + stride_ = shape_info.Pitch; + + auto size = stride_ * height_; + texture_.resize (size); + memcpy (texture_.data (), shape_buffer.data (), size); + break; + } + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: + buildMonochrom (); + break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + buildMaskedColor (); + break; + default: + GST_WARNING ("Unexpected shape type %u", shape_info.Type); + break; + } + + token_++; + } + + std::vector shape_buffer; + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; + DXGI_OUTDUPL_POINTER_POSITION position_info; + LARGE_INTEGER LastTimeStamp; + + UINT width_ = 0; + UINT height_ = 0; + UINT stride_ = 0; + std::vector texture_; + std::vector xor_texture_; + UINT64 token_ = 0; +}; + +struct MoveRectData +{ + RECT src_rect; + RECT dst_rect; + D3D12_BOX box; +}; + +struct VERTEX +{ + XMFLOAT3 Pos; + XMFLOAT2 TexCoord; +}; + +class DesktopDupCtx +{ +public: + DesktopDupCtx () {} + + GstFlowReturn Init (HMONITOR monitor) + { + ComPtr adapter; + ComPtr output; + ComPtr output1; + + HRESULT hr = gst_d3d12_screen_capture_find_output_for_monitor (monitor, + &adapter, &output); + if (FAILED (hr)) { + GST_ERROR ("Couldn't get adapter and output for monitor"); + return GST_FLOW_ERROR; + } + + hr = output.As (&output1); + if (FAILED (hr)) { + GST_ERROR ("Couldn't get IDXGIOutput1 interface, hr 0x%x", (guint) hr); + return GST_FLOW_ERROR; + } + + HDESK hdesk = OpenInputDesktop (0, FALSE, GENERIC_ALL); + if (hdesk) { + if (!SetThreadDesktop (hdesk)) { + GST_WARNING ("SetThreadDesktop() failed, error %lu", GetLastError()); + } + + CloseDesktop (hdesk); + } else { + GST_WARNING ("OpenInputDesktop() failed, error %lu", GetLastError()); + } + + 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, + }; + + hr = D3D11CreateDevice (adapter.Get (), D3D_DRIVER_TYPE_UNKNOWN, nullptr, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, feature_levels, + G_N_ELEMENTS (feature_levels), D3D11_SDK_VERSION, &device_, nullptr, + nullptr); + if (FAILED (hr)) { + hr = D3D11CreateDevice (adapter.Get (), D3D_DRIVER_TYPE_UNKNOWN, nullptr, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, &feature_levels[1], + G_N_ELEMENTS (feature_levels) - 1, D3D11_SDK_VERSION, &device_, + nullptr, nullptr); + } + + if (FAILED (hr)) { + GST_ERROR ("Couldn't create d3d11 device"); + return GST_FLOW_ERROR; + } + + /* FIXME: Use DuplicateOutput1 to avoid potentail color conversion */ + hr = output1->DuplicateOutput(device_.Get(), &dupl_); + if (FAILED (hr)) { + if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) { + GST_ERROR ("Hit the max allowed number of Desktop Duplication session"); + return GST_FLOW_ERROR; + } + + /* Seems to be one limitation of Desktop Duplication API design + * See + * https://docs.microsoft.com/en-US/troubleshoot/windows-client/shell-experience/error-when-dda-capable-app-is-against-gpu + */ + if (hr == DXGI_ERROR_UNSUPPORTED) { + GST_WARNING ("IDXGIOutput1::DuplicateOutput returned " + "DXGI_ERROR_UNSUPPORTED, possiblely application is run against a " + "discrete GPU"); + return GST_D3D12_SCREEN_CAPTURE_FLOW_UNSUPPORTED; + } + + return flow_return_from_hr (device_.Get(), hr, + CreateDuplicationExpectedErrors); + } + + dupl_->GetDesc (&output_desc_); + + return GST_FLOW_OK; + } + + GstFlowReturn AcquireNextFrame (IDXGIResource ** resource) + { + HRESULT hr; + + move_rect_.clear (); + dirty_rect_.clear (); + dirty_vertex_.clear (); + + if (acquired_frame_) { + acquired_frame_ = nullptr; + dupl_->ReleaseFrame (); + } + + hr = dupl_->AcquireNextFrame(0, &frame_info_, &acquired_frame_); + if (hr == DXGI_ERROR_WAIT_TIMEOUT) + return GST_FLOW_OK; + + if (FAILED (hr)) { + GST_WARNING ("AcquireNextFrame failed with 0x%x", (guint) hr); + return flow_return_from_hr (device_.Get (), hr, FrameInfoExpectedErrors); + } + + metadata_buffer_.resize (frame_info_.TotalMetadataBufferSize); + if (frame_info_.TotalMetadataBufferSize > 0) { + UINT buf_size = frame_info_.TotalMetadataBufferSize; + hr = dupl_->GetFrameMoveRects (buf_size, + (DXGI_OUTDUPL_MOVE_RECT *) metadata_buffer_.data (), &buf_size); + if (FAILED (hr)) { + GST_ERROR ("Couldn't get move rect, hr: 0x%x", (guint) hr); + return flow_return_from_hr (device_.Get (), + hr, FrameInfoExpectedErrors); + } + + auto move_count = buf_size / sizeof (DXGI_OUTDUPL_MOVE_RECT); + buildMoveRects (move_count); + + auto dirty_rects = metadata_buffer_.data () + buf_size; + buf_size = frame_info_.TotalMetadataBufferSize - buf_size; + + hr = dupl_->GetFrameDirtyRects (buf_size, (RECT *) dirty_rects, + &buf_size); + if (FAILED (hr)) { + GST_ERROR ("Couldn't get dirty rect, hr: 0x%x", (guint) hr); + return flow_return_from_hr (device_.Get (), + hr, FrameInfoExpectedErrors); + } + + auto dirty_count = buf_size / sizeof (RECT); + buildDirtyVertex ((RECT *) dirty_rects, dirty_count); + } + + if (frame_info_.LastMouseUpdateTime.QuadPart != 0) { + ptr_info_.position_info = frame_info_.PointerPosition; + ptr_info_.LastTimeStamp = frame_info_.LastMouseUpdateTime; + + if (frame_info_.PointerShapeBufferSize > 0) { + UINT buf_size; + ptr_info_.shape_buffer.resize (frame_info_.PointerShapeBufferSize); + hr = dupl_->GetFramePointerShape (frame_info_.PointerShapeBufferSize, + (void *) ptr_info_.shape_buffer.data (), &buf_size, + &ptr_info_.shape_info); + if (FAILED (hr)) { + return flow_return_from_hr(device_.Get (), + hr, FrameInfoExpectedErrors); + } + + ptr_info_.BuildTexture (); + } + } + + *resource = acquired_frame_.Get (); + (*resource)->AddRef (); + + return GST_FLOW_OK; + } + + void GetSize (guint * width, guint * height) + { + *width = output_desc_.ModeDesc.Width; + *height = output_desc_.ModeDesc.Height; + } + + DXGI_OUTDUPL_DESC GetDesc () + { + return output_desc_; + } + + UINT GetMoveCount () + { + return move_rect_.size (); + } + + const std::vector & GetMoveRects () + { + return move_rect_; + } + + UINT GetDirtyCount () + { + return dirty_rect_.size (); + } + + const std::vector & GetDirtyRects () + { + return dirty_rect_; + } + + const std::vector & GetDirtyVertex () + { + return dirty_vertex_; + } + + const PtrInfo & GetPointerInfo () + { + return ptr_info_; + } + +private: + void buildMoveRects (UINT move_count) + { + INT width = (INT) output_desc_.ModeDesc.Width; + INT height = (INT) output_desc_.ModeDesc.Height; + + for (UINT i = 0; i < move_count; i++) { + DXGI_OUTDUPL_MOVE_RECT *move_rect = + ((DXGI_OUTDUPL_MOVE_RECT *) metadata_buffer_.data ()) + i; + RECT src_rect; + RECT dst_rect; + + switch (output_desc_.Rotation) { + case DXGI_MODE_ROTATION_UNSPECIFIED: + case DXGI_MODE_ROTATION_IDENTITY: + src_rect.left = move_rect->SourcePoint.x; + src_rect.top = move_rect->SourcePoint.y; + src_rect.right = move_rect->SourcePoint.x + + move_rect->DestinationRect.right - move_rect->DestinationRect.left; + src_rect.bottom = move_rect->SourcePoint.y + + move_rect->DestinationRect.bottom - move_rect->DestinationRect.top; + dst_rect = move_rect->DestinationRect; + break; + case DXGI_MODE_ROTATION_ROTATE90: + src_rect.left = height - (move_rect->SourcePoint.y + + move_rect->DestinationRect.bottom - move_rect->DestinationRect.top); + src_rect.top = move_rect->SourcePoint.x; + src_rect.right = height - move_rect->SourcePoint.y; + src_rect.bottom = move_rect->SourcePoint.x + + move_rect->DestinationRect.right - move_rect->DestinationRect.left; + + dst_rect.left = height - move_rect->DestinationRect.bottom; + dst_rect.top = move_rect->DestinationRect.left; + dst_rect.right = height - move_rect->DestinationRect.top; + dst_rect.bottom = move_rect->DestinationRect.right; + break; + case DXGI_MODE_ROTATION_ROTATE180: + src_rect.left = width - (move_rect->SourcePoint.x + + move_rect->DestinationRect.right - move_rect->DestinationRect.left); + src_rect.top = height - (move_rect->SourcePoint.y + + move_rect->DestinationRect.bottom - move_rect->DestinationRect.top); + src_rect.right = width - move_rect->SourcePoint.x; + src_rect.bottom = height - move_rect->SourcePoint.y; + + dst_rect.left = width - move_rect->DestinationRect.right; + dst_rect.top = height - move_rect->DestinationRect.bottom; + dst_rect.right = width - move_rect->DestinationRect.left; + dst_rect.bottom = height - move_rect->DestinationRect.top; + break; + case DXGI_MODE_ROTATION_ROTATE270: + src_rect.left = move_rect->SourcePoint.x; + src_rect.top = width - (move_rect->SourcePoint.x + + move_rect->DestinationRect.right - move_rect->DestinationRect.left); + src_rect.right = move_rect->SourcePoint.y + + move_rect->DestinationRect.bottom - move_rect->DestinationRect.top; + src_rect.bottom = width - move_rect->SourcePoint.x; + + dst_rect.left = move_rect->DestinationRect.top; + dst_rect.top = width - move_rect->DestinationRect.right; + dst_rect.right = move_rect->DestinationRect.bottom; + dst_rect.bottom = width - move_rect->DestinationRect.left; + break; + default: + continue; + } + + MoveRectData rect_data = { }; + rect_data.src_rect = src_rect; + rect_data.dst_rect = dst_rect; + rect_data.box.left = src_rect.left; + rect_data.box.top = src_rect.top; + rect_data.box.right = src_rect.right; + rect_data.box.bottom = src_rect.bottom; + rect_data.box.front = 0; + rect_data.box.back = 1; + + move_rect_.push_back (rect_data); + } + } + + void buildDirtyVertex (RECT * rects, UINT num_rect) + { + if (num_rect == 0) + return; + + dirty_vertex_.resize (num_rect * 6); + FLOAT width = output_desc_.ModeDesc.Width; + FLOAT height = output_desc_.ModeDesc.Height; + FLOAT center_x = width / 2.0f; + FLOAT center_y = height / 2.0f; + + for (UINT i = 0; i < num_rect; i++) { + RECT dirty = rects[i]; + RECT dest_dirty = dirty; + UINT base = i * 6; + + dirty_rect_.push_back (dirty); + + switch (output_desc_.Rotation) { + case DXGI_MODE_ROTATION_ROTATE90: + dest_dirty.left = width - dirty.bottom; + dest_dirty.top = dirty.left; + dest_dirty.right = width - dirty.top; + dest_dirty.bottom = dirty.right; + + dirty_vertex_[base].TexCoord = XMFLOAT2 ( + dirty.right / width, dirty.bottom / height); + dirty_vertex_[base + 1].TexCoord = XMFLOAT2 ( + dirty.left / width, dirty.bottom / height); + dirty_vertex_[base + 2].TexCoord = XMFLOAT2 ( + dirty.right / width, dirty.top / height); + dirty_vertex_[base + 5].TexCoord = XMFLOAT2( + dirty.left / width, dirty.top / height); + break; + case DXGI_MODE_ROTATION_ROTATE180: + dest_dirty.left = width - dirty.right; + dest_dirty.top = height - dirty.bottom; + dest_dirty.right = width - dirty.left; + dest_dirty.bottom = height - dirty.top; + + dirty_vertex_[base].TexCoord = XMFLOAT2( + dirty.right / width, dirty.top / height); + dirty_vertex_[base + 1].TexCoord = XMFLOAT2 ( + dirty.right / width, dirty.bottom / height); + dirty_vertex_[base + 2].TexCoord = XMFLOAT2 ( + dirty.left / width, dirty.top / height); + dirty_vertex_[base + 5].TexCoord = XMFLOAT2 ( + dirty.left / width, dirty.bottom / height); + break; + case DXGI_MODE_ROTATION_ROTATE270: + dest_dirty.left = dirty.top; + dest_dirty.top = height - dirty.right; + dest_dirty.right = dirty.bottom; + dest_dirty.bottom = height - dirty.left; + + dirty_vertex_[base].TexCoord = XMFLOAT2 ( + dirty.left / width, dirty.top / height); + dirty_vertex_[base + 1].TexCoord = XMFLOAT2 ( + dirty.right / width, dirty.top / height); + dirty_vertex_[base + 2].TexCoord = XMFLOAT2 ( + dirty.left / width, dirty.bottom / height); + dirty_vertex_[base + 5].TexCoord = XMFLOAT2 ( + dirty.right / width, dirty.bottom / height); + break; + case DXGI_MODE_ROTATION_UNSPECIFIED: + case DXGI_MODE_ROTATION_IDENTITY: + default: + dirty_vertex_[base].TexCoord = XMFLOAT2 ( + dirty.left / width, dirty.bottom / height); + dirty_vertex_[base + 1].TexCoord = XMFLOAT2 ( + dirty.left / width, dirty.top / height); + dirty_vertex_[base + 2].TexCoord = XMFLOAT2 ( + dirty.right / width, dirty.bottom / height); + dirty_vertex_[base + 5].TexCoord = XMFLOAT2 ( + dirty.right / width, dirty.top / height); + break; + } + + /* Set positions */ + dirty_vertex_[base].Pos = XMFLOAT3 ( + (dest_dirty.left - center_x) / center_x, + -1 * (dest_dirty.bottom - center_y) / center_y, 0.0f); + dirty_vertex_[base + 1].Pos = XMFLOAT3 ( + (dest_dirty.left - center_x) / center_x, + -1 * (dest_dirty.top - center_y) / center_y, 0.0f); + dirty_vertex_[base + 2].Pos = XMFLOAT3 ( + (dest_dirty.right - center_x) / center_x, + -1 * (dest_dirty.bottom - center_y) / center_y, 0.0f); + dirty_vertex_[base + 3].Pos = dirty_vertex_[base + 2].Pos; + dirty_vertex_[base + 4].Pos = dirty_vertex_[base + 1].Pos; + dirty_vertex_[base + 5].Pos = XMFLOAT3 ( + (dest_dirty.right - center_x) / center_x, + -1 * (dest_dirty.top - center_y) / center_y, 0.0f); + + dirty_vertex_[base + 3].TexCoord = dirty_vertex_[base + 2].TexCoord; + dirty_vertex_[base + 4].TexCoord = dirty_vertex_[base + 1].TexCoord; + } + } + +private: + PtrInfo ptr_info_; + DXGI_OUTDUPL_DESC output_desc_; + DXGI_OUTDUPL_FRAME_INFO frame_info_; + ComPtr dupl_; + ComPtr device_; + ComPtr acquired_frame_; + std::vector move_rect_; + std::vector dirty_vertex_; + std::vector dirty_rect_; + + /* frame metadata */ + std::vector metadata_buffer_; +}; + +struct GstD3D12DxgiCapturePrivate +{ + GstD3D12DxgiCapturePrivate () + { + event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); + fence_data_pool = gst_d3d12_fence_data_pool_new (); + } + + ~GstD3D12DxgiCapturePrivate () + { + if (device) { + auto fence_to_wait = MAX (fence_val, mouse_fence_val); + gst_d3d12_device_fence_wait (device, D3D12_COMMAND_LIST_TYPE_DIRECT, + fence_to_wait, event_handle); + } + CloseHandle (event_handle); + gst_clear_buffer (&mouse_buf); + gst_clear_buffer (&mouse_xor_buf); + gst_clear_object (&ca_pool); + gst_clear_object (&fence_data_pool); + gst_clear_object (&mouse_blend); + gst_clear_object (&mouse_xor_blend); + gst_clear_object (&device); + } + + GstD3D12Device *device = nullptr; + + std::unique_ptr ctx; + ComPtr output; + ComPtr shared_resource; + ComPtr move_frame; + ComPtr processed_frame; + ComPtr rs; + ComPtr pso; + GstD3D12CommandAllocatorPool *ca_pool = nullptr; + GstD3D12FenceDataPool *fence_data_pool; + GstD3D12FenceData *mouse_fence_data = nullptr; + ComPtr cl; + ComPtr mouse_cl; + ComPtr srv_heap; + ComPtr rtv_heap; + ComPtr dirty_vertex_buf; + UINT dirty_vertex_size = 0; + GstBuffer *mouse_buf = nullptr; + GstBuffer *mouse_xor_buf = nullptr; + D3D12_VIEWPORT viewport; + D3D12_RECT scissor_rect; + D3D12_RESOURCE_STATES resource_state = D3D12_RESOURCE_STATE_COMMON; + + GstD3D12Converter *mouse_blend = nullptr; + GstD3D12Converter *mouse_xor_blend = nullptr; + + HMONITOR monitor_handle = nullptr; + RECT desktop_coordinates = { }; + + guint cached_width = 0; + guint cached_height = 0; + + HANDLE event_handle; + guint64 fence_val = 0; + guint64 mouse_fence_val = 0; + + guint64 mouse_token = 0; + + std::mutex lock; +}; +/* *INDENT-ON* */ + +struct _GstD3D12DxgiCapture +{ + GstD3D12ScreenCapture parent; + + GstD3D12Device *device; + + GstD3D12DxgiCapturePrivate *priv; +}; + +static void gst_d3d12_dxgi_capture_dispose (GObject * object); +static void gst_d3d12_dxgi_capture_finalize (GObject * object); +static void gst_d3d12_dxgi_capture_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static GstFlowReturn +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, + GST_TYPE_D3D12_SCREEN_CAPTURE); + +static void +gst_d3d12_dxgi_capture_class_init (GstD3D12DxgiCaptureClass * klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + auto capture_class = GST_D3D12_SCREEN_CAPTURE_CLASS (klass); + + object_class->finalize = gst_d3d12_dxgi_capture_finalize; + + 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 +gst_d3d12_dxgi_capture_init (GstD3D12DxgiCapture * self) +{ + self->priv = new GstD3D12DxgiCapturePrivate (); +} + +static void +gst_d3d12_dxgi_capture_finalize (GObject * object) +{ + auto self = GST_D3D12_DXGI_CAPTURE (object); + + delete self->priv; + gst_clear_object (&self->device); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_d3d12_dxgi_capture_weak_ref_notify (gpointer data, + GstD3D12DxgiCapture * dupl) +{ + std::lock_guard < std::mutex > lk (g_g_dupl_list_lock); + g_dupl_list = g_list_remove (g_dupl_list, dupl); +} + +static gboolean +gst_d3d12_dxgi_capture_open (GstD3D12DxgiCapture * self, + HMONITOR monitor_handle) +{ + const D3D12_ROOT_SIGNATURE_FLAGS rs_flags = + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | + D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_AMPLIFICATION_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_MESH_SHADER_ROOT_ACCESS; + const D3D12_STATIC_SAMPLER_DESC static_sampler_desc = { + D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT, + D3D12_TEXTURE_ADDRESS_MODE_CLAMP, + D3D12_TEXTURE_ADDRESS_MODE_CLAMP, + D3D12_TEXTURE_ADDRESS_MODE_CLAMP, + 0, + 1, + D3D12_COMPARISON_FUNC_ALWAYS, + D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK, + 0, + D3D12_FLOAT32_MAX, + 0, + 0, + D3D12_SHADER_VISIBILITY_PIXEL + }; + + auto priv = self->priv; + priv->monitor_handle = monitor_handle; + + ComPtr < IDXGIOutput > output; + auto hr = gst_d3d12_screen_capture_find_output_for_monitor (monitor_handle, + nullptr, &output); + if (!gst_d3d12_result (hr, self->device)) { + GST_WARNING_OBJECT (self, + "Failed to find associated adapter for monitor %p", monitor_handle); + return FALSE; + } + + hr = output.As (&priv->output); + if (!gst_d3d12_result (hr, self->device)) { + GST_WARNING_OBJECT (self, "IDXGIOutput1 interface unavailable"); + return FALSE; + } + + DXGI_OUTPUT_DESC output_desc; + hr = output->GetDesc (&output_desc); + if (!gst_d3d12_result (hr, self->device)) { + GST_WARNING_OBJECT (self, "Couldn't get output desc"); + return FALSE; + } + + /* DesktopCoordinates will not report actual texture size in case that + * application is running without dpi-awareness. To get actual monitor size, + * we need to use Win32 API... */ + MONITORINFOEXW monitor_info; + DEVMODEW dev_mode; + + monitor_info.cbSize = sizeof (MONITORINFOEXW); + if (!GetMonitorInfoW (output_desc.Monitor, (LPMONITORINFO) & monitor_info)) { + GST_WARNING_OBJECT (self, "Couldn't get monitor info"); + return FALSE; + } + + dev_mode.dmSize = sizeof (DEVMODEW); + dev_mode.dmDriverExtra = sizeof (POINTL); + dev_mode.dmFields = DM_POSITION; + if (!EnumDisplaySettingsW + (monitor_info.szDevice, ENUM_CURRENT_SETTINGS, &dev_mode)) { + GST_WARNING_OBJECT (self, "Couldn't enumerate display settings"); + return FALSE; + } + + priv->desktop_coordinates.left = dev_mode.dmPosition.x; + priv->desktop_coordinates.top = dev_mode.dmPosition.y; + priv->desktop_coordinates.right = + dev_mode.dmPosition.x + dev_mode.dmPelsWidth; + priv->desktop_coordinates.bottom = + dev_mode.dmPosition.y + dev_mode.dmPelsHeight; + + priv->cached_width = + priv->desktop_coordinates.right - priv->desktop_coordinates.left; + priv->cached_height = + priv->desktop_coordinates.bottom - priv->desktop_coordinates.top; + + GST_DEBUG_OBJECT (self, + "Desktop coordinates left:top:right:bottom = %ld:%ld:%ld:%ld (%dx%d)", + priv->desktop_coordinates.left, priv->desktop_coordinates.top, + priv->desktop_coordinates.right, priv->desktop_coordinates.bottom, + priv->cached_width, priv->cached_height); + + auto device = gst_d3d12_device_get_device_handle (self->device); + + CD3DX12_ROOT_PARAMETER param; + D3D12_DESCRIPTOR_RANGE range; + std::vector < D3D12_ROOT_PARAMETER > param_list; + + range = CD3DX12_DESCRIPTOR_RANGE (D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0); + param.InitAsDescriptorTable (1, &range, D3D12_SHADER_VISIBILITY_PIXEL); + param_list.push_back (param); + + D3D12_VERSIONED_ROOT_SIGNATURE_DESC rs_desc = { }; + CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC::Init_1_0 (rs_desc, + param_list.size (), param_list.data (), + 1, &static_sampler_desc, rs_flags); + ComPtr < ID3DBlob > rs_blob; + ComPtr < ID3DBlob > error_blob; + hr = D3DX12SerializeVersionedRootSignature (&rs_desc, + D3D_ROOT_SIGNATURE_VERSION_1_1, &rs_blob, &error_blob); + if (!gst_d3d12_result (hr, self->device)) { + const gchar *error_msg = nullptr; + if (error_blob) + error_msg = (const gchar *) error_blob->GetBufferPointer (); + + GST_ERROR_OBJECT (self, "Couldn't serialize root signature, error: %s", + GST_STR_NULL (error_msg)); + return FALSE; + } + + hr = device->CreateRootSignature (0, rs_blob->GetBufferPointer (), + rs_blob->GetBufferSize (), IID_PPV_ARGS (&priv->rs)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create root signature"); + return FALSE; + } + + D3D12_INPUT_ELEMENT_DESC input_desc[2]; + input_desc[0].SemanticName = "POSITION"; + input_desc[0].SemanticIndex = 0; + input_desc[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; + input_desc[0].InputSlot = 0; + input_desc[0].AlignedByteOffset = D3D12_APPEND_ALIGNED_ELEMENT; + input_desc[0].InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA; + input_desc[0].InstanceDataStepRate = 0; + + input_desc[1].SemanticName = "TEXCOORD"; + input_desc[1].SemanticIndex = 0; + input_desc[1].Format = DXGI_FORMAT_R32G32_FLOAT; + input_desc[1].InputSlot = 0; + input_desc[1].AlignedByteOffset = D3D12_APPEND_ALIGNED_ELEMENT; + input_desc[1].InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA; + input_desc[1].InstanceDataStepRate = 0; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->rs.Get (); + pso_desc.VS.BytecodeLength = sizeof (g_VSMain_coord); + pso_desc.VS.pShaderBytecode = g_VSMain_coord; + pso_desc.PS.BytecodeLength = sizeof (g_PSMain_sample); + pso_desc.PS.pShaderBytecode = g_PSMain_sample; + pso_desc.BlendState = CD3DX12_BLEND_DESC (D3D12_DEFAULT); + pso_desc.SampleMask = UINT_MAX; + pso_desc.RasterizerState = CD3DX12_RASTERIZER_DESC (D3D12_DEFAULT); + pso_desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; + pso_desc.DepthStencilState.DepthEnable = FALSE; + pso_desc.DepthStencilState.StencilEnable = FALSE; + pso_desc.InputLayout.pInputElementDescs = input_desc; + pso_desc.InputLayout.NumElements = G_N_ELEMENTS (input_desc); + pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + pso_desc.NumRenderTargets = 1; + pso_desc.RTVFormats[0] = DXGI_FORMAT_B8G8R8A8_UNORM; + pso_desc.SampleDesc.Count = 1; + + hr = device->CreateGraphicsPipelineState (&pso_desc, + IID_PPV_ARGS (&priv->pso)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + /* size will be updated later */ + GstVideoInfo info; + gst_video_info_set_format (&info, GST_VIDEO_FORMAT_BGRA, + priv->cached_width, priv->cached_height); + D3D12_BLEND_DESC blend_desc = CD3DX12_BLEND_DESC (D3D12_DEFAULT); + + blend_desc.RenderTarget[0].BlendEnable = TRUE; + blend_desc.RenderTarget[0].LogicOpEnable = FALSE; + blend_desc.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA; + blend_desc.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA; + blend_desc.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD; + blend_desc.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ZERO; + blend_desc.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE; + blend_desc.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD; + blend_desc.RenderTarget[0].LogicOp = D3D12_LOGIC_OP_NOOP; + blend_desc.RenderTarget[0].RenderTargetWriteMask = + D3D12_COLOR_WRITE_ENABLE_ALL; + + priv->mouse_blend = gst_d3d12_converter_new (self->device, &info, &info, + &blend_desc, nullptr, nullptr); + + blend_desc.RenderTarget[0].SrcBlend = D3D12_BLEND_INV_DEST_COLOR; + blend_desc.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_COLOR; + priv->mouse_xor_blend = gst_d3d12_converter_new (self->device, &info, &info, + &blend_desc, nullptr, nullptr); + + D3D12_DESCRIPTOR_HEAP_DESC heap_desc = { }; + heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + heap_desc.NumDescriptors = 1; + heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + hr = device->CreateDescriptorHeap (&heap_desc, + IID_PPV_ARGS (&priv->srv_heap)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create descriptor heap"); + return FALSE; + } + + heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + hr = device->CreateDescriptorHeap (&heap_desc, + IID_PPV_ARGS (&priv->rtv_heap)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create descriptor heap"); + return FALSE; + } + + priv->ca_pool = gst_d3d12_command_allocator_pool_new (self->device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + + priv->device = (GstD3D12Device *) gst_object_ref (self->device); + + return TRUE; +} + +GstD3D12ScreenCapture * +gst_d3d12_dxgi_capture_new (GstD3D12Device * device, HMONITOR monitor_handle) +{ + GList *iter; + + g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), nullptr); + + /* Check if we have dup object corresponding to monitor_handle, + * and if there is already configured capture object, reuse it. + * This is because of the limitation of desktop duplication API + * (i.e., in a process, only one duplication object can exist). + * See also + * https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgioutput1-duplicateoutput#remarks + */ + std::lock_guard < std::mutex > lk (g_g_dupl_list_lock); + for (iter = g_dupl_list; iter; iter = g_list_next (iter)) { + auto capture = (GstD3D12DxgiCapture *) iter->data; + auto priv = capture->priv; + + if (priv->monitor_handle == monitor_handle) { + GST_DEBUG ("Found configured desktop dup object for monitor handle %p", + monitor_handle); + gst_object_ref (capture); + return GST_D3D12_SCREEN_CAPTURE_CAST (capture); + } + } + + auto self = (GstD3D12DxgiCapture *) g_object_new (GST_TYPE_D3D12_DXGI_CAPTURE, + nullptr); + self->device = (GstD3D12Device *) gst_object_ref (device); + gst_object_ref_sink (self); + + if (!gst_d3d12_dxgi_capture_open (self, monitor_handle)) { + gst_object_unref (self); + return nullptr; + } + + g_object_weak_ref (G_OBJECT (self), + (GWeakNotify) gst_d3d12_dxgi_capture_weak_ref_notify, nullptr); + g_dupl_list = g_list_append (g_dupl_list, self); + + return GST_D3D12_SCREEN_CAPTURE_CAST (self); +} + +static GstFlowReturn +gst_d3d12_dxgi_capture_prepare_unlocked (GstD3D12DxgiCapture * self) +{ + auto priv = self->priv; + + if (priv->ctx) { + GST_DEBUG_OBJECT (self, "Already prepared"); + return GST_FLOW_OK; + } + + auto ctx = std::make_unique < DesktopDupCtx > (); + auto ret = ctx->Init (priv->monitor_handle); + if (ret != GST_FLOW_OK) { + GST_WARNING_OBJECT (self, + "Couldn't prepare capturing, %sexpected failure", + ret == GST_D3D12_SCREEN_CAPTURE_FLOW_EXPECTED_ERROR ? "" : "un"); + + return ret; + } + + ctx->GetSize (&priv->cached_width, &priv->cached_height); + priv->viewport.TopLeftX = 0; + priv->viewport.TopLeftY = 0; + priv->viewport.Width = priv->cached_width; + priv->viewport.Height = priv->cached_height; + priv->viewport.MinDepth = 0; + priv->viewport.MaxDepth = 1; + + priv->scissor_rect.left = 0; + priv->scissor_rect.top = 0; + priv->scissor_rect.right = priv->cached_width; + priv->scissor_rect.bottom = priv->cached_height; + + ComPtr < ID3D12Resource > processed_frame; + D3D12_CLEAR_VALUE clear_value = { }; + clear_value.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + clear_value.Color[0] = 0.0f; + clear_value.Color[1] = 0.0f; + clear_value.Color[2] = 0.0f; + clear_value.Color[3] = 1.0f; + + D3D12_HEAP_PROPERTIES heap_prop = + CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT); + D3D12_RESOURCE_DESC resource_desc = + CD3DX12_RESOURCE_DESC::Tex2D (DXGI_FORMAT_B8G8R8A8_UNORM, + priv->cached_width, priv->cached_height, 1, 1, 1, 0, + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | + D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS); + + auto device = gst_d3d12_device_get_device_handle (self->device); + auto hr = device->CreateCommittedResource (&heap_prop, D3D12_HEAP_FLAG_NONE, + &resource_desc, D3D12_RESOURCE_STATE_COMMON, &clear_value, + IID_PPV_ARGS (&processed_frame)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create texture"); + return GST_FLOW_ERROR; + } + + D3D12_RENDER_TARGET_VIEW_DESC rtv_desc = { }; + rtv_desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + rtv_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + rtv_desc.Texture2D.PlaneSlice = 0; + + device->CreateRenderTargetView (processed_frame.Get (), &rtv_desc, + priv->rtv_heap->GetCPUDescriptorHandleForHeapStart ()); + + priv->ctx = std::move (ctx); + priv->processed_frame = processed_frame; + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_d3d12_dxgi_capture_prepare (GstD3D12ScreenCapture * capture) +{ + auto self = GST_D3D12_DXGI_CAPTURE (capture); + auto priv = self->priv; + + std::lock_guard < std::mutex > lk (priv->lock); + return gst_d3d12_dxgi_capture_prepare_unlocked (self); +} + +static gboolean +gst_d3d12_dxgi_capture_get_size_unlocked (GstD3D12DxgiCapture * self, + guint * width, guint * height) +{ + auto priv = self->priv; + + *width = 0; + *height = 0; + + if (priv->ctx) + priv->ctx->GetSize (&priv->cached_width, &priv->cached_height); + + *width = priv->cached_width; + *height = priv->cached_height; + + return TRUE; +} + +static gboolean +gst_d3d12_dxgi_capture_get_size (GstD3D12ScreenCapture * capture, + guint * width, guint * height) +{ + auto self = GST_D3D12_DXGI_CAPTURE (capture); + auto priv = self->priv; + + std::lock_guard < std::mutex > lk (priv->lock); + + return gst_d3d12_dxgi_capture_get_size_unlocked (self, width, height); +} + +static gboolean +gst_d3d12_dxgi_capture_copy_move_rects (GstD3D12DxgiCapture * self, + ID3D12GraphicsCommandList * cl) +{ + auto priv = self->priv; + HRESULT hr; + + auto device = gst_d3d12_device_get_device_handle (self->device); + + GST_LOG_OBJECT (self, "Rendering move rects"); + + std::vector < D3D12_RESOURCE_BARRIER > barriers; + if (!priv->move_frame) { + D3D12_HEAP_PROPERTIES heap_prop = + CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT); + D3D12_RESOURCE_DESC resource_desc = priv->processed_frame->GetDesc (); + resource_desc.Flags = D3D12_RESOURCE_FLAG_NONE; + hr = device->CreateCommittedResource (&heap_prop, + D3D12_HEAP_FLAG_NONE, &resource_desc, D3D12_RESOURCE_STATE_COPY_DEST, + nullptr, IID_PPV_ARGS (&priv->move_frame)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create move texture"); + return FALSE; + } + } + + D3D12_TEXTURE_COPY_LOCATION move_frame = + CD3DX12_TEXTURE_COPY_LOCATION (priv->move_frame.Get ()); + D3D12_TEXTURE_COPY_LOCATION processed_frame = + CD3DX12_TEXTURE_COPY_LOCATION (priv->processed_frame.Get ()); + + const auto & data = priv->ctx->GetMoveRects (); + for (size_t i = 0; i < data.size (); i++) { + const auto & rect = data[i]; + cl->CopyTextureRegion (&move_frame, rect.src_rect.left, rect.src_rect.top, + 0, &processed_frame, &rect.box); + } + + priv->resource_state = D3D12_RESOURCE_STATE_COPY_DEST; + if (priv->ctx->GetDirtyCount () > 0) { + auto desc = priv->ctx->GetDesc (); + if (desc.Rotation != DXGI_MODE_ROTATION_UNSPECIFIED && + desc.Rotation != DXGI_MODE_ROTATION_IDENTITY) { + priv->resource_state |= D3D12_RESOURCE_STATE_RENDER_TARGET; + } + } + + barriers.clear (); + barriers. + push_back (CD3DX12_RESOURCE_BARRIER::Transition (priv->processed_frame. + Get (), D3D12_RESOURCE_STATE_COPY_SOURCE, priv->resource_state)); + barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (priv-> + move_frame.Get (), D3D12_RESOURCE_STATE_COPY_DEST, + D3D12_RESOURCE_STATE_COPY_SOURCE)); + cl->ResourceBarrier (barriers.size (), barriers.data ()); + for (size_t i = 0; i < data.size (); i++) { + const auto & rect = data[i]; + cl->CopyTextureRegion (&processed_frame, rect.dst_rect.left, + rect.dst_rect.top, 0, &move_frame, &rect.box); + } + + barriers.clear (); + barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (priv-> + move_frame.Get (), D3D12_RESOURCE_STATE_COPY_SOURCE, + D3D12_RESOURCE_STATE_COPY_DEST)); + cl->ResourceBarrier (barriers.size (), barriers.data ()); + + return TRUE; +} + +static gboolean +gst_d3d12_dxgi_capture_copy_dirty_rects (GstD3D12DxgiCapture * self, + IDXGIResource * resource, ID3D12GraphicsCommandList * cl) +{ + auto priv = self->priv; + HRESULT hr; + + auto device = gst_d3d12_device_get_device_handle (self->device); + + GST_LOG_OBJECT (self, "Rendering dirty rects"); + + ComPtr < IDXGIResource1 > resource1; + hr = resource->QueryInterface (IID_PPV_ARGS (&resource1)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "IDXGIResource1 interface unavailable"); + return FALSE; + } + + HANDLE shared_handle; + hr = resource1->CreateSharedHandle (nullptr, DXGI_SHARED_RESOURCE_READ, + nullptr, &shared_handle); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create shared handle"); + return FALSE; + } + + hr = device->OpenSharedHandle (shared_handle, + IID_PPV_ARGS (&priv->shared_resource)); + CloseHandle (shared_handle); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't open shared resource"); + return FALSE; + } + + auto desc = priv->ctx->GetDesc (); + if (desc.Rotation == DXGI_MODE_ROTATION_UNSPECIFIED || + desc.Rotation == DXGI_MODE_ROTATION_IDENTITY) { + const auto & rects = priv->ctx->GetDirtyRects (); + D3D12_TEXTURE_COPY_LOCATION src = + CD3DX12_TEXTURE_COPY_LOCATION (priv->shared_resource.Get ()); + D3D12_TEXTURE_COPY_LOCATION dst = + CD3DX12_TEXTURE_COPY_LOCATION (priv->processed_frame.Get ()); + D3D12_BOX box; + box.front = 0; + box.back = 1; + + GST_LOG_OBJECT (self, "Perform copy"); + + for (size_t i = 0; i < rects.size (); i++) { + const auto & rect = rects[i]; + box.left = rect.left; + box.right = rect.right; + box.top = rect.top; + box.bottom = rect.bottom; + + cl->CopyTextureRegion (&dst, box.left, box.top, 0, &src, &box); + } + + if (priv->resource_state == D3D12_RESOURCE_STATE_COMMON) + priv->resource_state = D3D12_RESOURCE_STATE_COPY_DEST; + } else { + GST_LOG_OBJECT (self, "Perform draw"); + + cl->SetGraphicsRootSignature (priv->rs.Get ()); + cl->IASetPrimitiveTopology (D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + cl->RSSetViewports (1, &priv->viewport); + cl->RSSetScissorRects (1, &priv->scissor_rect); + D3D12_CPU_DESCRIPTOR_HANDLE rtv_heaps[] = { + priv->rtv_heap->GetCPUDescriptorHandleForHeapStart () + }; + + cl->OMSetRenderTargets (1, rtv_heaps, FALSE, nullptr); + + D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = { }; + srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srv_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + srv_desc.Texture2D.PlaneSlice = 0; + srv_desc.Texture2D.MipLevels = 1; + + device->CreateShaderResourceView (priv->shared_resource.Get (), &srv_desc, + priv->srv_heap->GetCPUDescriptorHandleForHeapStart ()); + + const auto & vertex = priv->ctx->GetDirtyVertex (); + UINT buf_size = vertex.size () * sizeof (VERTEX); + if (priv->dirty_vertex_size < buf_size) + priv->dirty_vertex_buf = nullptr; + + if (!priv->dirty_vertex_buf) { + D3D12_HEAP_PROPERTIES heap_prop = + CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_UPLOAD); + D3D12_RESOURCE_DESC buffer_desc = + CD3DX12_RESOURCE_DESC::Buffer (buf_size); + hr = device->CreateCommittedResource (&heap_prop, + D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, &buffer_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, + IID_PPV_ARGS (&priv->dirty_vertex_buf)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create vertex buffer"); + return FALSE; + } + + priv->dirty_vertex_size = buf_size; + } + + CD3DX12_RANGE range (0, 0); + void *data; + hr = priv->dirty_vertex_buf->Map (0, &range, &data); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't map buffer"); + return FALSE; + } + + memcpy (data, vertex.data (), buf_size); + priv->dirty_vertex_buf->Unmap (0, nullptr); + D3D12_VERTEX_BUFFER_VIEW vbv = { }; + vbv.BufferLocation = priv->dirty_vertex_buf->GetGPUVirtualAddress (); + vbv.SizeInBytes = buf_size; + vbv.StrideInBytes = sizeof (VERTEX); + + ID3D12DescriptorHeap *heaps[] = { priv->srv_heap.Get () }; + cl->SetDescriptorHeaps (1, heaps); + cl->SetGraphicsRootDescriptorTable (0, + priv->srv_heap->GetGPUDescriptorHandleForHeapStart ()); + cl->IASetVertexBuffers (0, 1, &vbv); + cl->DrawInstanced (vertex.size (), 1, 0, 0); + + if (priv->resource_state == D3D12_RESOURCE_STATE_COMMON) + priv->resource_state = D3D12_RESOURCE_STATE_RENDER_TARGET; + } + + return TRUE; +} + +static gboolean +gst_d3d12_dxgi_capture_draw_mouse (GstD3D12DxgiCapture * self, + GstBuffer * buffer, const D3D12_BOX * crop_box) +{ + auto priv = self->priv; + const auto & info = priv->ctx->GetPointerInfo (); + HRESULT hr; + + if (!info.position_info.Visible) + return TRUE; + + if (!info.width_ || !info.height_) + return TRUE; + + if (info.position_info.Position.x + info.width_ < crop_box->left || + info.position_info.Position.x > crop_box->right || + info.position_info.Position.y + info.height_ < crop_box->top || + info.position_info.Position.y > crop_box->bottom) { + return TRUE; + } + + if (info.token_ != priv->mouse_token) { + gst_clear_buffer (&priv->mouse_buf); + gst_clear_buffer (&priv->mouse_xor_buf); + priv->mouse_token = info.token_; + } + + auto device = gst_d3d12_device_get_device_handle (self->device); + if (!priv->mouse_buf) { + ComPtr < ID3D12Resource > mouse_texture; + ComPtr < ID3D12Resource > mouse_xor_texture; + + D3D12_HEAP_PROPERTIES heap_prop = + CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT); + D3D12_RESOURCE_DESC resource_desc = + CD3DX12_RESOURCE_DESC::Tex2D (DXGI_FORMAT_B8G8R8A8_UNORM, + info.width_, info.height_, 1, 1, 1, 0, + D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS); + + hr = device->CreateCommittedResource (&heap_prop, + D3D12_HEAP_FLAG_NONE, &resource_desc, D3D12_RESOURCE_STATE_COMMON, + nullptr, IID_PPV_ARGS (&mouse_texture)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create mouse texture"); + return FALSE; + } + + if (!info.xor_texture_.empty ()) { + hr = device->CreateCommittedResource (&heap_prop, + D3D12_HEAP_FLAG_NONE, &resource_desc, D3D12_RESOURCE_STATE_COMMON, + nullptr, IID_PPV_ARGS (&mouse_xor_texture)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create mouse texture"); + return FALSE; + } + } + + priv->mouse_buf = gst_buffer_new (); + auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, self->device, + mouse_texture.Get (), 0); + gst_buffer_append_memory (priv->mouse_buf, mem); + + if (mouse_xor_texture) { + priv->mouse_xor_buf = gst_buffer_new (); + auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, self->device, + mouse_xor_texture.Get (), 0); + gst_buffer_append_memory (priv->mouse_xor_buf, mem); + } + + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout; + UINT64 buffer_size; + + device->GetCopyableFootprints (&resource_desc, + 0, 1, 0, &layout, nullptr, nullptr, &buffer_size); + + GstMapInfo map_info; + gst_buffer_map (priv->mouse_buf, &map_info, GST_MAP_WRITE); + auto src = info.texture_.data (); + auto dst = (guint8 *) map_info.data; + for (UINT i = 0; i < info.height_; i++) { + memcpy (dst, src, info.width_ * 4); + src += info.stride_; + dst += layout.Footprint.RowPitch; + } + gst_buffer_unmap (priv->mouse_buf, &map_info); + + if (priv->mouse_xor_buf) { + gst_buffer_map (priv->mouse_xor_buf, &map_info, GST_MAP_WRITE); + auto src = info.xor_texture_.data (); + auto dst = (guint8 *) map_info.data; + for (UINT i = 0; i < info.height_; i++) { + memcpy (dst, src, info.width_ * 4); + src += info.stride_; + dst += layout.Footprint.RowPitch; + } + gst_buffer_unmap (priv->mouse_xor_buf, &map_info); + } + } + + gst_d3d12_fence_data_pool_acquire (priv->fence_data_pool, + &priv->mouse_fence_data); + auto fence_data = priv->mouse_fence_data; + + GstD3D12CommandAllocator *gst_ca = nullptr; + ComPtr < ID3D12CommandAllocator > ca; + + if (!gst_d3d12_command_allocator_pool_acquire (priv->ca_pool, &gst_ca)) { + GST_ERROR_OBJECT (self, "Couldn't acquire command allocator"); + return FALSE; + } + + gst_d3d12_fence_data_add_notify (fence_data, gst_ca, + (GDestroyNotify) gst_d3d12_command_allocator_unref); + + gst_d3d12_command_allocator_get_handle (gst_ca, &ca); + hr = ca->Reset (); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command allocator"); + return FALSE; + } + + if (!priv->mouse_cl) { + hr = device->CreateCommandList (0, D3D12_COMMAND_LIST_TYPE_DIRECT, + ca.Get (), nullptr, IID_PPV_ARGS (&priv->mouse_cl)); + } else { + hr = priv->mouse_cl->Reset (ca.Get (), nullptr); + } + + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command list"); + return FALSE; + } + + auto cl = priv->mouse_cl; + gint ptr_x = info.position_info.Position.x - crop_box->left; + gint ptr_y = info.position_info.Position.y - crop_box->top; + gint ptr_w = info.width_; + gint ptr_h = info.height_; + + g_object_set (priv->mouse_blend, "src-x", 0, "src-y", 0, "src-width", + ptr_w, "src-height", ptr_h, "dest-x", ptr_x, "dest-y", ptr_y, + "dest-width", ptr_w, "dest-height", ptr_h, nullptr); + + if (!gst_d3d12_converter_convert_buffer (priv->mouse_blend, + priv->mouse_buf, buffer, fence_data, cl.Get ())) { + GST_ERROR_OBJECT (self, "Couldn't build mouse blend command"); + return FALSE; + } + + if (priv->mouse_xor_buf) { + g_object_set (priv->mouse_xor_blend, "src-x", 0, "src-y", 0, "src-width", + ptr_w, "src-height", ptr_h, "dest-x", ptr_x, "dest-y", ptr_y, + "dest-width", ptr_w, "dest-height", ptr_h, nullptr); + + if (!gst_d3d12_converter_convert_buffer (priv->mouse_xor_blend, + priv->mouse_xor_buf, buffer, fence_data, cl.Get ())) { + GST_ERROR_OBJECT (self, "Couldn't build mouse blend command"); + return FALSE; + } + } + + hr = cl->Close (); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't close command list"); + return FALSE; + } + + return TRUE; +} + +static GstFlowReturn +gst_d3d12_dxgi_capture_do_capture (GstD3D12ScreenCapture * capture, + GstBuffer * buffer, const D3D12_BOX * crop_box, gboolean draw_mouse) +{ + auto self = GST_D3D12_DXGI_CAPTURE (capture); + auto priv = self->priv; + GstFlowReturn ret = GST_FLOW_OK; + guint width, height; + GstD3D12Memory *dmem; + ID3D12Resource *out_resource; + D3D12_TEXTURE_COPY_LOCATION src, dst; + ID3D12CommandList *cmd_list[1]; + GstD3D12FenceData *fence_data = nullptr; + GstD3D12CommandAllocator *gst_ca = nullptr; + ComPtr < ID3D12CommandAllocator > ca; + HRESULT hr; + + std::lock_guard < std::mutex > lk (priv->lock); + if (!priv->ctx) { + ret = gst_d3d12_dxgi_capture_prepare_unlocked (self); + if (ret != GST_FLOW_OK) { + GST_WARNING_OBJECT (self, "We are not prepared"); + return ret; + } + } + + gst_d3d12_dxgi_capture_get_size_unlocked (self, &width, &height); + if (crop_box->left > width || crop_box->right > width || + crop_box->top > height || crop_box->bottom > height) { + GST_INFO_OBJECT (self, + "Capture area (%u, %u, %u, %u) doesn't fit into screen size %ux%u", + crop_box->left, crop_box->right, crop_box->top, + crop_box->bottom, width, height); + + return GST_D3D12_SCREEN_CAPTURE_FLOW_SIZE_CHANGED; + } + + /* Wait for previous command if dirty rect drawing happend */ + if (priv->shared_resource) { + gst_d3d12_device_fence_wait (self->device, D3D12_COMMAND_LIST_TYPE_DIRECT, + priv->fence_val, priv->event_handle); + } + priv->shared_resource = nullptr; + + ComPtr < IDXGIResource > resource; + ret = priv->ctx->AcquireNextFrame (&resource); + if (ret != GST_FLOW_OK) { + priv->ctx = nullptr; + priv->processed_frame = nullptr; + priv->move_frame = nullptr; + if (ret == GST_D3D12_SCREEN_CAPTURE_FLOW_EXPECTED_ERROR) { + GST_WARNING_OBJECT (self, "Couldn't capture frame, but expected failure"); + } else { + GST_ERROR_OBJECT (self, "Unexpected failure during capture"); + } + + return ret; + } + + GST_LOG_OBJECT (self, "Capture done"); + + std::future < gboolean > mouse_blend_ret; + if (draw_mouse) { + /* Build mouse draw command from other thread */ + mouse_blend_ret = std::async (std::launch::async, + gst_d3d12_dxgi_capture_draw_mouse, self, buffer, crop_box); + } + + auto device = gst_d3d12_device_get_device_handle (self->device); + if (!gst_d3d12_command_allocator_pool_acquire (priv->ca_pool, &gst_ca)) { + GST_ERROR_OBJECT (self, "Couldn't acquire command allocator"); + goto error; + } + + gst_d3d12_fence_data_pool_acquire (priv->fence_data_pool, &fence_data); + gst_d3d12_fence_data_add_notify (fence_data, gst_ca, + (GDestroyNotify) gst_d3d12_command_allocator_unref); + + gst_d3d12_command_allocator_get_handle (gst_ca, &ca); + + hr = ca->Reset (); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command allocator"); + goto error; + } + + if (!priv->cl) { + hr = device->CreateCommandList (0, D3D12_COMMAND_LIST_TYPE_DIRECT, + ca.Get (), priv->pso.Get (), IID_PPV_ARGS (&priv->cl)); + } else { + hr = priv->cl->Reset (ca.Get (), priv->pso.Get ()); + } + + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command list"); + goto error; + } + + priv->resource_state = D3D12_RESOURCE_STATE_COMMON; + if (priv->ctx->GetMoveCount () > 0 && + !gst_d3d12_dxgi_capture_copy_move_rects (self, priv->cl.Get ())) { + GST_ERROR_OBJECT (self, "Couldn't copy move rects"); + goto error; + } + + if (priv->ctx->GetDirtyCount () > 0 && + !gst_d3d12_dxgi_capture_copy_dirty_rects (self, resource.Get (), + priv->cl.Get ())) { + GST_ERROR_OBJECT (self, "Couldn't copy dirty rects"); + goto error; + } + + dmem = (GstD3D12Memory *) gst_buffer_peek_memory (buffer, 0); + out_resource = gst_d3d12_memory_get_resource_handle (dmem); + + src = CD3DX12_TEXTURE_COPY_LOCATION (priv->processed_frame.Get ()); + dst = CD3DX12_TEXTURE_COPY_LOCATION (out_resource); + + if (priv->resource_state != D3D12_RESOURCE_STATE_COMMON) { + D3D12_RESOURCE_BARRIER barrier = + CD3DX12_RESOURCE_BARRIER::Transition (priv->processed_frame.Get (), + priv->resource_state, D3D12_RESOURCE_STATE_COPY_SOURCE); + priv->cl->ResourceBarrier (1, &barrier); + } + + priv->cl->CopyTextureRegion (&dst, 0, 0, 0, &src, crop_box); + + hr = priv->cl->Close (); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't close command list"); + goto error; + } + + cmd_list[0] = priv->cl.Get (); + + if (!gst_d3d12_device_execute_command_lists (self->device, + D3D12_COMMAND_LIST_TYPE_DIRECT, 1, cmd_list, &priv->fence_val)) { + GST_ERROR_OBJECT (self, "Couldn't execute command list"); + goto error; + } + + gst_d3d12_device_set_fence_notify (self->device, + D3D12_COMMAND_LIST_TYPE_DIRECT, priv->fence_val, fence_data, + (GDestroyNotify) gst_d3d12_fence_data_unref); + + gst_d3d12_buffer_after_write (buffer, priv->fence_val); + + if (draw_mouse) { + auto blend_ret = mouse_blend_ret.get (); + if (!blend_ret) { + GST_ERROR_OBJECT (self, "Couldn't build mouse draw command"); + goto error; + } + + if (priv->mouse_fence_data && priv->mouse_cl) { + cmd_list[0] = priv->mouse_cl.Get (); + + if (!gst_d3d12_device_execute_command_lists (self->device, + D3D12_COMMAND_LIST_TYPE_DIRECT, 1, cmd_list, + &priv->mouse_fence_val)) { + GST_ERROR_OBJECT (self, "Couldn't execute command list"); + goto error; + } + + gst_d3d12_device_set_fence_notify (self->device, + D3D12_COMMAND_LIST_TYPE_DIRECT, priv->mouse_fence_val, + priv->mouse_fence_data, (GDestroyNotify) gst_d3d12_fence_data_unref); + priv->mouse_fence_data = nullptr; + + gst_d3d12_buffer_after_write (buffer, priv->mouse_fence_val); + } + } + + return GST_FLOW_OK; + +error: + if (mouse_blend_ret.valid ()) + mouse_blend_ret.get (); + + gst_clear_d3d12_fence_data (&priv->mouse_fence_data); + gst_clear_d3d12_fence_data (&fence_data); + gst_clear_buffer (&priv->mouse_buf); + gst_clear_buffer (&priv->mouse_xor_buf); + resource = nullptr; + priv->shared_resource = nullptr; + priv->processed_frame = nullptr; + priv->move_frame = nullptr; + priv->ctx = nullptr; + + return GST_FLOW_ERROR; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.h new file mode 100644 index 0000000000..187a7bb812 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.h @@ -0,0 +1,36 @@ +/* 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 +#include "gstd3d12screencapture.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_DXGI_CAPTURE (gst_d3d12_dxgi_capture_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12DxgiCapture, gst_d3d12_dxgi_capture, + GST, D3D12_DXGI_CAPTURE, GstD3D12ScreenCapture); + +GstD3D12ScreenCapture * gst_d3d12_dxgi_capture_new (GstD3D12Device * device, + HMONITOR monitor_handle); + +G_END_DECLS + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.cpp new file mode 100644 index 0000000000..52e4e05d72 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.cpp @@ -0,0 +1,276 @@ +/* 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 + +#include "gstd3d12screencapture.h" +#include "gstd3d12pluginutils.h" +#include + +#include + +GST_DEBUG_CATEGORY_EXTERN (gst_d3d12_screen_capture_debug); +#define GST_CAT_DEFAULT gst_d3d12_screen_capture_debug + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +#define gst_d3d12_screen_capture_parent_class parent_class +G_DEFINE_ABSTRACT_TYPE (GstD3D12ScreenCapture, gst_d3d12_screen_capture, + GST_TYPE_OBJECT); + +static void +gst_d3d12_screen_capture_class_init (GstD3D12ScreenCaptureClass * klass) +{ +} + +static void +gst_d3d12_screen_capture_init (GstD3D12ScreenCapture * self) +{ +} + +GstFlowReturn +gst_d3d12_screen_capture_prepare (GstD3D12ScreenCapture * capture) +{ + g_return_val_if_fail (GST_IS_D3D12_SCREEN_CAPTURE (capture), GST_FLOW_ERROR); + + auto klass = GST_D3D12_SCREEN_CAPTURE_GET_CLASS (capture); + g_assert (klass->prepare); + + return klass->prepare (capture); +} + +gboolean +gst_d3d12_screen_capture_get_size (GstD3D12ScreenCapture * capture, + guint * width, guint * height) +{ + g_return_val_if_fail (GST_IS_D3D12_SCREEN_CAPTURE (capture), FALSE); + g_return_val_if_fail (width != nullptr, FALSE); + g_return_val_if_fail (height != nullptr, FALSE); + + auto klass = GST_D3D12_SCREEN_CAPTURE_GET_CLASS (capture); + g_assert (klass->get_size); + + return klass->get_size (capture, width, height); +} + +gboolean +gst_d3d12_screen_capture_unlock (GstD3D12ScreenCapture * capture) +{ + g_return_val_if_fail (GST_IS_D3D12_SCREEN_CAPTURE (capture), FALSE); + + auto klass = GST_D3D12_SCREEN_CAPTURE_GET_CLASS (capture); + + if (klass->unlock) + return klass->unlock (capture); + + return TRUE; +} + +gboolean +gst_d3d12_screen_capture_unlock_stop (GstD3D12ScreenCapture * capture) +{ + g_return_val_if_fail (GST_IS_D3D12_SCREEN_CAPTURE (capture), FALSE); + + auto klass = GST_D3D12_SCREEN_CAPTURE_GET_CLASS (capture); + + if (klass->unlock_stop) + return klass->unlock_stop (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) +{ + ComPtr < IDXGIFactory1 > factory; + HRESULT hr = S_OK; + + g_return_val_if_fail (monitor != nullptr, E_INVALIDARG); + + hr = CreateDXGIFactory1 (IID_PPV_ARGS (&factory)); + if (FAILED (hr)) + return hr; + + for (UINT adapter_idx = 0;; adapter_idx++) { + ComPtr < IDXGIAdapter1 > adapter_tmp; + + hr = factory->EnumAdapters1 (adapter_idx, &adapter_tmp); + if (FAILED (hr)) + break; + + for (UINT output_idx = 0;; output_idx++) { + ComPtr < IDXGIOutput > output_tmp; + DXGI_OUTPUT_DESC desc; + + hr = adapter_tmp->EnumOutputs (output_idx, &output_tmp); + if (FAILED (hr)) + break; + + hr = output_tmp->GetDesc (&desc); + if (FAILED (hr)) + continue; + + if (desc.Monitor == monitor) { + if (adapter) + *adapter = adapter_tmp.Detach (); + if (output) + *output = output_tmp.Detach (); + + return S_OK; + } + } + } + + return E_FAIL; +} + +HRESULT +gst_d3d12_screen_capture_find_primary_monitor (HMONITOR * monitor, + IDXGIAdapter1 ** adapter, IDXGIOutput ** output) +{ + ComPtr < IDXGIFactory1 > factory; + HRESULT hr = S_OK; + + hr = CreateDXGIFactory1 (IID_PPV_ARGS (&factory)); + if (FAILED (hr)) + return hr; + + for (UINT adapter_idx = 0;; adapter_idx++) { + ComPtr < IDXGIAdapter1 > adapter_tmp; + + hr = factory->EnumAdapters1 (adapter_idx, &adapter_tmp); + if (FAILED (hr)) + break; + + for (UINT output_idx = 0;; output_idx++) { + ComPtr < IDXGIOutput > output_tmp; + DXGI_OUTPUT_DESC desc; + MONITORINFOEXW minfo; + + hr = adapter_tmp->EnumOutputs (output_idx, &output_tmp); + if (FAILED (hr)) + break; + + hr = output_tmp->GetDesc (&desc); + if (FAILED (hr)) + continue; + + minfo.cbSize = sizeof (MONITORINFOEXW); + if (!GetMonitorInfoW (desc.Monitor, &minfo)) + continue; + + if ((minfo.dwFlags & MONITORINFOF_PRIMARY) != 0) { + if (monitor) + *monitor = desc.Monitor; + if (adapter) + *adapter = adapter_tmp.Detach (); + if (output) + *output = output_tmp.Detach (); + + return S_OK; + } + } + } + + return E_FAIL; +} + +HRESULT +gst_d3d12_screen_capture_find_nth_monitor (guint index, HMONITOR * monitor, + IDXGIAdapter1 ** adapter, IDXGIOutput ** output) +{ + ComPtr < IDXGIFactory1 > factory; + HRESULT hr = S_OK; + guint num_found = 0; + + hr = CreateDXGIFactory1 (IID_PPV_ARGS (&factory)); + if (FAILED (hr)) + return hr; + + for (UINT adapter_idx = 0;; adapter_idx++) { + ComPtr < IDXGIAdapter1 > adapter_tmp; + + hr = factory->EnumAdapters1 (adapter_idx, &adapter_tmp); + if (FAILED (hr)) + break; + + for (UINT output_idx = 0;; output_idx++) { + ComPtr < IDXGIOutput > output_tmp; + DXGI_OUTPUT_DESC desc; + MONITORINFOEXW minfo; + + hr = adapter_tmp->EnumOutputs (output_idx, &output_tmp); + if (FAILED (hr)) + break; + + hr = output_tmp->GetDesc (&desc); + if (FAILED (hr)) + continue; + + minfo.cbSize = sizeof (MONITORINFOEXW); + if (!GetMonitorInfoW (desc.Monitor, &minfo)) + continue; + + if (num_found == index) { + if (monitor) + *monitor = desc.Monitor; + if (adapter) + *adapter = adapter_tmp.Detach (); + if (output) + *output = output_tmp.Detach (); + + return S_OK; + } + + num_found++; + } + } + + return E_FAIL; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.h new file mode 100644 index 0000000000..dd76730d6b --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.h @@ -0,0 +1,107 @@ +/* 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 +#include "gstd3d12.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_SCREEN_CAPTURE (gst_d3d12_screen_capture_get_type()) +#define GST_D3D12_SCREEN_CAPTURE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_D3D12_SCREEN_CAPTURE,GstD3D12ScreenCapture)) +#define GST_D3D12_SCREEN_CAPTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_D3D12_SCREEN_CAPTURE,GstD3D12ScreenCaptureClass)) +#define GST_D3D12_SCREEN_CAPTURE_GET_CLASS(obj) (GST_D3D12_SCREEN_CAPTURE_CLASS(G_OBJECT_GET_CLASS(obj))) +#define GST_IS_D3D12_SCREEN_CAPTURE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_D3D12_SCREEN_CAPTURE)) +#define GST_IS_D3D12_SCREEN_CAPTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_D3D12_SCREEN_CAPTURE)) +#define GST_D3D12_SCREEN_CAPTURE_CAST(obj) ((GstD3D12ScreenCapture*)(obj)) + +typedef struct _GstD3D12ScreenCapture GstD3D12ScreenCapture; +typedef struct _GstD3D12ScreenCaptureClass GstD3D12ScreenCaptureClass; + +#define GST_D3D12_SCREEN_CAPTURE_FLOW_EXPECTED_ERROR GST_FLOW_CUSTOM_SUCCESS +#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 _GstD3D12ScreenCapture +{ + GstObject parent; +}; + +struct _GstD3D12ScreenCaptureClass +{ + GstObjectClass parent_class; + + GstFlowReturn (*prepare) (GstD3D12ScreenCapture * capture); + + gboolean (*get_size) (GstD3D12ScreenCapture * capture, + guint * width, + guint * height); + + 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); + +GstFlowReturn gst_d3d12_screen_capture_prepare (GstD3D12ScreenCapture * capture); + +gboolean gst_d3d12_screen_capture_get_size (GstD3D12ScreenCapture * capture, + guint * width, + guint * height); + +gboolean gst_d3d12_screen_capture_unlock (GstD3D12ScreenCapture * capture); + +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); + +HRESULT gst_d3d12_screen_capture_find_primary_monitor (HMONITOR * monitor, + IDXGIAdapter1 ** adapter, + IDXGIOutput ** output); + +HRESULT gst_d3d12_screen_capture_find_nth_monitor (guint index, + HMONITOR * monitor, + IDXGIAdapter1 ** adapter, + IDXGIOutput ** output); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstD3D12ScreenCapture, gst_object_unref) + +G_END_DECLS + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturedevice.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturedevice.cpp new file mode 100644 index 0000000000..5751049676 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturedevice.cpp @@ -0,0 +1,455 @@ +/* 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 + +#include "gstd3d12screencapturedevice.h" +#include "gstd3d12screencapture.h" +#include +#include +#include +#include +#include +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +GST_DEBUG_CATEGORY_EXTERN (gst_d3d12_screen_capture_debug); +#define GST_CAT_DEFAULT gst_d3d12_screen_capture_debug + +static GstStaticCaps template_caps = + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY, + "BGRA") ", pixel-aspect-ratio = 1/1, colorimetry = (string) sRGB; " + GST_VIDEO_CAPS_MAKE ("BGRA") ", pixel-aspect-ratio = 1/1, " + "colorimetry = (string) sRGB"); + +enum +{ + PROP_0, + PROP_MONITOR_HANDLE, +}; + +struct _GstD3D12ScreenCaptureDevice +{ + GstDevice parent; + + HMONITOR monitor_handle; +}; + +static void gst_d3d12_screen_capture_device_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); +static void gst_d3d12_screen_capture_device_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static GstElement *gst_d3d12_screen_capture_device_create_element (GstDevice * + device, const gchar * name); + +G_DEFINE_TYPE (GstD3D12ScreenCaptureDevice, + gst_d3d12_screen_capture_device, GST_TYPE_DEVICE); + +static void +gst_d3d12_screen_capture_device_class_init (GstD3D12ScreenCaptureDeviceClass * + klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + auto dev_class = GST_DEVICE_CLASS (klass); + + object_class->get_property = gst_d3d12_screen_capture_device_get_property; + object_class->set_property = gst_d3d12_screen_capture_device_set_property; + + g_object_class_install_property (object_class, PROP_MONITOR_HANDLE, + g_param_spec_uint64 ("monitor-handle", "Monitor Handle", + "A HMONITOR handle", 0, G_MAXUINT64, 0, + (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY))); + + dev_class->create_element = gst_d3d12_screen_capture_device_create_element; +} + +static void +gst_d3d12_screen_capture_device_init (GstD3D12ScreenCaptureDevice * self) +{ +} + +static void +gst_d3d12_screen_capture_device_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_DEVICE (object); + + switch (prop_id) { + case PROP_MONITOR_HANDLE: + g_value_set_uint64 (value, (guint64) self->monitor_handle); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_d3d12_screen_capture_device_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_DEVICE (object); + + switch (prop_id) { + case PROP_MONITOR_HANDLE: + self->monitor_handle = (HMONITOR) g_value_get_uint64 (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstElement * +gst_d3d12_screen_capture_device_create_element (GstDevice * device, + const gchar * name) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_DEVICE (device); + auto elem = gst_element_factory_make ("d3d12screencapturesrc", name); + + g_object_set (elem, "monitor-handle", self->monitor_handle, nullptr); + + return elem; +} + +struct _GstD3D12ScreenCaptureDeviceProvider +{ + GstDeviceProvider parent; +}; + +G_DEFINE_TYPE (GstD3D12ScreenCaptureDeviceProvider, + gst_d3d12_screen_capture_device_provider, GST_TYPE_DEVICE_PROVIDER); + +static GList *gst_d3d12_screen_capture_device_provider_probe (GstDeviceProvider + * provider); + +static void + gst_d3d12_screen_capture_device_provider_class_init + (GstD3D12ScreenCaptureDeviceProviderClass * klass) +{ + auto provider_class = GST_DEVICE_PROVIDER_CLASS (klass); + + provider_class->probe = + GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_device_provider_probe); + + gst_device_provider_class_set_static_metadata (provider_class, + "Direct3D12 Screen Capture Device Provider", + "Source/Monitor", "List Direct3D12 screen capture source devices", + "Seungha Yang "); +} + +static void + gst_d3d12_screen_capture_device_provider_init + (GstD3D12ScreenCaptureDeviceProvider * self) +{ +} + +static gboolean +get_monitor_name (const MONITORINFOEXW * info, + DISPLAYCONFIG_TARGET_DEVICE_NAME * target) +{ + UINT32 num_path = 0; + UINT32 num_mode = 0; + LONG query_ret; + DISPLAYCONFIG_PATH_INFO *path_infos = nullptr; + DISPLAYCONFIG_MODE_INFO *mode_infos = nullptr; + gboolean ret = FALSE; + + memset (target, 0, sizeof (DISPLAYCONFIG_TARGET_DEVICE_NAME)); + + query_ret = GetDisplayConfigBufferSizes (QDC_ONLY_ACTIVE_PATHS, + &num_path, &num_mode); + if (query_ret != ERROR_SUCCESS || num_path == 0 || num_mode == 0) + return FALSE; + + path_infos = g_new0 (DISPLAYCONFIG_PATH_INFO, num_path); + mode_infos = g_new0 (DISPLAYCONFIG_MODE_INFO, num_mode); + + query_ret = QueryDisplayConfig (QDC_ONLY_ACTIVE_PATHS, &num_path, + path_infos, &num_mode, mode_infos, nullptr); + if (query_ret != ERROR_SUCCESS) + goto out; + + for (UINT32 i = 0; i < num_path; i++) { + DISPLAYCONFIG_PATH_INFO *p = &path_infos[i]; + DISPLAYCONFIG_SOURCE_DEVICE_NAME source; + + memset (&source, 0, sizeof (DISPLAYCONFIG_SOURCE_DEVICE_NAME)); + + source.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + source.header.size = sizeof (DISPLAYCONFIG_SOURCE_DEVICE_NAME); + source.header.adapterId = p->sourceInfo.adapterId; + source.header.id = p->sourceInfo.id; + + query_ret = DisplayConfigGetDeviceInfo (&source.header); + if (query_ret != ERROR_SUCCESS) + continue; + + if (wcscmp (info->szDevice, source.viewGdiDeviceName) != 0) + continue; + + DISPLAYCONFIG_TARGET_DEVICE_NAME tmp; + + memset (&tmp, 0, sizeof (DISPLAYCONFIG_TARGET_DEVICE_NAME)); + + tmp.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; + tmp.header.size = sizeof (DISPLAYCONFIG_TARGET_DEVICE_NAME); + tmp.header.adapterId = p->sourceInfo.adapterId; + tmp.header.id = p->targetInfo.id; + + query_ret = DisplayConfigGetDeviceInfo (&tmp.header); + if (query_ret != ERROR_SUCCESS) + continue; + + memcpy (target, &tmp, sizeof (DISPLAYCONFIG_TARGET_DEVICE_NAME)); + + ret = TRUE; + break; + } + +out: + g_free (path_infos); + g_free (mode_infos); + + return ret; +} + +/* XXX: please bump MinGW toolchain version, + * DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY defined in wingdi.h */ +typedef enum +{ + OUTPUT_TECH_OTHER = -1, + OUTPUT_TECH_HD15 = 0, + OUTPUT_TECH_SVIDEO = 1, + OUTPUT_TECH_COMPOSITE_VIDEO = 2, + OUTPUT_TECH_COMPONENT_VIDEO = 3, + OUTPUT_TECH_DVI = 4, + OUTPUT_TECH_HDMI = 5, + OUTPUT_TECH_LVDS = 6, + OUTPUT_TECH_D_JPN = 8, + OUTPUT_TECH_SDI = 9, + OUTPUT_TECH_DISPLAYPORT_EXTERNAL = 10, + OUTPUT_TECH_DISPLAYPORT_EMBEDDED = 11, + OUTPUT_TECH_UDI_EXTERNAL = 12, + OUTPUT_TECH_UDI_EMBEDDED = 13, + OUTPUT_TECH_SDTVDONGLE = 14, + OUTPUT_TECH_MIRACAST = 15, + OUTPUT_TECH_INDIRECT_WIRED = 16, + OUTPUT_TECH_INDIRECT_VIRTUAL = 17, + OUTPUT_TECH_INTERNAL = 0x80000000, + OUTPUT_TECH_FORCE_UINT32 = 0xFFFFFFFF +} GST_OUTPUT_TECHNOLOGY; + +static const gchar * +output_tech_to_string (GST_OUTPUT_TECHNOLOGY tech) +{ + switch (tech) { + case OUTPUT_TECH_HD15: + return "hd15"; + case OUTPUT_TECH_SVIDEO: + return "svideo"; + case OUTPUT_TECH_COMPOSITE_VIDEO: + return "composite-video"; + case OUTPUT_TECH_DVI: + return "dvi"; + case OUTPUT_TECH_HDMI: + return "hdmi"; + case OUTPUT_TECH_LVDS: + return "lvds"; + case OUTPUT_TECH_D_JPN: + return "d-jpn"; + case OUTPUT_TECH_SDI: + return "sdi"; + case OUTPUT_TECH_DISPLAYPORT_EXTERNAL: + return "displayport-external"; + case OUTPUT_TECH_DISPLAYPORT_EMBEDDED: + return "displayport-internal"; + case OUTPUT_TECH_UDI_EXTERNAL: + return "udi-external"; + case OUTPUT_TECH_UDI_EMBEDDED: + return "udi-embedded"; + case OUTPUT_TECH_SDTVDONGLE: + return "sdtv"; + case OUTPUT_TECH_MIRACAST: + return "miracast"; + case OUTPUT_TECH_INDIRECT_WIRED: + return "indirect-wired"; + case OUTPUT_TECH_INDIRECT_VIRTUAL: + return "indirect-virtual"; + case OUTPUT_TECH_INTERNAL: + return "internal"; + default: + break; + } + + return "unknown"; +} + +static GstDevice * +create_device (const DXGI_ADAPTER_DESC * adapter_desc, + const DXGI_OUTPUT_DESC * output_desc, + const MONITORINFOEXW * minfo, const DEVMODEW * dev_mode, + const DISPLAYCONFIG_TARGET_DEVICE_NAME * target) +{ + GstCaps *caps; + gint width, height, left, top, right, bottom; + GstStructure *props; + /* *INDENT-OFF* */ + std::wstring_convert < std::codecvt_utf8 < wchar_t >, wchar_t > converter; + /* *INDENT-ON* */ + std::string device_name; + std::string display_name; + std::string device_path; + std::string device_description; + const gchar *output_type; + gboolean primary = FALSE; + GstDevice *device; + + left = (gint) dev_mode->dmPosition.x; + top = (gint) dev_mode->dmPosition.y; + width = dev_mode->dmPelsWidth; + height = dev_mode->dmPelsHeight; + right = left + width; + bottom = top + height; + + caps = gst_static_caps_get (&template_caps); + caps = gst_caps_make_writable (caps); + gst_caps_set_simple (caps, + "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, nullptr); + + device_name = converter.to_bytes (minfo->szDevice); + display_name = converter.to_bytes (target->monitorFriendlyDeviceName); + device_path = converter.to_bytes (target->monitorDevicePath); + device_description = converter.to_bytes (adapter_desc->Description); + output_type = + output_tech_to_string ((GST_OUTPUT_TECHNOLOGY) target->outputTechnology); + if ((minfo->dwFlags & MONITORINFOF_PRIMARY) != 0) + primary = TRUE; + + props = gst_structure_new ("d3d12screencapturedevice-proplist", + "device.api", G_TYPE_STRING, "d3d12", + "device.name", G_TYPE_STRING, GST_STR_NULL (device_name.c_str ()), + "device.path", G_TYPE_STRING, GST_STR_NULL (device_path.c_str ()), + "device.primary", G_TYPE_BOOLEAN, primary, + "device.type", G_TYPE_STRING, output_type, + "device.hmonitor", G_TYPE_UINT64, (guint64) output_desc->Monitor, + "device.adapter.luid", G_TYPE_INT64, + gst_d3d12_luid_to_int64 (&adapter_desc->AdapterLuid), + "device.adapter.description", G_TYPE_STRING, + GST_STR_NULL (device_description.c_str ()), + "desktop.coordinates.left", G_TYPE_INT, + (gint) output_desc->DesktopCoordinates.left, + "desktop.coordinates.top", G_TYPE_INT, + (gint) output_desc->DesktopCoordinates.top, + "desktop.coordinates.right", G_TYPE_INT, + (gint) output_desc->DesktopCoordinates.right, + "desktop.coordinates.bottom", G_TYPE_INT, + (gint) output_desc->DesktopCoordinates.bottom, + "display.coordinates.left", G_TYPE_INT, left, + "display.coordinates.top", G_TYPE_INT, top, + "display.coordinates.right", G_TYPE_INT, right, + "display.coordinates.bottom", G_TYPE_INT, bottom, nullptr); + + device = (GstDevice *) g_object_new (GST_TYPE_D3D12_SCREEN_CAPTURE_DEVICE, + "display-name", display_name.c_str (), "caps", caps, "device-class", + "Source/Monitor", "properties", props, "monitor-handle", + (guint64) output_desc->Monitor, nullptr); + + gst_caps_unref (caps); + + return device; +} + +static GList * +gst_d3d12_screen_capture_device_provider_probe (GstDeviceProvider * provider) +{ + GList *devices = nullptr; + ComPtr < IDXGIFactory1 > factory; + HRESULT hr = S_OK; + + hr = CreateDXGIFactory1 (IID_PPV_ARGS (&factory)); + if (FAILED (hr)) + return nullptr; + + for (UINT adapter_idx = 0;; adapter_idx++) { + ComPtr < IDXGIAdapter1 > adapter; + DXGI_ADAPTER_DESC adapter_desc; + + hr = factory->EnumAdapters1 (adapter_idx, &adapter); + if (FAILED (hr)) + break; + + hr = adapter->GetDesc (&adapter_desc); + if (FAILED (hr)) + continue; + + for (UINT output_idx = 0;; output_idx++) { + ComPtr < IDXGIOutput > output; + ComPtr < IDXGIOutput1 > output1; + DXGI_OUTPUT_DESC desc; + MONITORINFOEXW minfo; + DEVMODEW dev_mode; + DISPLAYCONFIG_TARGET_DEVICE_NAME target; + GstDevice *dev; + + hr = adapter->EnumOutputs (output_idx, &output); + if (FAILED (hr)) + break; + + hr = output.As (&output1); + if (FAILED (hr)) + continue; + + hr = output->GetDesc (&desc); + if (FAILED (hr)) + continue; + + minfo.cbSize = sizeof (MONITORINFOEXW); + if (!GetMonitorInfoW (desc.Monitor, &minfo)) + continue; + + dev_mode.dmSize = sizeof (DEVMODEW); + dev_mode.dmDriverExtra = sizeof (POINTL); + dev_mode.dmFields = DM_POSITION; + if (!EnumDisplaySettingsW (minfo.szDevice, + ENUM_CURRENT_SETTINGS, &dev_mode)) { + continue; + } + + /* Human readable monitor name is not always availabe, if it's empty + * fill it with generic one */ + if (!get_monitor_name (&minfo, &target) || + wcslen (target.monitorFriendlyDeviceName) == 0) { + wcscpy (target.monitorFriendlyDeviceName, L"Generic PnP Monitor"); + } + + dev = create_device (&adapter_desc, &desc, &minfo, &dev_mode, &target); + devices = g_list_append (devices, dev); + } + } + + return devices; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturedevice.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturedevice.h new file mode 100644 index 0000000000..6264e9b8fb --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturedevice.h @@ -0,0 +1,37 @@ +/* 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 "gstd3d12.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_SCREEN_CAPTURE_DEVICE (gst_d3d12_screen_capture_device_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12ScreenCaptureDevice, gst_d3d12_screen_capture_device, + GST, D3D12_SCREEN_CAPTURE_DEVICE, GstDevice); + +#define GST_TYPE_D3D12_SCREEN_CAPTURE_DEVICE_PROVIDER (gst_d3d12_screen_capture_device_provider_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12ScreenCaptureDeviceProvider, + gst_d3d12_screen_capture_device_provider, + GST, D3D12_SCREEN_CAPTURE_DEVICE_PROVIDER, GstDeviceProvider); + +G_END_DECLS + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.cpp new file mode 100644 index 0000000000..127f60f2d0 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.cpp @@ -0,0 +1,1006 @@ +/* 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. + */ + +/** + * SECTION:element-d3d12screencapturesrc + * @title: d3d12screencapturesrc + * + * Direct3D12 screen capture element + * + * ## Example launch line + * ``` + * gst-launch-1.0 d3d12screencapturesrc ! queue ! d3d12videosink + * ``` + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d12screencapturesrc.h" +#include "gstd3d12dxgicapture.h" +#include "gstd3d12pluginutils.h" +#include +#include +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +GST_DEBUG_CATEGORY (gst_d3d12_screen_capture_debug); +#define GST_CAT_DEFAULT gst_d3d12_screen_capture_debug + +enum +{ + PROP_0, + PROP_MONITOR_INDEX, + PROP_MONITOR_HANDLE, + PROP_SHOW_CURSOR, + PROP_CROP_X, + PROP_CROP_Y, + PROP_CROP_WIDTH, + PROP_CROP_HEIGHT, +}; + +#define DEFAULT_MONITOR_INDEX -1 +#define DEFAULT_SHOW_CURSOR FALSE + +static GstStaticPadTemplate src_template = + GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY, "BGRA") + ", pixel-aspect-ratio = 1/1, colorimetry = (string) sRGB; " + GST_VIDEO_CAPS_MAKE ("BGRA") ", pixel-aspect-ratio = 1/1, " + "colorimetry = (string) sRGB")); + +struct GstD3D12ScreenCaptureSrcPrivate +{ + ~GstD3D12ScreenCaptureSrcPrivate () + { + if (pool) { + gst_buffer_pool_set_active (pool, FALSE); + gst_clear_object (&pool); + } + + gst_clear_object (&capture); + } + + guint64 last_frame_no = 0; + GstClockID clock_id = nullptr; + GstVideoInfo video_info; + + GstD3D12ScreenCapture *capture = nullptr; + + GstBufferPool *pool = nullptr; + + gint64 adapter_luid = 0; + gint monitor_index = DEFAULT_MONITOR_INDEX; + HMONITOR monitor_handle = nullptr; + gboolean show_cursor = DEFAULT_SHOW_CURSOR; + + guint crop_x = 0; + guint crop_y = 0; + guint crop_w = 0; + guint crop_h = 0; + D3D12_BOX crop_box = { }; + + gboolean flushing = FALSE; + GstClockTime latency = GST_CLOCK_TIME_NONE; + + gboolean downstream_supports_d3d12 = FALSE; + + std::recursive_mutex lock; + std::mutex flush_lock; +}; + +struct _GstD3D12ScreenCaptureSrc +{ + GstBaseSrc src; + GstD3D12Device *device; + GstD3D12ScreenCaptureSrcPrivate *priv; +}; + +static void gst_d3d12_screen_capture_src_finalize (GObject * object); +static void gst_d3d12_screen_capture_src_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_d3d12_screen_capture_src_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static GstClock *gst_d3d12_screen_capture_src_provide_clock (GstElement * + element); +static void gst_d3d12_screen_capture_src_set_context (GstElement * element, + GstContext * context); + +static GstCaps *gst_d3d12_screen_capture_src_get_caps (GstBaseSrc * bsrc, + GstCaps * filter); +static GstCaps *gst_d3d12_screen_capture_src_fixate (GstBaseSrc * bsrc, + GstCaps * caps); +static gboolean gst_d3d12_screen_capture_src_set_caps (GstBaseSrc * bsrc, + GstCaps * caps); +static gboolean gst_d3d12_screen_capture_src_decide_allocation (GstBaseSrc * + bsrc, GstQuery * query); +static gboolean gst_d3d12_screen_capture_src_start (GstBaseSrc * bsrc); +static gboolean gst_d3d12_screen_capture_src_stop (GstBaseSrc * bsrc); +static gboolean gst_d3d12_screen_capture_src_unlock (GstBaseSrc * bsrc); +static gboolean gst_d3d12_screen_capture_src_unlock_stop (GstBaseSrc * bsrc); +static gboolean +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); + +#define gst_d3d12_screen_capture_src_parent_class parent_class +G_DEFINE_TYPE (GstD3D12ScreenCaptureSrc, gst_d3d12_screen_capture_src, + GST_TYPE_BASE_SRC); + +static void +gst_d3d12_screen_capture_src_class_init (GstD3D12ScreenCaptureSrcClass * klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + auto element_class = GST_ELEMENT_CLASS (klass); + auto basesrc_class = GST_BASE_SRC_CLASS (klass); + + object_class->finalize = gst_d3d12_screen_capture_src_finalize; + object_class->set_property = gst_d3d12_screen_capture_src_set_property; + object_class->get_property = gst_d3d12_screen_capture_src_get_property; + + g_object_class_install_property (object_class, PROP_MONITOR_INDEX, + g_param_spec_int ("monitor-index", "Monitor Index", + "Zero-based index for monitor to capture (-1 = primary monitor)", + -1, G_MAXINT, DEFAULT_MONITOR_INDEX, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_MONITOR_HANDLE, + g_param_spec_uint64 ("monitor-handle", "Monitor Handle", + "A HMONITOR handle of monitor to capture", + 0, G_MAXUINT64, 0, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_SHOW_CURSOR, + g_param_spec_boolean ("show-cursor", + "Show Mouse Cursor", "Whether to show mouse cursor", + DEFAULT_SHOW_CURSOR, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_CROP_X, + g_param_spec_uint ("crop-x", "Crop X", + "Horizontal coordinate of top left corner for the screen capture area", + 0, G_MAXUINT, 0, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_CROP_Y, + g_param_spec_uint ("crop-y", "Crop Y", + "Vertical coordinate of top left corner for the screen capture area", + 0, G_MAXUINT, 0, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_CROP_WIDTH, + g_param_spec_uint ("crop-width", "Crop Width", + "Width of screen capture area (0 = maximum)", + 0, G_MAXUINT, 0, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_CROP_HEIGHT, + g_param_spec_uint ("crop-height", "Crop Height", + "Height of screen capture area (0 = maximum)", + 0, G_MAXUINT, 0, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + element_class->provide_clock = + GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_provide_clock); + element_class->set_context = + GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_set_context); + + gst_element_class_set_static_metadata (element_class, + "Direct3D12 Screen Capture Source", "Source/Video", + "Captures desktop screen", "Seungha Yang "); + + gst_element_class_add_static_pad_template (element_class, &src_template); + + basesrc_class->get_caps = + GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_get_caps); + basesrc_class->fixate = + GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_fixate); + basesrc_class->set_caps = + GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_set_caps); + basesrc_class->decide_allocation = + GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_decide_allocation); + basesrc_class->start = GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_start); + basesrc_class->stop = GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_stop); + basesrc_class->unlock = + GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_unlock); + basesrc_class->unlock_stop = + GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_unlock_stop); + basesrc_class->query = + GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_src_query); + basesrc_class->create = + GST_DEBUG_FUNCPTR (gst_d3d12_screen_capture_src_create); + + GST_DEBUG_CATEGORY_INIT (gst_d3d12_screen_capture_debug, + "d3d12screencapturesrc", 0, "d3d12screencapturesrc"); +} + +static void +gst_d3d12_screen_capture_src_init (GstD3D12ScreenCaptureSrc * self) +{ + gst_base_src_set_live (GST_BASE_SRC (self), TRUE); + gst_base_src_set_format (GST_BASE_SRC (self), GST_FORMAT_TIME); + + self->priv = new GstD3D12ScreenCaptureSrcPrivate (); + + GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_PROVIDE_CLOCK); + GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_REQUIRE_CLOCK); +} + +static void +gst_d3d12_screen_capture_src_finalize (GObject * object) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (object); + + delete self->priv; + gst_clear_object (&self->device); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_d3d12_screen_capture_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (object); + auto priv = self->priv; + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + switch (prop_id) { + case PROP_MONITOR_INDEX: + priv->monitor_index = g_value_get_int (value); + break; + case PROP_MONITOR_HANDLE: + priv->monitor_handle = (HMONITOR) g_value_get_uint64 (value); + break; + case PROP_SHOW_CURSOR: + priv->show_cursor = g_value_get_boolean (value); + break; + case PROP_CROP_X: + priv->crop_x = g_value_get_uint (value); + break; + case PROP_CROP_Y: + priv->crop_y = g_value_get_uint (value); + break; + case PROP_CROP_WIDTH: + priv->crop_w = g_value_get_uint (value); + break; + case PROP_CROP_HEIGHT: + priv->crop_h = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; +} + +static void +gst_d3d12_screen_capture_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (object); + auto priv = self->priv; + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + switch (prop_id) { + case PROP_MONITOR_INDEX: + g_value_set_int (value, priv->monitor_index); + break; + case PROP_MONITOR_HANDLE: + g_value_set_uint64 (value, (guint64) priv->monitor_handle); + break; + case PROP_SHOW_CURSOR: + g_value_set_boolean (value, priv->show_cursor); + break; + case PROP_CROP_X: + g_value_set_uint (value, priv->crop_x); + break; + case PROP_CROP_Y: + g_value_set_uint (value, priv->crop_y); + break; + case PROP_CROP_WIDTH: + g_value_set_uint (value, priv->crop_w); + break; + case PROP_CROP_HEIGHT: + g_value_set_uint (value, priv->crop_h); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; +} + +static GstClock * +gst_d3d12_screen_capture_src_provide_clock (GstElement * element) +{ + return gst_system_clock_obtain (); +} + +static void +gst_d3d12_screen_capture_src_set_context (GstElement * element, + GstContext * context) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (element); + auto priv = self->priv; + + { + std::lock_guard < std::recursive_mutex > lk (priv->lock); + gst_d3d12_handle_set_context_for_adapter_luid (element, + context, priv->adapter_luid, &self->device); + } + + GST_ELEMENT_CLASS (parent_class)->set_context (element, context); +} + +static void +gst_d3d12_screen_capture_src_get_crop_box (GstD3D12ScreenCaptureSrc * self, + D3D12_BOX & box) +{ + auto priv = self->priv; + guint screen_width, screen_height; + + box.front = 0; + box.back = 1; + + 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) { + GST_WARNING ("Capture region outside of the screen bounds; ignoring."); + + box.left = 0; + box.top = 0; + 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; + } +} + +static GstCaps * +gst_d3d12_screen_capture_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (bsrc); + auto priv = self->priv; + guint width, height; + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (!priv->capture) { + GST_DEBUG_OBJECT (self, "capture object is not configured yet"); + 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; + + auto caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (bsrc)); + caps = gst_caps_make_writable (caps); + + gst_caps_set_simple (caps, "width", G_TYPE_INT, width, "height", + G_TYPE_INT, height, nullptr); + + if (filter) { + auto tmp = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (caps); + + caps = tmp; + } + + return caps; +} + +static GstCaps * +gst_d3d12_screen_capture_src_fixate (GstBaseSrc * bsrc, GstCaps * caps) +{ + GstCaps *d3d12_caps = nullptr; + + caps = gst_caps_make_writable (caps); + auto size = gst_caps_get_size (caps); + + for (guint i = 0; i < size; i++) { + auto s = gst_caps_get_structure (caps, i); + gst_structure_fixate_field_nearest_fraction (s, "framerate", 30, 1); + + if (!d3d12_caps) { + auto features = gst_caps_get_features (caps, i); + + if (gst_caps_features_contains (features, + GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY)) { + d3d12_caps = gst_caps_new_empty (); + gst_caps_append_structure (d3d12_caps, gst_structure_copy (s)); + + gst_caps_set_features (d3d12_caps, 0, + gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY, + nullptr)); + break; + } + } + } + + if (d3d12_caps) { + gst_caps_unref (caps); + caps = d3d12_caps; + } + + return gst_caps_fixate (caps); +} + +static gboolean +gst_d3d12_screen_capture_src_set_caps (GstBaseSrc * bsrc, GstCaps * caps) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (bsrc); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Set caps %" GST_PTR_FORMAT, caps); + + auto features = gst_caps_get_features (caps, 0); + if (gst_caps_features_contains (features, + GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY)) { + priv->downstream_supports_d3d12 = TRUE; + } else { + priv->downstream_supports_d3d12 = FALSE; + } + + gst_video_info_from_caps (&priv->video_info, caps); + gst_base_src_set_blocksize (bsrc, GST_VIDEO_INFO_SIZE (&priv->video_info)); + + return TRUE; +} + +static gboolean +gst_d3d12_screen_capture_src_decide_allocation (GstBaseSrc * bsrc, + GstQuery * query) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (bsrc); + auto priv = self->priv; + GstBufferPool *pool = nullptr; + GstStructure *config; + GstCaps *caps; + guint min, max, size; + gboolean update_pool; + GstVideoInfo vinfo; + + if (priv->pool) { + gst_buffer_pool_set_active (priv->pool, FALSE); + gst_clear_object (&priv->pool); + } + + gst_query_parse_allocation (query, &caps, nullptr); + + if (!caps) { + GST_ERROR_OBJECT (self, "No output caps"); + return FALSE; + } + + gst_video_info_from_caps (&vinfo, caps); + + if (gst_query_get_n_allocation_pools (query) > 0) { + gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); + update_pool = TRUE; + } else { + size = GST_VIDEO_INFO_SIZE (&vinfo); + + min = max = 0; + update_pool = FALSE; + } + + if (pool && priv->downstream_supports_d3d12) { + if (!GST_IS_D3D12_BUFFER_POOL (pool)) { + gst_clear_object (&pool); + } else { + auto dpool = GST_D3D12_BUFFER_POOL (pool); + if (dpool->device != self->device) + gst_clear_object (&pool); + } + } + + if (!pool) { + if (priv->downstream_supports_d3d12) + pool = gst_d3d12_buffer_pool_new (self->device); + else + pool = gst_video_buffer_pool_new (); + } + + config = gst_buffer_pool_get_config (pool); + + gst_buffer_pool_config_set_params (config, caps, size, min, max); + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + + if (priv->downstream_supports_d3d12) { + D3D12_RESOURCE_FLAGS resource_flags = + D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS | + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + + auto params = gst_buffer_pool_config_get_d3d12_allocation_params (config); + if (!params) { + params = gst_d3d12_allocation_params_new (self->device, &vinfo, + GST_D3D12_ALLOCATION_FLAG_DEFAULT, resource_flags); + } else { + gst_d3d12_allocation_params_set_resource_flags (params, resource_flags); + gst_d3d12_allocation_params_unset_resource_flags (params, + D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE); + } + + gst_buffer_pool_config_set_d3d12_allocation_params (config, params); + gst_d3d12_allocation_params_free (params); + } + + if (!gst_buffer_pool_set_config (pool, config)) { + GST_ERROR_OBJECT (self, "Failed to set config"); + gst_clear_object (&pool); + return FALSE; + } + + /* d3d12 buffer pool will update buffer size based on allocated texture, + * get size from config again */ + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_get_params (config, nullptr, &size, nullptr, nullptr); + gst_structure_free (config); + + if (!priv->downstream_supports_d3d12) { + priv->pool = gst_d3d12_buffer_pool_new (self->device); + + config = gst_buffer_pool_get_config (priv->pool); + + gst_buffer_pool_config_set_params (config, caps, size, 0, 0); + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + + D3D12_RESOURCE_FLAGS resource_flags = + D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS | + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + + auto params = gst_d3d12_allocation_params_new (self->device, &vinfo, + GST_D3D12_ALLOCATION_FLAG_DEFAULT, resource_flags); + gst_buffer_pool_config_set_d3d12_allocation_params (config, params); + gst_d3d12_allocation_params_free (params); + + if (!gst_buffer_pool_set_config (priv->pool, config)) { + GST_ERROR_OBJECT (self, "Failed to set config for internal pool"); + gst_clear_object (&priv->pool); + gst_clear_object (&pool); + return FALSE; + } + + if (!gst_buffer_pool_set_active (priv->pool, TRUE)) { + GST_ERROR_OBJECT (self, "Failed to activate internal pool"); + gst_clear_object (&pool); + return FALSE; + } + } + + if (update_pool) + gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max); + else + gst_query_add_allocation_pool (query, pool, size, min, max); + + gst_object_unref (pool); + + return TRUE; +} + +static gboolean +gst_d3d12_screen_capture_src_start (GstBaseSrc * bsrc) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (bsrc); + auto priv = self->priv; + GstFlowReturn ret; + HMONITOR monitor = priv->monitor_handle; + ComPtr < IDXGIAdapter1 > adapter; + DXGI_ADAPTER_DESC desc; + GstD3D12ScreenCapture *capture = nullptr; + HRESULT hr; + + 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; + } + + 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); + } + + if (!self->device) { + 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 (!capture) { + GST_ERROR_OBJECT (self, "Couldn't create capture object"); + goto error; + } + + /* Check if we can open device */ + ret = gst_d3d12_screen_capture_prepare (capture); + switch (ret) { + case GST_D3D12_SCREEN_CAPTURE_FLOW_EXPECTED_ERROR: + case GST_FLOW_OK: + break; + case GST_D3D12_SCREEN_CAPTURE_FLOW_UNSUPPORTED: + goto unsupported; + default: + goto error; + } + + priv->last_frame_no = -1; + priv->latency = GST_CLOCK_TIME_NONE; + priv->capture = capture; + + return TRUE; + +error: + { + gst_clear_object (&capture); + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("Failed to prepare capture object with given configuration, " + "monitor-index: %d, monitor-handle: %p", + priv->monitor_index, priv->monitor_handle), (nullptr)); + return FALSE; + } + +unsupported: + { + gst_clear_object (&capture); + GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, + ("Failed to prepare capture object with given configuration, " + "monitor-index: %d, monitor-handle: %p", + priv->monitor_index, priv->monitor_handle), + ("Try run the application on the integrated GPU")); + return FALSE; + } +} + +static gboolean +gst_d3d12_screen_capture_src_stop (GstBaseSrc * bsrc) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (bsrc); + auto priv = self->priv; + + if (priv->pool) { + gst_buffer_pool_set_active (priv->pool, FALSE); + gst_clear_object (&priv->pool); + } + + gst_clear_object (&priv->capture); + gst_clear_object (&self->device); + + return TRUE; +} + +static gboolean +gst_d3d12_screen_capture_src_unlock (GstBaseSrc * bsrc) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (bsrc); + auto priv = self->priv; + + std::lock_guard < std::mutex > lk (priv->flush_lock); + if (priv->clock_id) { + GST_DEBUG_OBJECT (self, "Waking up waiting clock"); + gst_clock_id_unschedule (priv->clock_id); + } + priv->flushing = TRUE; + + return TRUE; +} + +static gboolean +gst_d3d12_screen_capture_src_unlock_stop (GstBaseSrc * bsrc) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (bsrc); + auto priv = self->priv; + + std::lock_guard < std::mutex > lk (priv->flush_lock); + priv->flushing = FALSE; + + return TRUE; +} + +static gboolean +gst_d3d12_screen_capture_src_src_query (GstBaseSrc * bsrc, GstQuery * query) +{ + auto self = GST_D3D12_SCREEN_CAPTURE_SRC (bsrc); + auto priv = self->priv; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONTEXT: + { + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (gst_d3d12_handle_context_query (GST_ELEMENT_CAST (self), query, + self->device)) { + return TRUE; + } + break; + } + case GST_QUERY_LATENCY: + { + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (GST_CLOCK_TIME_IS_VALID (priv->latency)) { + gst_query_set_latency (query, TRUE, priv->latency, GST_CLOCK_TIME_NONE); + return TRUE; + } + break; + } + default: + break; + } + + return GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query); +} + +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; + gint fps_n, fps_d; + GstFlowReturn ret = GST_FLOW_OK; + GstClockTime base_time; + GstClockTime next_capture_ts; + GstClockTime latency; + GstClockTime dur; + guint64 next_frame_no; + gboolean draw_mouse; + /* Just magic number... */ + gint unsupported_retry_count = 100; + 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; + } + + fps_n = GST_VIDEO_INFO_FPS_N (&priv->video_info); + fps_d = GST_VIDEO_INFO_FPS_D (&priv->video_info); + + if (fps_n <= 0 || fps_d <= 0) + return GST_FLOW_NOT_NEGOTIATED; + + { + std::lock_guard < std::recursive_mutex > lk (priv->lock); + draw_mouse = priv->show_cursor; + gst_d3d12_screen_capture_src_get_crop_box (self, crop_box); + if (crop_box.left != priv->crop_box.left || + crop_box.right != priv->crop_box.right || + crop_box.top != priv->crop_box.top || + crop_box.bottom != priv->crop_box.bottom) { + GST_INFO_OBJECT (self, "Capture area changed, need negotiation"); + if (!gst_base_src_negotiate (bsrc)) { + GST_ERROR_OBJECT (self, "Failed to negotiate with new capture area"); + return GST_FLOW_NOT_NEGOTIATED; + } + } + } + +again: + auto clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); + + /* Check flushing before waiting clock because we are might be doing + * retry */ + { + 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; + + if (!buffer) { + if (priv->downstream_supports_d3d12) { + ret = GST_BASE_SRC_CLASS (parent_class)->alloc (bsrc, + offset, size, &buffer); + } else { + ret = gst_buffer_pool_acquire_buffer (priv->pool, &buffer, nullptr); + } + + if (ret != GST_FLOW_OK) + return ret; + } + + auto before_capture = gst_util_get_timestamp (); + ret = gst_d3d12_screen_capture_do_capture (priv->capture, buffer, + &crop_box, draw_mouse); + + switch (ret) { + case GST_D3D12_SCREEN_CAPTURE_FLOW_EXPECTED_ERROR: + GST_WARNING_OBJECT (self, "Got expected error, try again"); + goto again; + case GST_D3D12_SCREEN_CAPTURE_FLOW_UNSUPPORTED: + GST_WARNING_OBJECT (self, "Got DXGI_ERROR_UNSUPPORTED error"); + unsupported_retry_count--; + + if (unsupported_retry_count < 0) { + gst_clear_buffer (&buffer); + GST_ERROR_OBJECT (self, "too many DXGI_ERROR_UNSUPPORTED"); + return GST_FLOW_ERROR; + } + + goto again; + case GST_D3D12_SCREEN_CAPTURE_FLOW_SIZE_CHANGED: + GST_INFO_OBJECT (self, "Size was changed, need negotiation"); + gst_clear_buffer (&buffer); + + if (!gst_base_src_negotiate (bsrc)) { + GST_ERROR_OBJECT (self, "Failed to negotiate with new size"); + return GST_FLOW_NOT_NEGOTIATED; + } + goto again; + default: + break; + } + + if (ret != GST_FLOW_OK) { + gst_clear_buffer (&buffer); + return ret; + } + + if (!priv->downstream_supports_d3d12) { + GstVideoFrame in_frame, out_frame; + GstBuffer *sysmem_buf = nullptr; + ret = GST_BASE_SRC_CLASS (parent_class)->alloc (bsrc, + offset, size, &sysmem_buf); + if (ret != GST_FLOW_OK) { + gst_clear_buffer (&buffer); + return ret; + } + + if (!gst_video_frame_map (&in_frame, &priv->video_info, buffer, + GST_MAP_READ)) { + GST_ERROR_OBJECT (self, "Couldn't map frame"); + gst_clear_buffer (&buffer); + gst_clear_buffer (&sysmem_buf); + return GST_FLOW_ERROR; + } + + if (!gst_video_frame_map (&out_frame, &priv->video_info, sysmem_buf, + GST_MAP_WRITE)) { + GST_ERROR_OBJECT (self, "Couldn't map frame"); + gst_video_frame_unmap (&in_frame); + gst_clear_buffer (&buffer); + gst_clear_buffer (&sysmem_buf); + return GST_FLOW_ERROR; + } + + auto copy_ret = gst_video_frame_copy (&out_frame, &in_frame); + gst_video_frame_unmap (&out_frame); + gst_video_frame_unmap (&in_frame); + + if (!copy_ret) { + GST_ERROR_OBJECT (self, "Couldn't copy frame"); + gst_clear_buffer (&buffer); + gst_clear_buffer (&sysmem_buf); + return GST_FLOW_ERROR; + } + + gst_buffer_unref (buffer); + buffer = sysmem_buf; + } + + GST_BUFFER_DTS (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_PTS (buffer) = next_capture_ts; + GST_BUFFER_DURATION (buffer) = dur; + + auto after_capture = gst_util_get_timestamp (); + latency = after_capture - before_capture; + + { + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (!GST_CLOCK_TIME_IS_VALID (priv->latency) || priv->latency < latency) { + priv->latency = latency; + gst_element_post_message (GST_ELEMENT_CAST (self), + gst_message_new_latency (GST_OBJECT_CAST (self))); + } + } + + *buf = buffer; + + return GST_FLOW_OK; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.h new file mode 100644 index 0000000000..850f5434e7 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.h @@ -0,0 +1,34 @@ +/* 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 +#include +#include "gstd3d12.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_SCREEN_CAPTURE_SRC (gst_d3d12_screen_capture_src_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12ScreenCaptureSrc, gst_d3d12_screen_capture_src, + GST, D3D12_SCREEN_CAPTURE_SRC, GstBaseSrc); + +G_END_DECLS + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/meson.build b/subprojects/gst-plugins-bad/sys/d3d12/meson.build index dee0e52d8a..b5f654da1f 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/meson.build +++ b/subprojects/gst-plugins-bad/sys/d3d12/meson.build @@ -14,6 +14,7 @@ d3d12_sources = [ 'gstd3d12descriptorpool.cpp', 'gstd3d12device.cpp', 'gstd3d12download.cpp', + 'gstd3d12dxgicapture.cpp', 'gstd3d12fencedatapool.cpp', 'gstd3d12format.cpp', 'gstd3d12h264dec.cpp', @@ -21,6 +22,9 @@ d3d12_sources = [ 'gstd3d12memory.cpp', 'gstd3d12overlaycompositor.cpp', 'gstd3d12pluginutils.cpp', + 'gstd3d12screencapture.cpp', + 'gstd3d12screencapturedevice.cpp', + 'gstd3d12screencapturesrc.cpp', 'gstd3d12testsrc.cpp', 'gstd3d12upload.cpp', 'gstd3d12utils.cpp', diff --git a/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp b/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp index 460b0ec2ac..27a48d9427 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp @@ -35,6 +35,8 @@ #include "gstd3d12videosink.h" #include "gstd3d12testsrc.h" #include "gstd3d12compositor.h" +#include "gstd3d12screencapturedevice.h" +#include "gstd3d12screencapturesrc.h" #include "gstd3d12h264dec.h" #include "gstd3d12h265dec.h" #include "gstd3d12vp9dec.h" @@ -119,6 +121,11 @@ plugin_init (GstPlugin * plugin) "d3d12testsrc", GST_RANK_NONE, GST_TYPE_D3D12_TEST_SRC); gst_element_register (plugin, "d3d12compositor", GST_RANK_NONE, GST_TYPE_D3D12_COMPOSITOR); + gst_element_register (plugin, "d3d12screencapturesrc", GST_RANK_NONE, + GST_TYPE_D3D12_SCREEN_CAPTURE_SRC); + gst_device_provider_register (plugin, + "d3d12screencapturedeviceprovider", GST_RANK_PRIMARY, + GST_TYPE_D3D12_SCREEN_CAPTURE_DEVICE_PROVIDER); return TRUE; }