From ae3ed20f41e0157720b8e46a97668d98636a1c68 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Tue, 2 Jan 2024 01:05:23 +0900 Subject: [PATCH] d3d12: Add screen capture element Since DXGI desktop duplication API does not work with Direct3D12 device, this element will use Direct3D11 device to acquire frame. Then other rendering operations (e.g., texture copy, render pipeline) will happen using Direct3D12 API Part-of: --- .../docs/plugins/gst_plugins_cache.json | 119 ++ .../sys/d3d12/gstd3d12dxgicapture.cpp | 1752 +++++++++++++++++ .../sys/d3d12/gstd3d12dxgicapture.h | 36 + .../sys/d3d12/gstd3d12screencapture.cpp | 276 +++ .../sys/d3d12/gstd3d12screencapture.h | 107 + .../sys/d3d12/gstd3d12screencapturedevice.cpp | 455 +++++ .../sys/d3d12/gstd3d12screencapturedevice.h | 37 + .../sys/d3d12/gstd3d12screencapturesrc.cpp | 1006 ++++++++++ .../sys/d3d12/gstd3d12screencapturesrc.h | 34 + .../gst-plugins-bad/sys/d3d12/meson.build | 4 + .../gst-plugins-bad/sys/d3d12/plugin.cpp | 7 + 11 files changed, 3833 insertions(+) create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.cpp create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.h create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.cpp create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapture.h create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturedevice.cpp create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturedevice.h create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.cpp create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12screencapturesrc.h 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; }