| /* GStreamer | |
| * Copyright (C) 2024 Seungha Yang <seungha@centricular.com> | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Library General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Library General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Library General Public | |
| * License along with this library; if not, write to the | |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, | |
| * Boston, MA 02110-1301, USA. | |
| */ | |
| /* | |
| * 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 <d3d11_3.h> | |
| #include <directx/d3dx12.h> | |
| #include <string.h> | |
| #include <mutex> | |
| #include <vector> | |
| #include <memory> | |
| #include <future> | |
| #include <wrl.h> | |
| #include "PSMain_sample.h" | |
| #include "VSMain_coord.h" | |
| #define _XM_NO_INTRINSICS_ | |
| #include <DirectXMath.h> | |
| 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<HRESULT>(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<HRESULT>(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<HRESULT>(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<BYTE> 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<BYTE> texture_; | |
| std::vector<BYTE> 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<IDXGIAdapter1> adapter; | |
| ComPtr<IDXGIOutput> output; | |
| ComPtr<IDXGIOutput1> 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; | |
| } | |
| void ReleaseFrame () | |
| { | |
| if (acquired_frame_) { | |
| acquired_frame_ = nullptr; | |
| dupl_->ReleaseFrame (); | |
| } | |
| } | |
| 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<MoveRectData> & GetMoveRects () | |
| { | |
| return move_rect_; | |
| } | |
| UINT GetDirtyCount () | |
| { | |
| return dirty_rect_.size (); | |
| } | |
| const std::vector<RECT> & GetDirtyRects () | |
| { | |
| return dirty_rect_; | |
| } | |
| const std::vector<VERTEX> & 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<IDXGIOutputDuplication> dupl_; | |
| ComPtr<ID3D11Device> device_; | |
| ComPtr<IDXGIResource> acquired_frame_; | |
| std::vector<MoveRectData> move_rect_; | |
| std::vector<VERTEX> dirty_vertex_; | |
| std::vector<RECT> dirty_rect_; | |
| /* frame metadata */ | |
| std::vector<BYTE> 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<DesktopDupCtx> ctx; | |
| ComPtr<IDXGIOutput1> output; | |
| ComPtr<IDXGIResource> last_resource; | |
| ComPtr<ID3D12Resource> shared_resource; | |
| ComPtr<ID3D12Resource> move_frame; | |
| ComPtr<ID3D12Resource> processed_frame; | |
| ComPtr<ID3D12RootSignature> rs; | |
| ComPtr<ID3D12PipelineState> pso; | |
| GstD3D12CommandAllocatorPool *ca_pool = nullptr; | |
| GstD3D12FenceDataPool *fence_data_pool; | |
| GstD3D12FenceData *mouse_fence_data = nullptr; | |
| ComPtr<ID3D12GraphicsCommandList> cl; | |
| ComPtr<ID3D12GraphicsCommandList> mouse_cl; | |
| ComPtr<ID3D12DescriptorHeap> srv_heap; | |
| ComPtr<ID3D12DescriptorHeap> rtv_heap; | |
| ComPtr<ID3D12Resource> 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; | |
| barriers.clear (); | |
| barriers. | |
| push_back (CD3DX12_RESOURCE_BARRIER::Transition (priv->processed_frame. | |
| Get (), D3D12_RESOURCE_STATE_COPY_SOURCE, | |
| D3D12_RESOURCE_STATE_COPY_DEST)); | |
| 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"); | |
| if (!priv->shared_resource) { | |
| 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; | |
| } | |
| 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 ()); | |
| } | |
| 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"); | |
| 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_RENDER_TARGET); | |
| priv->cl->ResourceBarrier (1, &barrier); | |
| } | |
| 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); | |
| 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_mini_object (fence_data, gst_ca); | |
| 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; | |
| } | |
| 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; | |
| } | |
| if (resource) { | |
| if (resource != priv->last_resource) | |
| priv->shared_resource = nullptr; | |
| priv->last_resource = resource; | |
| } | |
| GST_LOG_OBJECT (self, "Capture done"); | |
| bool have_move_rect = false; | |
| if (priv->ctx->GetMoveCount () > 0) | |
| have_move_rect = true; | |
| bool have_dirty_rect = false; | |
| if ((priv->ctx->GetDirtyCount () > 0) && resource) | |
| have_dirty_rect = true; | |
| 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_mini_object (fence_data, gst_ca); | |
| 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 (have_move_rect && | |
| !gst_d3d12_dxgi_capture_copy_move_rects (self, priv->cl.Get ())) { | |
| GST_ERROR_OBJECT (self, "Couldn't copy move rects"); | |
| goto error; | |
| } | |
| if (have_dirty_rect && | |
| !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); | |
| 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); | |
| priv->mouse_fence_data = nullptr; | |
| gst_d3d12_buffer_after_write (buffer, priv->mouse_fence_val); | |
| } | |
| } | |
| if (have_dirty_rect) { | |
| gst_d3d12_device_fence_wait (self->device, D3D12_COMMAND_LIST_TYPE_DIRECT, | |
| priv->fence_val, priv->event_handle); | |
| } | |
| priv->ctx->ReleaseFrame (); | |
| 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->last_resource = nullptr; | |
| priv->shared_resource = nullptr; | |
| priv->processed_frame = nullptr; | |
| priv->move_frame = nullptr; | |
| priv->ctx = nullptr; | |
| return GST_FLOW_ERROR; | |
| } |