mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 05:31:15 +00:00
abeccdd6bc
Returns COM pointer directly everywhere Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6494>
1775 lines
58 KiB
C++
1775 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 <gst/d3dshader/gstd3dshader.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
|
|
*/
|
|
|
|
/* 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_finalize (GObject * object);
|
|
static GstFlowReturn
|
|
gst_d3d12_dxgi_capture_prepare (GstD3D12ScreenCapture * capture);
|
|
static gboolean
|
|
gst_d3d12_dxgi_capture_get_size (GstD3D12ScreenCapture * capture,
|
|
guint * width, guint * height);
|
|
|
|
#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);
|
|
}
|
|
|
|
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;
|
|
|
|
GstD3DShaderByteCode vs_code;
|
|
GstD3DShaderByteCode ps_code;
|
|
if (!gst_d3d_plugin_shader_get_vs_blob (GST_D3D_PLUGIN_VS_COORD,
|
|
GST_D3D_SM_5_0, &vs_code)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't get vs bytecode");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!gst_d3d_plugin_shader_get_ps_blob (GST_D3D_PLUGIN_PS_SAMPLE,
|
|
GST_D3D_SM_5_0, &ps_code)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't get ps bytecode");
|
|
return FALSE;
|
|
}
|
|
|
|
D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = { };
|
|
pso_desc.pRootSignature = priv->rs.Get ();
|
|
pso_desc.VS.BytecodeLength = vs_code.byte_code_len;
|
|
pso_desc.VS.pShaderBytecode = vs_code.byte_code;
|
|
pso_desc.PS.BytecodeLength = ps_code.byte_code_len;
|
|
pso_desc.PS.pShaderBytecode = ps_code.byte_code;
|
|
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 (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,
|
|
GetCPUDescriptorHandleForHeapStart (priv->rtv_heap));
|
|
|
|
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 = GetDesc (priv->processed_frame);
|
|
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,
|
|
GetCPUDescriptorHandleForHeapStart (priv->srv_heap));
|
|
}
|
|
|
|
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[] = {
|
|
GetCPUDescriptorHandleForHeapStart (priv->rtv_heap)
|
|
};
|
|
|
|
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,
|
|
GetGPUDescriptorHandleForHeapStart (priv->srv_heap));
|
|
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 (static_cast < INT > (info.position_info.Position.x + info.width_) <
|
|
static_cast < INT > (crop_box->left) ||
|
|
static_cast < INT > (info.position_info.Position.x) >
|
|
static_cast < INT > (crop_box->right) ||
|
|
static_cast < INT > (info.position_info.Position.y + info.height_) <
|
|
static_cast < INT > (crop_box->top) ||
|
|
static_cast < INT > (info.position_info.Position.y) >
|
|
static_cast < INT > (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, nullptr, nullptr);
|
|
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, nullptr, nullptr);
|
|
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;
|
|
|
|
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);
|
|
|
|
auto ca = gst_d3d12_command_allocator_get_handle (gst_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, nullptr, IID_PPV_ARGS (&priv->mouse_cl));
|
|
} else {
|
|
hr = priv->mouse_cl->Reset (ca, 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;
|
|
}
|
|
|
|
GstFlowReturn
|
|
gst_d3d12_dxgi_capture_do_capture (GstD3D12DxgiCapture * capture,
|
|
GstBuffer * buffer, const D3D12_BOX * crop_box, gboolean draw_mouse)
|
|
{
|
|
auto self = GST_D3D12_DXGI_CAPTURE (capture);
|
|
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;
|
|
ID3D12CommandAllocator *ca = nullptr;
|
|
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);
|
|
|
|
ca = gst_d3d12_command_allocator_get_handle (gst_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, priv->pso.Get (), IID_PPV_ARGS (&priv->cl));
|
|
} else {
|
|
hr = priv->cl->Reset (ca, 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;
|
|
}
|