gstreamer/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dxgicapture.cpp
Seungha Yang ae3ed20f41 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: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5883>
2024-01-05 21:27:35 +09:00

1753 lines
58 KiB
C++

/* 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;
}
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<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;
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;
}