gstreamer/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11dxgicapture.cpp
Seungha Yang 0afeacce92 d3d11screencapturesrc: Fix wrong color with HDR enabled
Even if IDXGIOutput6 says current display colorspace is HDR,
captured texture via IDXGIOutputDuplication::AcquireNextFrame()
is converted frame by OS unless we use IDXGIOutput5::DuplicateOutput1()
with DXGI_FORMAT_R16G16B16A16_FLOAT format, in order for captured
frame to be scRGB color space. Then application should perform
tonemap operation based on reported display white level, color primaries, etc.

Since we don't have any tonemapping implementation, ignores colorimetry
reported by IDXGIOutput6.

Fixes: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/3128
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5671>
2023-11-16 19:00:24 +09:00

1868 lines
60 KiB
C++

/*
* GStreamer
* Copyright (C) 2020 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 "gstd3d11dxgicapture.h"
#include "gstd3d11pluginutils.h"
#include <string.h>
#include <mutex>
#include <wrl.h>
#define _XM_NO_INTRINSICS_
#include <DirectXMath.h>
GST_DEBUG_CATEGORY_EXTERN (gst_d3d11_screen_capture_debug);
#define GST_CAT_DEFAULT gst_d3d11_screen_capture_debug
/* *INDENT-OFF* */
using namespace Microsoft::WRL;
using namespace DirectX;
/* List of GstD3D11DxgiCapture weakref */
static std::mutex dupl_list_lock;
static GList *dupl_list = nullptr;
/* Below implementation were taken from Microsoft sample
* https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/DXGIDesktopDuplication
*/
#define NUMVERTICES 6
#define BPP 4
struct VERTEX
{
XMFLOAT3 Pos;
XMFLOAT2 TexCoord;
};
/* List of expected error cases */
/* These are the errors we expect from general Dxgi API due to a transition */
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 */
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 */
HRESULT FrameInfoExpectedErrors[] = {
DXGI_ERROR_DEVICE_REMOVED,
DXGI_ERROR_ACCESS_LOST,
S_OK
};
/* These are the errors we expect from IDXGIAdapter::EnumOutputs methods
* due to outputs becoming stale during a transition */
HRESULT EnumOutputsExpectedErrors[] = {
DXGI_ERROR_NOT_FOUND,
S_OK
};
static GstFlowReturn
gst_d3d11_dxgi_capture_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_D3D11_SCREEN_CAPTURE_FLOW_EXPECTED_ERROR;
rst++;
}
}
return GST_FLOW_ERROR;
}
class PTR_INFO
{
public:
PTR_INFO ()
: PtrShapeBuffer (nullptr)
, BufferSize (0)
{
LastTimeStamp.QuadPart = 0;
}
~PTR_INFO ()
{
if (PtrShapeBuffer)
delete[] PtrShapeBuffer;
}
void
MaybeReallocBuffer (UINT buffer_size)
{
if (buffer_size <= BufferSize)
return;
if (PtrShapeBuffer)
delete[] PtrShapeBuffer;
PtrShapeBuffer = new BYTE[buffer_size];
BufferSize = buffer_size;
}
BYTE* PtrShapeBuffer;
UINT BufferSize;
DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info;
POINT Position;
bool Visible;
LARGE_INTEGER LastTimeStamp;
};
class D3D11DesktopDupObject
{
public:
D3D11DesktopDupObject ()
: device_(nullptr)
, metadata_buffer_(nullptr)
, metadata_buffer_size_(0)
, vertex_buffer_(nullptr)
, vertex_buffer_size_(0)
{
}
~D3D11DesktopDupObject ()
{
if (metadata_buffer_)
delete[] metadata_buffer_;
if (vertex_buffer_)
delete[] vertex_buffer_;
if (keyed_mutex_) {
keyed_mutex_->ReleaseSync (0);
keyed_mutex_ = nullptr;
}
gst_clear_object (&device_);
}
GstFlowReturn
Init (GstD3D11Device * device, HMONITOR monitor)
{
GstFlowReturn ret;
ID3D11Device *device_handle;
HRESULT hr;
D3D11_TEXTURE2D_DESC texture_desc = { 0, };
if (!InitShader (device))
return GST_FLOW_ERROR;
ret = InitDupl (device, monitor);
if (ret != GST_FLOW_OK)
return ret;
GST_INFO ("Init done");
device_handle = gst_d3d11_device_get_device_handle (device);
texture_desc.Width = output_desc_.ModeDesc.Width;
texture_desc.Height = output_desc_.ModeDesc.Height;
texture_desc.MipLevels = 1;
texture_desc.ArraySize = 1;
/* FIXME: we can support DXGI_FORMAT_R10G10B10A2_UNORM */
texture_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
texture_desc.SampleDesc.Count = 1;
texture_desc.Usage = D3D11_USAGE_DEFAULT;
texture_desc.BindFlags =
D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
texture_desc.CPUAccessFlags = 0;
/* source element may hold different d3d11 device object */
texture_desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
hr = device_handle->CreateTexture2D (&texture_desc,
nullptr, &shared_texture_);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR_OBJECT (device, "Couldn't create texture, hr 0x%x", (guint) hr);
return GST_FLOW_ERROR;
}
hr = shared_texture_.As (&keyed_mutex_);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR_OBJECT (device, "Couldn't get keyed mutex interface");
shared_texture_ = nullptr;
return GST_FLOW_ERROR;
}
hr = keyed_mutex_->AcquireSync (0, INFINITE);
if (hr != S_OK) {
GST_ERROR_OBJECT (device, "Couldn't acquire sync");
keyed_mutex_ = nullptr;
shared_texture_ = nullptr;
return GST_FLOW_ERROR;
}
device_ = (GstD3D11Device *) gst_object_ref (device);
return GST_FLOW_OK;
}
GstFlowReturn
Capture ()
{
GstFlowReturn ret;
bool timeout = false;
ComPtr<ID3D11Texture2D> texture;
UINT move_count, dirty_count;
DXGI_OUTDUPL_FRAME_INFO frame_info;
GST_TRACE ("Capturing");
ret = GetFrame (&texture, &move_count, &dirty_count, &frame_info, &timeout);
if (ret != GST_FLOW_OK)
return ret;
/* Nothing updated */
if (timeout) {
GST_TRACE ("timeout");
return GST_FLOW_OK;
}
GST_TRACE ("Getting mouse pointer info");
ret = GetMouse (&ptr_info_, &frame_info);
if (ret != GST_FLOW_OK) {
GST_WARNING ("Couldn't get mouse pointer info");
dupl_->ReleaseFrame ();
return ret;
}
ret = ProcessFrame (texture.Get(), shared_texture_.Get(),
&output_desc_, move_count, dirty_count, &frame_info);
if (ret != GST_FLOW_OK) {
dupl_->ReleaseFrame ();
GST_WARNING ("Couldn't process frame");
return ret;
}
HRESULT hr = dupl_->ReleaseFrame ();
if (!gst_d3d11_result (hr, device_)) {
GST_WARNING ("Couldn't release frame");
return gst_d3d11_dxgi_capture_return_from_hr (nullptr, hr, FrameInfoExpectedErrors);
}
GST_TRACE ("Capture done");
return GST_FLOW_OK;
}
bool
DrawMouse (GstD3D11Device * device, ID3D11RenderTargetView * rtv,
ShaderResource * resource, D3D11_BOX * cropBox)
{
GST_TRACE ("Drawing mouse");
if (!ptr_info_.Visible) {
GST_TRACE ("Mouse is invisiable");
return true;
}
ComPtr<ID3D11Texture2D> MouseTex;
ComPtr<ID3D11ShaderResourceView> ShaderRes;
ComPtr<ID3D11Buffer> VertexBufferMouse;
D3D11_SUBRESOURCE_DATA InitData;
D3D11_TEXTURE2D_DESC Desc;
D3D11_SHADER_RESOURCE_VIEW_DESC SDesc;
ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device);
ID3D11DeviceContext *context_handle =
gst_d3d11_device_get_device_context_handle (device);
VERTEX Vertices[NUMVERTICES] =
{
{XMFLOAT3(-1.0f, -1.0f, 0), XMFLOAT2(0.0f, 1.0f)},
{XMFLOAT3(-1.0f, 1.0f, 0), XMFLOAT2(0.0f, 0.0f)},
{XMFLOAT3(1.0f, -1.0f, 0), XMFLOAT2(1.0f, 1.0f)},
{XMFLOAT3(1.0f, -1.0f, 0), XMFLOAT2(1.0f, 1.0f)},
{XMFLOAT3(-1.0f, 1.0f, 0), XMFLOAT2(0.0f, 0.0f)},
{XMFLOAT3(1.0f, 1.0f, 0), XMFLOAT2(1.0f, 0.0f)},
};
D3D11_TEXTURE2D_DESC FullDesc;
shared_texture_->GetDesc(&FullDesc);
INT DesktopWidth = FullDesc.Width;
INT DesktopHeight = FullDesc.Height;
INT CenterX = (DesktopWidth / 2);
INT CenterY = (DesktopHeight / 2);
INT PtrWidth = 0;
INT PtrHeight = 0;
INT PtrLeft = 0;
INT PtrTop = 0;
BYTE* InitBuffer = nullptr;
D3D11_BOX Box;
Box.front = 0;
Box.back = 1;
Desc.MipLevels = 1;
Desc.ArraySize = 1;
Desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
Desc.SampleDesc.Count = 1;
Desc.SampleDesc.Quality = 0;
Desc.Usage = D3D11_USAGE_DEFAULT;
Desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
Desc.CPUAccessFlags = 0;
Desc.MiscFlags = 0;
// Set shader resource properties
SDesc.Format = Desc.Format;
SDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
SDesc.Texture2D.MostDetailedMip = Desc.MipLevels - 1;
SDesc.Texture2D.MipLevels = Desc.MipLevels;
switch (ptr_info_.shape_info.Type) {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
PtrLeft = ptr_info_.Position.x;
PtrTop = ptr_info_.Position.y;
PtrWidth = static_cast<INT>(ptr_info_.shape_info.Width);
PtrHeight = static_cast<INT>(ptr_info_.shape_info.Height);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
ProcessMonoMask(true, &ptr_info_, &PtrWidth, &PtrHeight, &PtrLeft,
&PtrTop, &InitBuffer, &Box);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
ProcessMonoMask(false, &ptr_info_, &PtrWidth, &PtrHeight, &PtrLeft,
&PtrTop, &InitBuffer, &Box);
break;
default:
break;
}
/* Nothing to draw */
if (PtrWidth == 0 || PtrHeight == 0 ||
(PtrLeft + PtrWidth) < static_cast<INT>(cropBox->left) ||
PtrLeft > static_cast<INT>(cropBox->right) ||
(PtrTop + PtrHeight) < static_cast<INT>(cropBox->top) ||
PtrTop > static_cast<INT>(cropBox->bottom)) {
if (InitBuffer)
delete[] InitBuffer;
return true;
}
PtrLeft -= cropBox->left;
PtrTop -= cropBox->top;
Vertices[0].Pos.x = (PtrLeft - CenterX) / (FLOAT)CenterX;
Vertices[0].Pos.y = -1 * ((PtrTop + PtrHeight) - CenterY) / (FLOAT)CenterY;
Vertices[1].Pos.x = (PtrLeft - CenterX) / (FLOAT)CenterX;
Vertices[1].Pos.y = -1 * (PtrTop - CenterY) / (FLOAT)CenterY;
Vertices[2].Pos.x = ((PtrLeft + PtrWidth) - CenterX) / (FLOAT)CenterX;
Vertices[2].Pos.y = -1 * ((PtrTop + PtrHeight) - CenterY) / (FLOAT)CenterY;
Vertices[3].Pos.x = Vertices[2].Pos.x;
Vertices[3].Pos.y = Vertices[2].Pos.y;
Vertices[4].Pos.x = Vertices[1].Pos.x;
Vertices[4].Pos.y = Vertices[1].Pos.y;
Vertices[5].Pos.x = ((PtrLeft + PtrWidth) - CenterX) / (FLOAT)CenterX;
Vertices[5].Pos.y = -1 * (PtrTop - CenterY) / (FLOAT)CenterY;
Desc.Width = PtrWidth;
Desc.Height = PtrHeight;
InitData.pSysMem =
(ptr_info_.shape_info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR) ?
ptr_info_.PtrShapeBuffer : InitBuffer;
InitData.SysMemPitch =
(ptr_info_.shape_info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR) ?
ptr_info_.shape_info.Pitch : PtrWidth * BPP;
InitData.SysMemSlicePitch = 0;
// Create mouseshape as texture
HRESULT hr = device_handle->CreateTexture2D(&Desc, &InitData, &MouseTex);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR ("Failed to create texture for rendering mouse");
return false;
}
// Create shader resource from texture
hr = device_handle->CreateShaderResourceView(MouseTex.Get(), &SDesc,
&ShaderRes);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR ("Failed to create shader resource view for rendering mouse");
return false;
}
D3D11_BUFFER_DESC BDesc;
memset (&BDesc, 0, sizeof(D3D11_BUFFER_DESC));
BDesc.Usage = D3D11_USAGE_DEFAULT;
BDesc.ByteWidth = sizeof(VERTEX) * NUMVERTICES;
BDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
BDesc.CPUAccessFlags = 0;
memset (&InitData, 0, sizeof(D3D11_SUBRESOURCE_DATA));
InitData.pSysMem = Vertices;
// Create vertex buffer
hr = device_handle->CreateBuffer(&BDesc, &InitData, &VertexBufferMouse);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR ("Failed to create vertex buffer for rendering mouse");
return false;
}
FLOAT BlendFactor[4] = {0.f, 0.f, 0.f, 0.f};
UINT Stride = sizeof(VERTEX);
UINT Offset = 0;
ID3D11ShaderResourceView *srv = ShaderRes.Get();
ID3D11Buffer *vert_buf = VertexBufferMouse.Get();
context_handle->IASetVertexBuffers(0, 1, &vert_buf, &Stride, &Offset);
context_handle->OMSetBlendState(resource->blend, BlendFactor, 0xFFFFFFFF);
context_handle->OMSetRenderTargets(1, &rtv, nullptr);
context_handle->VSSetShader(resource->vs, nullptr, 0);
context_handle->PSSetShader(resource->ps, nullptr, 0);
context_handle->PSSetShaderResources(0, 1, &srv);
context_handle->PSSetSamplers(0, 1, &resource->sampler);
context_handle->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
context_handle->IASetInputLayout(resource->layout);
D3D11_VIEWPORT VP;
VP.Width = static_cast<FLOAT>(FullDesc.Width);
VP.Height = static_cast<FLOAT>(FullDesc.Height);
VP.MinDepth = 0.0f;
VP.MaxDepth = 1.0f;
VP.TopLeftX = 0.0f;
VP.TopLeftY = 0.0f;
context_handle->RSSetViewports(1, &VP);
context_handle->RSSetState (resource->rs);
context_handle->Draw(NUMVERTICES, 0);
/* Unbind srv and rtv from context */
srv = nullptr;
context_handle->PSSetShaderResources (0, 1, &srv);
context_handle->OMSetRenderTargets (0, nullptr, nullptr);
if (InitBuffer)
delete[] InitBuffer;
return true;
}
GstFlowReturn
CopyToTexture (GstD3D11Device * device, ID3D11Texture2D * texture,
D3D11_BOX * cropBox)
{
ID3D11DeviceContext *context_handle = nullptr;
ComPtr <ID3D11Texture2D> tex;
ComPtr < IDXGIKeyedMutex > other_keyed_mutex;
HRESULT hr;
context_handle = gst_d3d11_device_get_device_context_handle (device);
if (device == device_) {
tex = shared_texture_;
} else {
ID3D11Device *device_handle = nullptr;
ComPtr < IDXGIResource > dxgi_resource;
HANDLE shared_handle;
device_handle = gst_d3d11_device_get_device_handle (device);
hr = shared_texture_.As (&dxgi_resource);
if (!gst_d3d11_result (hr, device_))
return GST_FLOW_ERROR;
hr = dxgi_resource->GetSharedHandle (&shared_handle);
if (!gst_d3d11_result (hr, device_))
return GST_FLOW_ERROR;
hr = device_handle->OpenSharedResource (shared_handle,
IID_PPV_ARGS (&tex));
if (!gst_d3d11_result (hr, device))
return GST_FLOW_ERROR;
hr = tex.As (&other_keyed_mutex);
if (!gst_d3d11_result (hr, device))
return GST_FLOW_ERROR;
/* release sync from our device, and acquire for other device */
keyed_mutex_->ReleaseSync (0);
other_keyed_mutex->AcquireSync (0, INFINITE);
}
context_handle->CopySubresourceRegion (texture, 0, 0, 0, 0,
tex.Get(), 0, cropBox);
if (other_keyed_mutex) {
other_keyed_mutex->ReleaseSync (0);
keyed_mutex_->AcquireSync (0, INFINITE);
}
return GST_FLOW_OK;
}
void
GetSize (guint * width, guint * height)
{
*width = output_desc_.ModeDesc.Width;
*height = output_desc_.ModeDesc.Height;
}
private:
/* This method is not expected to be failed unless un-recoverable error case */
bool
InitShader (GstD3D11Device * device)
{
ComPtr<ID3D11VertexShader> vs;
ComPtr<ID3D11PixelShader> ps;
ComPtr<ID3D11InputLayout> layout;
ComPtr<ID3D11RasterizerState> rs;
HRESULT hr;
hr = gst_d3d11_get_vertex_shader_coord (device, &vs, &layout);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR ("Failed to create vertex shader");
return false;
}
hr = gst_d3d11_get_pixel_shader_sample (device, &ps);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR ("Failed to create pixel shader");
return false;
}
ComPtr<ID3D11SamplerState> sampler;
hr = gst_d3d11_device_get_sampler (device, D3D11_FILTER_MIN_MAG_MIP_LINEAR,
&sampler);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR ("Failed to create sampler state, hr 0x%x", (guint) hr);
return false;
}
hr = gst_d3d11_device_get_rasterizer (device, &rs);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR ("Couldn't get rasterizer state");
return false;
}
/* Everything is prepared now */
vs_ = vs;
ps_ = ps;
layout_ = layout;
sampler_ = sampler;
rs_ = rs;
return true;
}
/* Maybe returning expected error code depending on desktop status */
GstFlowReturn
InitDupl (GstD3D11Device * device, HMONITOR monitor)
{
ComPtr<ID3D11Device> d3d11_device;
ComPtr<IDXGIAdapter1> adapter;
ComPtr<IDXGIOutput> output;
ComPtr<IDXGIOutput1> output1;
d3d11_device = gst_d3d11_device_get_device_handle (device);
HRESULT hr = gst_d3d11_screen_capture_find_output_for_monitor (monitor,
&adapter, &output);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR ("Couldn't get adapter and output for monitor");
return GST_FLOW_ERROR;
}
hr = output.As (&output1);
if (!gst_d3d11_result (hr, device)) {
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());
}
/* FIXME: Use DuplicateOutput1 to avoid potentail color conversion */
hr = output1->DuplicateOutput(d3d11_device.Get(), &dupl_);
if (!gst_d3d11_result (hr, device)) {
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_D3D11_SCREEN_CAPTURE_FLOW_UNSUPPORTED;
}
return gst_d3d11_dxgi_capture_return_from_hr (d3d11_device.Get(), hr,
CreateDuplicationExpectedErrors);
}
dupl_->GetDesc (&output_desc_);
return GST_FLOW_OK;
}
GstFlowReturn
GetMouse (PTR_INFO * ptr_info, DXGI_OUTDUPL_FRAME_INFO * frame_info)
{
/* A non-zero mouse update timestamp indicates that there is a mouse
* position update and optionally a shape change */
if (frame_info->LastMouseUpdateTime.QuadPart == 0)
return GST_FLOW_OK;
ptr_info->Position.x = frame_info->PointerPosition.Position.x;
ptr_info->Position.y = frame_info->PointerPosition.Position.y;
ptr_info->LastTimeStamp = frame_info->LastMouseUpdateTime;
ptr_info->Visible = frame_info->PointerPosition.Visible != 0;
/* No new shape */
if (frame_info->PointerShapeBufferSize == 0)
return GST_FLOW_OK;
/* Realloc buffer if needed */
ptr_info->MaybeReallocBuffer (frame_info->PointerShapeBufferSize);
/* Must always get shape of cursor, even if not drawn at the moment.
* Shape of cursor is not repeated by the AcquireNextFrame and can be
* requested to be drawn any time later */
UINT dummy;
HRESULT hr = dupl_->GetFramePointerShape(frame_info->PointerShapeBufferSize,
(void *) ptr_info->PtrShapeBuffer, &dummy, &ptr_info->shape_info);
if (!gst_d3d11_result (hr, device_)) {
ID3D11Device *device_handle =
gst_d3d11_device_get_device_handle (device_);
return gst_d3d11_dxgi_capture_return_from_hr(device_handle, hr,
FrameInfoExpectedErrors);
}
return GST_FLOW_OK;
}
void
MaybeReallocMetadataBuffer (UINT buffer_size)
{
if (buffer_size <= metadata_buffer_size_)
return;
if (metadata_buffer_)
delete[] metadata_buffer_;
metadata_buffer_ = new BYTE[buffer_size];
metadata_buffer_size_ = buffer_size;
}
GstFlowReturn
GetFrame (ID3D11Texture2D ** texture, UINT * move_count, UINT * dirty_count,
DXGI_OUTDUPL_FRAME_INFO * frame_info, bool* timeout)
{
ComPtr<IDXGIResource> resource;
ComPtr<ID3D11Texture2D> acquired_texture;
ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device_);
/* Get new frame */
HRESULT hr = dupl_->AcquireNextFrame(0, frame_info, &resource);
if (hr == DXGI_ERROR_WAIT_TIMEOUT) {
GST_TRACE ("Timeout");
*timeout = true;
return GST_FLOW_OK;
}
*timeout = false;
*move_count = 0;
*dirty_count = 0;
if (!gst_d3d11_result (hr, device_)) {
return gst_d3d11_dxgi_capture_return_from_hr(device_handle, hr,
FrameInfoExpectedErrors);
}
GST_TRACE (
"LastPresentTime: %" G_GINT64_FORMAT
", LastMouseUpdateTime: %" G_GINT64_FORMAT
", AccumulatedFrames: %d"
", RectsCoalesced: %d"
", ProtectedContentMaskedOut: %d"
", PointerPosition: (%ldx%ld, visible %d)"
", TotalMetadataBufferSize: %d"
", PointerShapeBufferSize: %d",
frame_info->LastPresentTime.QuadPart,
frame_info->LastMouseUpdateTime.QuadPart,
frame_info->AccumulatedFrames,
frame_info->RectsCoalesced,
frame_info->ProtectedContentMaskedOut,
frame_info->PointerPosition.Position.x,
frame_info->PointerPosition.Position.y,
frame_info->PointerPosition.Visible,
frame_info->TotalMetadataBufferSize,
frame_info->PointerShapeBufferSize);
hr = resource.As (&acquired_texture);
if (!gst_d3d11_result (hr, device_)) {
GST_ERROR ("Failed to get ID3D11Texture2D interface from IDXGIResource "
"hr 0x%x", (guint) hr);
return GST_FLOW_ERROR;
}
/* Get metadata */
if (frame_info->TotalMetadataBufferSize) {
UINT buf_size = frame_info->TotalMetadataBufferSize;
MaybeReallocMetadataBuffer (buf_size);
/* Get move rectangles */
hr = dupl_->GetFrameMoveRects(buf_size,
(DXGI_OUTDUPL_MOVE_RECT *) metadata_buffer_, &buf_size);
if (!gst_d3d11_result (hr, device_)) {
GST_ERROR ("Couldn't get move rect, hr 0x%x", (guint) hr);
return gst_d3d11_dxgi_capture_return_from_hr(nullptr, hr,
FrameInfoExpectedErrors);
}
*move_count = buf_size / sizeof(DXGI_OUTDUPL_MOVE_RECT);
GST_TRACE ("MoveRects count %d", *move_count);
#ifndef GST_DISABLE_GST_DEBUG
{
DXGI_OUTDUPL_MOVE_RECT *rects =
(DXGI_OUTDUPL_MOVE_RECT *) metadata_buffer_;
for (guint i = 0; i < *move_count; i++) {
GST_TRACE ("MoveRect[%d] SourcePoint: %ldx%ld, "
"DestinationRect (left:top:right:bottom): %ldx%ldx%ldx%ld",
i, rects->SourcePoint.x, rects->SourcePoint.y,
rects->DestinationRect.left, rects->DestinationRect.top,
rects->DestinationRect.right, rects->DestinationRect.bottom);
}
}
#endif
BYTE* dirty_rects = metadata_buffer_ + buf_size;
buf_size = frame_info->TotalMetadataBufferSize - buf_size;
/* Get dirty rectangles */
hr = dupl_->GetFrameDirtyRects(buf_size, (RECT *) dirty_rects, &buf_size);
if (!gst_d3d11_result (hr, device_)) {
GST_ERROR ("Couldn't get dirty rect, hr 0x%x", (guint) hr);
*move_count = 0;
*dirty_count = 0;
return gst_d3d11_dxgi_capture_return_from_hr(nullptr,
hr, FrameInfoExpectedErrors);
}
*dirty_count = buf_size / sizeof(RECT);
GST_TRACE ("DirtyRects count %d", *dirty_count);
#ifndef GST_DISABLE_GST_DEBUG
{
RECT *rects = (RECT *) dirty_rects;
for (guint i = 0; i < *dirty_count; i++) {
GST_TRACE ("DirtyRect[%d] left:top:right:bottom: %ldx%ldx%ldx%ld",
i, rects[i].left, rects[i].top, rects[i].right, rects[i].bottom);
}
}
#endif
}
*texture = acquired_texture.Detach();
return GST_FLOW_OK;
}
void
SetMoveRect (RECT* SrcRect, RECT* DestRect, DXGI_OUTDUPL_DESC* DeskDesc,
DXGI_OUTDUPL_MOVE_RECT* MoveRect, INT TexWidth, INT TexHeight)
{
switch (DeskDesc->Rotation)
{
case DXGI_MODE_ROTATION_UNSPECIFIED:
case DXGI_MODE_ROTATION_IDENTITY:
SrcRect->left = MoveRect->SourcePoint.x;
SrcRect->top = MoveRect->SourcePoint.y;
SrcRect->right = MoveRect->SourcePoint.x +
MoveRect->DestinationRect.right - MoveRect->DestinationRect.left;
SrcRect->bottom = MoveRect->SourcePoint.y +
MoveRect->DestinationRect.bottom - MoveRect->DestinationRect.top;
*DestRect = MoveRect->DestinationRect;
break;
case DXGI_MODE_ROTATION_ROTATE90:
SrcRect->left = TexHeight - (MoveRect->SourcePoint.y +
MoveRect->DestinationRect.bottom - MoveRect->DestinationRect.top);
SrcRect->top = MoveRect->SourcePoint.x;
SrcRect->right = TexHeight - MoveRect->SourcePoint.y;
SrcRect->bottom = MoveRect->SourcePoint.x +
MoveRect->DestinationRect.right - MoveRect->DestinationRect.left;
DestRect->left = TexHeight - MoveRect->DestinationRect.bottom;
DestRect->top = MoveRect->DestinationRect.left;
DestRect->right = TexHeight - MoveRect->DestinationRect.top;
DestRect->bottom = MoveRect->DestinationRect.right;
break;
case DXGI_MODE_ROTATION_ROTATE180:
SrcRect->left = TexWidth - (MoveRect->SourcePoint.x +
MoveRect->DestinationRect.right - MoveRect->DestinationRect.left);
SrcRect->top = TexHeight - (MoveRect->SourcePoint.y +
MoveRect->DestinationRect.bottom - MoveRect->DestinationRect.top);
SrcRect->right = TexWidth - MoveRect->SourcePoint.x;
SrcRect->bottom = TexHeight - MoveRect->SourcePoint.y;
DestRect->left = TexWidth - MoveRect->DestinationRect.right;
DestRect->top = TexHeight - MoveRect->DestinationRect.bottom;
DestRect->right = TexWidth - MoveRect->DestinationRect.left;
DestRect->bottom = TexHeight - MoveRect->DestinationRect.top;
break;
case DXGI_MODE_ROTATION_ROTATE270:
SrcRect->left = MoveRect->SourcePoint.x;
SrcRect->top = TexWidth - (MoveRect->SourcePoint.x +
MoveRect->DestinationRect.right - MoveRect->DestinationRect.left);
SrcRect->right = MoveRect->SourcePoint.y +
MoveRect->DestinationRect.bottom - MoveRect->DestinationRect.top;
SrcRect->bottom = TexWidth - MoveRect->SourcePoint.x;
DestRect->left = MoveRect->DestinationRect.top;
DestRect->top = TexWidth - MoveRect->DestinationRect.right;
DestRect->right = MoveRect->DestinationRect.bottom;
DestRect->bottom = TexWidth - MoveRect->DestinationRect.left;
break;
default:
memset (DestRect, 0, sizeof (RECT));
memset (SrcRect, 0, sizeof (RECT));
break;
}
}
GstFlowReturn
CopyMove (ID3D11Texture2D* SharedSurf, DXGI_OUTDUPL_MOVE_RECT* MoveBuffer,
UINT MoveCount, DXGI_OUTDUPL_DESC* DeskDesc)
{
ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device_);
ID3D11DeviceContext *device_context =
gst_d3d11_device_get_device_context_handle (device_);
D3D11_TEXTURE2D_DESC FullDesc;
SharedSurf->GetDesc(&FullDesc);
GST_TRACE ("Copying MoveRects (count %d)", MoveCount);
/* Make new intermediate surface to copy into for moving */
if (!move_texture_) {
D3D11_TEXTURE2D_DESC MoveDesc;
MoveDesc = FullDesc;
MoveDesc.BindFlags = D3D11_BIND_RENDER_TARGET;
MoveDesc.MiscFlags = 0;
HRESULT hr = device_handle->CreateTexture2D(&MoveDesc,
nullptr, &move_texture_);
if (!gst_d3d11_result (hr, device_)) {
GST_ERROR ("Couldn't create intermediate texture, hr 0x%x", (guint) hr);
return GST_FLOW_ERROR;
}
}
for (UINT i = 0; i < MoveCount; i++) {
RECT SrcRect;
RECT DestRect;
SetMoveRect(&SrcRect, &DestRect, DeskDesc, &MoveBuffer[i],
FullDesc.Width, FullDesc.Height);
/* Copy rect out of shared surface */
D3D11_BOX Box;
Box.left = SrcRect.left;
Box.top = SrcRect.top;
Box.front = 0;
Box.right = SrcRect.right;
Box.bottom = SrcRect.bottom;
Box.back = 1;
device_context->CopySubresourceRegion(move_texture_.Get(),
0, SrcRect.left, SrcRect.top, 0, SharedSurf, 0, &Box);
/* Copy back to shared surface */
device_context->CopySubresourceRegion(SharedSurf,
0, DestRect.left, DestRect.top, 0, move_texture_.Get(), 0, &Box);
}
return GST_FLOW_OK;
}
void
SetDirtyVert (VERTEX* Vertices, RECT* Dirty,
DXGI_OUTDUPL_DESC* DeskDesc, D3D11_TEXTURE2D_DESC* FullDesc,
D3D11_TEXTURE2D_DESC* ThisDesc)
{
INT CenterX = FullDesc->Width / 2;
INT CenterY = FullDesc->Height / 2;
INT Width = FullDesc->Width;
INT Height = FullDesc->Height;
/* Rotation compensated destination rect */
RECT DestDirty = *Dirty;
/* Set appropriate coordinates compensated for rotation */
switch (DeskDesc->Rotation)
{
case DXGI_MODE_ROTATION_ROTATE90:
DestDirty.left = Width - Dirty->bottom;
DestDirty.top = Dirty->left;
DestDirty.right = Width - Dirty->top;
DestDirty.bottom = Dirty->right;
Vertices[0].TexCoord =
XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
Vertices[1].TexCoord =
XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
Vertices[2].TexCoord =
XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
Vertices[5].TexCoord =
XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
break;
case DXGI_MODE_ROTATION_ROTATE180:
DestDirty.left = Width - Dirty->right;
DestDirty.top = Height - Dirty->bottom;
DestDirty.right = Width - Dirty->left;
DestDirty.bottom = Height - Dirty->top;
Vertices[0].TexCoord =
XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
Vertices[1].TexCoord =
XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
Vertices[2].TexCoord =
XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
Vertices[5].TexCoord =
XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
break;
case DXGI_MODE_ROTATION_ROTATE270:
DestDirty.left = Dirty->top;
DestDirty.top = Height - Dirty->right;
DestDirty.right = Dirty->bottom;
DestDirty.bottom = Height - Dirty->left;
Vertices[0].TexCoord =
XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
Vertices[1].TexCoord =
XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
Vertices[2].TexCoord =
XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
Vertices[5].TexCoord =
XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
break;
case DXGI_MODE_ROTATION_UNSPECIFIED:
case DXGI_MODE_ROTATION_IDENTITY:
default:
Vertices[0].TexCoord =
XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
Vertices[1].TexCoord =
XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
Vertices[2].TexCoord =
XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
Vertices[5].TexCoord =
XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
break;
}
/* Set positions */
Vertices[0].Pos =
XMFLOAT3(
(DestDirty.left - CenterX) / static_cast<FLOAT>(CenterX),
-1 * (DestDirty.bottom - CenterY) / static_cast<FLOAT>(CenterY),
0.0f);
Vertices[1].Pos =
XMFLOAT3(
(DestDirty.left - CenterX) / static_cast<FLOAT>(CenterX),
-1 * (DestDirty.top - CenterY) / static_cast<FLOAT>(CenterY),
0.0f);
Vertices[2].Pos =
XMFLOAT3(
(DestDirty.right - CenterX) / static_cast<FLOAT>(CenterX),
-1 * (DestDirty.bottom - CenterY) / static_cast<FLOAT>(CenterY),
0.0f);
Vertices[3].Pos = Vertices[2].Pos;
Vertices[4].Pos = Vertices[1].Pos;
Vertices[5].Pos =
XMFLOAT3(
(DestDirty.right - CenterX) / static_cast<FLOAT>(CenterX),
-1 * (DestDirty.top - CenterY) / static_cast<FLOAT>(CenterY),
0.0f);
Vertices[3].TexCoord = Vertices[2].TexCoord;
Vertices[4].TexCoord = Vertices[1].TexCoord;
}
void
MaybeReallocVertexBuffer (UINT buffer_size)
{
if (buffer_size <= vertex_buffer_size_)
return;
if (vertex_buffer_)
delete[] vertex_buffer_;
vertex_buffer_ = new BYTE[buffer_size];
vertex_buffer_size_ = buffer_size;
}
GstFlowReturn
CopyDirty (ID3D11Texture2D* SrcSurface, ID3D11Texture2D* SharedSurf,
RECT* DirtyBuffer, UINT DirtyCount, DXGI_OUTDUPL_DESC* DeskDesc)
{
D3D11_TEXTURE2D_DESC FullDesc;
D3D11_TEXTURE2D_DESC ThisDesc;
ComPtr<ID3D11ShaderResourceView> ShaderResource;
ComPtr<ID3D11Buffer> VertBuf;
HRESULT hr;
ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device_);
ID3D11DeviceContext *device_context =
gst_d3d11_device_get_device_context_handle (device_);
GST_TRACE ("Copying DirtyRects (count %d)", DirtyCount);
SharedSurf->GetDesc(&FullDesc);
SrcSurface->GetDesc(&ThisDesc);
if (!rtv_) {
hr = device_handle->CreateRenderTargetView(SharedSurf, nullptr, &rtv_);
if (!gst_d3d11_result (hr, device_)) {
GST_ERROR ("Couldn't create render target view, hr 0x%x", (guint) hr);
return GST_FLOW_ERROR;
}
}
D3D11_SHADER_RESOURCE_VIEW_DESC ShaderDesc;
ShaderDesc.Format = ThisDesc.Format;
ShaderDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
ShaderDesc.Texture2D.MostDetailedMip = ThisDesc.MipLevels - 1;
ShaderDesc.Texture2D.MipLevels = ThisDesc.MipLevels;
/* Create new shader resource view */
hr = device_handle->CreateShaderResourceView(SrcSurface,
&ShaderDesc, &ShaderResource);
if (!gst_d3d11_result (hr, device_)) {
return gst_d3d11_dxgi_capture_return_from_hr(device_handle, hr,
SystemTransitionsExpectedErrors);
}
ID3D11SamplerState *samplers = sampler_.Get();
ID3D11ShaderResourceView *srv = ShaderResource.Get();
ID3D11RenderTargetView *rtv = rtv_.Get();
device_context->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF);
device_context->OMSetRenderTargets(1, &rtv, nullptr);
device_context->VSSetShader(vs_.Get(), nullptr, 0);
device_context->PSSetShader(ps_.Get(), nullptr, 0);
device_context->PSSetShaderResources(0, 1, &srv);
device_context->PSSetSamplers(0, 1, &samplers);
device_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
device_context->IASetInputLayout(layout_.Get());
/* Create space for vertices for the dirty rects if the current space isn't
* large enough */
UINT byte_needed = sizeof(VERTEX) * NUMVERTICES * DirtyCount;
MaybeReallocVertexBuffer (byte_needed);
/* Fill them in */
VERTEX* DirtyVertex = (VERTEX *) vertex_buffer_;
for (UINT i = 0; i < DirtyCount; ++i, DirtyVertex += NUMVERTICES) {
SetDirtyVert(DirtyVertex, &DirtyBuffer[i], DeskDesc,
&FullDesc, &ThisDesc);
}
/* Create vertex buffer */
D3D11_BUFFER_DESC BufferDesc;
memset (&BufferDesc, 0, sizeof (D3D11_BUFFER_DESC));
BufferDesc.Usage = D3D11_USAGE_DEFAULT;
BufferDesc.ByteWidth = byte_needed;
BufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
BufferDesc.CPUAccessFlags = 0;
D3D11_SUBRESOURCE_DATA InitData;
memset (&InitData, 0, sizeof (D3D11_SUBRESOURCE_DATA));
InitData.pSysMem = vertex_buffer_;
hr = device_handle->CreateBuffer(&BufferDesc, &InitData, &VertBuf);
if (!gst_d3d11_result (hr, device_)) {
GST_ERROR ("Failed to create vertex buffer");
return GST_FLOW_ERROR;
}
UINT Stride = sizeof(VERTEX);
UINT Offset = 0;
ID3D11Buffer *vert_buf = VertBuf.Get();
device_context->IASetVertexBuffers(0, 1, &vert_buf, &Stride, &Offset);
D3D11_VIEWPORT VP;
VP.Width = static_cast<FLOAT>(FullDesc.Width);
VP.Height = static_cast<FLOAT>(FullDesc.Height);
VP.MinDepth = 0.0f;
VP.MaxDepth = 1.0f;
VP.TopLeftX = 0.0f;
VP.TopLeftY = 0.0f;
device_context->RSSetViewports(1, &VP);
device_context->RSSetState (rs_.Get ());
device_context->Draw(NUMVERTICES * DirtyCount, 0);
/* Unbind srv and rtv from context */
srv = nullptr;
device_context->PSSetShaderResources (0, 1, &srv);
device_context->OMSetRenderTargets (0, nullptr, nullptr);
return GST_FLOW_OK;
}
GstFlowReturn
ProcessFrame(ID3D11Texture2D * acquired_texture, ID3D11Texture2D* SharedSurf,
DXGI_OUTDUPL_DESC* DeskDesc, UINT move_count, UINT dirty_count,
DXGI_OUTDUPL_FRAME_INFO * frame_info)
{
GstFlowReturn ret = GST_FLOW_OK;
GST_TRACE ("Processing frame");
/* Process dirties and moves */
if (frame_info->TotalMetadataBufferSize) {
if (move_count) {
ret = CopyMove(SharedSurf, (DXGI_OUTDUPL_MOVE_RECT *) metadata_buffer_,
move_count, DeskDesc);
if (ret != GST_FLOW_OK)
return ret;
}
if (dirty_count) {
ret = CopyDirty(acquired_texture, SharedSurf,
(RECT *)(metadata_buffer_ +
(move_count * sizeof(DXGI_OUTDUPL_MOVE_RECT))),
dirty_count, DeskDesc);
}
} else {
GST_TRACE ("No metadata");
}
return ret;
}
/* To draw mouse */
bool
ProcessMonoMask (bool IsMono, PTR_INFO* PtrInfo, INT* PtrWidth,
INT* PtrHeight, INT* PtrLeft, INT* PtrTop, BYTE** InitBuffer,
D3D11_BOX* Box)
{
ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device_);
ID3D11DeviceContext *context_handle =
gst_d3d11_device_get_device_context_handle (device_);
D3D11_TEXTURE2D_DESC FullDesc;
shared_texture_->GetDesc(&FullDesc);
INT DesktopWidth = FullDesc.Width;
INT DesktopHeight = FullDesc.Height;
// Pointer position
INT GivenLeft = PtrInfo->Position.x;
INT GivenTop = PtrInfo->Position.y;
// Figure out if any adjustment is needed for out of bound positions
if (GivenLeft < 0) {
*PtrWidth = GivenLeft + static_cast<INT>(PtrInfo->shape_info.Width);
} else if ((GivenLeft + static_cast<INT>(PtrInfo->shape_info.Width)) > DesktopWidth) {
*PtrWidth = DesktopWidth - GivenLeft;
} else {
*PtrWidth = static_cast<INT>(PtrInfo->shape_info.Width);
}
if (IsMono)
PtrInfo->shape_info.Height = PtrInfo->shape_info.Height / 2;
if (GivenTop < 0) {
*PtrHeight = GivenTop + static_cast<INT>(PtrInfo->shape_info.Height);
} else if ((GivenTop + static_cast<INT>(PtrInfo->shape_info.Height)) > DesktopHeight) {
*PtrHeight = DesktopHeight - GivenTop;
} else {
*PtrHeight = static_cast<INT>(PtrInfo->shape_info.Height);
}
if (IsMono)
PtrInfo->shape_info.Height = PtrInfo->shape_info.Height * 2;
*PtrLeft = (GivenLeft < 0) ? 0 : GivenLeft;
*PtrTop = (GivenTop < 0) ? 0 : GivenTop;
D3D11_TEXTURE2D_DESC CopyBufferDesc;
CopyBufferDesc.Width = *PtrWidth;
CopyBufferDesc.Height = *PtrHeight;
CopyBufferDesc.MipLevels = 1;
CopyBufferDesc.ArraySize = 1;
CopyBufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
CopyBufferDesc.SampleDesc.Count = 1;
CopyBufferDesc.SampleDesc.Quality = 0;
CopyBufferDesc.Usage = D3D11_USAGE_STAGING;
CopyBufferDesc.BindFlags = 0;
CopyBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
CopyBufferDesc.MiscFlags = 0;
ComPtr<ID3D11Texture2D> CopyBuffer;
HRESULT hr = device_handle->CreateTexture2D(&CopyBufferDesc,
nullptr, &CopyBuffer);
if (!gst_d3d11_result (hr, device_)) {
GST_ERROR ("Couldn't create texture for mouse pointer");
return false;
}
Box->left = *PtrLeft;
Box->top = *PtrTop;
Box->right = *PtrLeft + *PtrWidth;
Box->bottom = *PtrTop + *PtrHeight;
context_handle->CopySubresourceRegion(CopyBuffer.Get(),
0, 0, 0, 0, shared_texture_.Get(), 0, Box);
ComPtr<IDXGISurface> CopySurface;
hr = CopyBuffer.As (&CopySurface);
if (!gst_d3d11_result (hr, device_)) {
GST_ERROR ("Couldn't get DXGI resource from mouse texture");
return false;
}
DXGI_MAPPED_RECT MappedSurface;
hr = CopySurface->Map(&MappedSurface, DXGI_MAP_READ);
if (!gst_d3d11_result (hr, device_)) {
GST_ERROR ("Couldn't map DXGI surface");
return false;
}
*InitBuffer = new BYTE[*PtrWidth * *PtrHeight * BPP];
UINT* InitBuffer32 = reinterpret_cast<UINT*>(*InitBuffer);
UINT* Desktop32 = reinterpret_cast<UINT*>(MappedSurface.pBits);
UINT DesktopPitchInPixels = MappedSurface.Pitch / sizeof(UINT);
// What to skip (pixel offset)
UINT SkipX = (GivenLeft < 0) ? (-1 * GivenLeft) : (0);
UINT SkipY = (GivenTop < 0) ? (-1 * GivenTop) : (0);
if (IsMono) {
for (INT Row = 0; Row < *PtrHeight; Row++) {
BYTE Mask = 0x80;
Mask = Mask >> (SkipX % 8);
for (INT Col = 0; Col < *PtrWidth; Col++) {
BYTE AndMask = PtrInfo->PtrShapeBuffer[((Col + SkipX) / 8) +
((Row + SkipY) * (PtrInfo->shape_info.Pitch))] & Mask;
BYTE XorMask = PtrInfo->PtrShapeBuffer[((Col + SkipX) / 8) +
((Row + SkipY + (PtrInfo->shape_info.Height / 2)) *
(PtrInfo->shape_info.Pitch))] & Mask;
UINT AndMask32 = (AndMask) ? 0xFFFFFFFF : 0xFF000000;
UINT XorMask32 = (XorMask) ? 0x00FFFFFF : 0x00000000;
InitBuffer32[(Row * *PtrWidth) + Col] =
(Desktop32[(Row * DesktopPitchInPixels) + Col] & AndMask32) ^ XorMask32;
if (Mask == 0x01) {
Mask = 0x80;
} else {
Mask = Mask >> 1;
}
}
}
} else {
UINT* Buffer32 = reinterpret_cast<UINT*>(PtrInfo->PtrShapeBuffer);
for (INT Row = 0; Row < *PtrHeight; Row++) {
for (INT Col = 0; Col < *PtrWidth; ++Col) {
// Set up mask
UINT MaskVal = 0xFF000000 & Buffer32[(Col + SkipX) + ((Row + SkipY) *
(PtrInfo->shape_info.Pitch / sizeof(UINT)))];
if (MaskVal) {
// Mask was 0xFF
InitBuffer32[(Row * *PtrWidth) + Col] =
(Desktop32[(Row * DesktopPitchInPixels) + Col] ^
Buffer32[(Col + SkipX) + ((Row + SkipY) *
(PtrInfo->shape_info.Pitch / sizeof(UINT)))]) | 0xFF000000;
} else {
// Mask was 0x00
InitBuffer32[(Row * *PtrWidth) + Col] = Buffer32[(Col + SkipX) +
((Row + SkipY) * (PtrInfo->shape_info.Pitch / sizeof(UINT)))] | 0xFF000000;
}
}
}
}
// Done with resource
hr = CopySurface->Unmap();
if (!gst_d3d11_result (hr, device_)) {
GST_ERROR ("Failed to unmap DXGI surface");
return false;
}
return true;
}
private:
PTR_INFO ptr_info_;
DXGI_OUTDUPL_DESC output_desc_;
GstD3D11Device * device_;
ComPtr<ID3D11Texture2D> shared_texture_;
ComPtr<IDXGIKeyedMutex> keyed_mutex_;
ComPtr<ID3D11RenderTargetView> rtv_;
ComPtr<ID3D11Texture2D> move_texture_;
ComPtr<ID3D11VertexShader> vs_;
ComPtr<ID3D11PixelShader> ps_;
ComPtr<ID3D11InputLayout> layout_;
ComPtr<ID3D11SamplerState> sampler_;
ComPtr<ID3D11RasterizerState> rs_;
ComPtr<IDXGIOutputDuplication> dupl_;
/* frame metadata */
BYTE *metadata_buffer_;
UINT metadata_buffer_size_;
/* vertex buffers */
BYTE *vertex_buffer_;
UINT vertex_buffer_size_;
};
/* *INDENT-ON* */
enum
{
PROP_0,
PROP_D3D11_DEVICE,
PROP_MONITOR_HANDLE,
};
struct _GstD3D11DxgiCapture
{
GstD3D11ScreenCapture parent;
GstD3D11Device *device;
guint cached_width;
guint cached_height;
D3D11DesktopDupObject *dupl_obj;
IDXGIOutput *output;
HMONITOR monitor_handle;
RECT desktop_coordinates;
gboolean prepared;
gint64 adapter_luid;
CRITICAL_SECTION lock;
};
static void gst_d3d11_dxgi_capture_constructed (GObject * object);
static void gst_d3d11_dxgi_capture_dispose (GObject * object);
static void gst_d3d11_dxgi_capture_finalize (GObject * object);
static void gst_d3d11_dxgi_capture_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static GstFlowReturn
gst_d3d11_dxgi_capture_prepare (GstD3D11ScreenCapture * capture);
static gboolean
gst_d3d11_dxgi_capture_get_size (GstD3D11ScreenCapture * capture,
guint * width, guint * height);
static GstFlowReturn
gst_d3d11_dxgi_capture_do_capture (GstD3D11ScreenCapture * capture,
GstD3D11Device * device, ID3D11Texture2D * texture,
ID3D11RenderTargetView * rtv, ShaderResource * resource,
D3D11_BOX * crop_box, gboolean draw_mouse);
#define gst_d3d11_dxgi_capture_parent_class parent_class
G_DEFINE_TYPE (GstD3D11DxgiCapture, gst_d3d11_dxgi_capture,
GST_TYPE_D3D11_SCREEN_CAPTURE);
static void
gst_d3d11_dxgi_capture_class_init (GstD3D11DxgiCaptureClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstD3D11ScreenCaptureClass *capture_class =
GST_D3D11_SCREEN_CAPTURE_CLASS (klass);
gobject_class->constructed = gst_d3d11_dxgi_capture_constructed;
gobject_class->dispose = gst_d3d11_dxgi_capture_dispose;
gobject_class->finalize = gst_d3d11_dxgi_capture_finalize;
gobject_class->set_property = gst_d3d11_dxgi_capture_set_property;
g_object_class_install_property (gobject_class, PROP_D3D11_DEVICE,
g_param_spec_object ("d3d11device", "D3D11 Device",
"GstD3D11Device object for operating",
GST_TYPE_D3D11_DEVICE, (GParamFlags)
(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (gobject_class, PROP_MONITOR_HANDLE,
g_param_spec_pointer ("monitor-handle", "Monitor Handle",
"A HMONITOR handle of monitor to capture", (GParamFlags)
(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)));
capture_class->prepare = GST_DEBUG_FUNCPTR (gst_d3d11_dxgi_capture_prepare);
capture_class->get_size = GST_DEBUG_FUNCPTR (gst_d3d11_dxgi_capture_get_size);
capture_class->do_capture =
GST_DEBUG_FUNCPTR (gst_d3d11_dxgi_capture_do_capture);
}
static void
gst_d3d11_dxgi_capture_init (GstD3D11DxgiCapture * self)
{
InitializeCriticalSection (&self->lock);
memset (&self->desktop_coordinates, 0, sizeof (RECT));
}
static void
gst_d3d11_dxgi_capture_constructed (GObject * object)
{
GstD3D11DxgiCapture *self = GST_D3D11_DXGI_CAPTURE (object);
/* *INDENT-OFF* */
ComPtr<IDXGIDevice> dxgi_device;
ComPtr<IDXGIAdapter1> adapter;
ComPtr<IDXGIOutput> output;
ComPtr<IDXGIOutput1> output1;
/* *INDENT-ON* */
HRESULT hr;
gboolean ret = FALSE;
DXGI_OUTPUT_DESC output_desc;
DXGI_ADAPTER_DESC adapter_desc;
gint64 luid, device_luid;
if (!self->device) {
GST_WARNING_OBJECT (self, "D3D11 device is unavailable");
goto out;
}
if (!self->monitor_handle) {
GST_WARNING_OBJECT (self, "Null monitor handle");
goto out;
}
hr = gst_d3d11_screen_capture_find_output_for_monitor (self->monitor_handle,
&adapter, &output);
if (!gst_d3d11_result (hr, self->device)) {
GST_WARNING_OBJECT (self,
"Failed to find associated adapter for monitor %p",
self->monitor_handle);
goto out;
}
hr = output.As (&output1);
if (!gst_d3d11_result (hr, self->device)) {
GST_WARNING_OBJECT (self, "IDXGIOutput1 interface is unavailble");
goto out;
}
hr = adapter->GetDesc (&adapter_desc);
if (!gst_d3d11_result (hr, self->device)) {
GST_WARNING_OBJECT (self, "Failed to get adapter desc");
goto out;
}
luid = gst_d3d11_luid_to_int64 (&adapter_desc.AdapterLuid);
g_object_get (self->device, "adapter-luid", &device_luid, nullptr);
if (luid != device_luid) {
GST_WARNING_OBJECT (self, "Incompatible d3d11 device");
goto out;
}
hr = output->GetDesc (&output_desc);
if (!gst_d3d11_result (hr, self->device)) {
GST_WARNING_OBJECT (self, "Failed to get output desc");
goto out;
}
/* 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");
goto out;
}
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");
goto out;
}
self->desktop_coordinates.left = dev_mode.dmPosition.x;
self->desktop_coordinates.top = dev_mode.dmPosition.y;
self->desktop_coordinates.right =
dev_mode.dmPosition.x + dev_mode.dmPelsWidth;
self->desktop_coordinates.bottom =
dev_mode.dmPosition.y + dev_mode.dmPelsHeight;
self->cached_width =
self->desktop_coordinates.right - self->desktop_coordinates.left;
self->cached_height =
self->desktop_coordinates.bottom - self->desktop_coordinates.top;
GST_DEBUG_OBJECT (self,
"Desktop coordinates left:top:right:bottom = %ld:%ld:%ld:%ld (%dx%d)",
self->desktop_coordinates.left, self->desktop_coordinates.top,
self->desktop_coordinates.right, self->desktop_coordinates.bottom,
self->cached_width, self->cached_height);
g_object_get (self->device, "adapter-luid", &self->adapter_luid, nullptr);
self->output = output.Detach ();
ret = TRUE;
out:
if (!ret)
gst_clear_object (&self->device);
G_OBJECT_CLASS (parent_class)->constructed (object);
}
static void
gst_d3d11_dxgi_capture_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstD3D11DxgiCapture *self = GST_D3D11_DXGI_CAPTURE (object);
switch (prop_id) {
case PROP_D3D11_DEVICE:
self->device = (GstD3D11Device *) g_value_dup_object (value);
break;
case PROP_MONITOR_HANDLE:
self->monitor_handle = (HMONITOR) g_value_get_pointer (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_d3d11_dxgi_capture_dispose (GObject * object)
{
GstD3D11DxgiCapture *self = GST_D3D11_DXGI_CAPTURE (object);
GST_D3D11_CLEAR_COM (self->output);
if (self->dupl_obj) {
delete self->dupl_obj;
self->dupl_obj = nullptr;
}
gst_clear_object (&self->device);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_d3d11_dxgi_capture_finalize (GObject * object)
{
GstD3D11DxgiCapture *self = GST_D3D11_DXGI_CAPTURE (object);
DeleteCriticalSection (&self->lock);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_d3d11_dxgi_capture_weak_ref_notify (gpointer data,
GstD3D11DxgiCapture * dupl)
{
std::lock_guard < std::mutex > lk (dupl_list_lock);
dupl_list = g_list_remove (dupl_list, dupl);
}
GstD3D11ScreenCapture *
gst_d3d11_dxgi_capture_new (GstD3D11Device * device, HMONITOR monitor_handle)
{
GstD3D11DxgiCapture *self = nullptr;
GList *iter;
g_return_val_if_fail (GST_IS_D3D11_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 (dupl_list_lock);
for (iter = dupl_list; iter; iter = g_list_next (iter)) {
GstD3D11DxgiCapture *dupl = (GstD3D11DxgiCapture *) iter->data;
if (dupl->monitor_handle == monitor_handle) {
GST_DEBUG ("Found configured desktop dup object for monitor handle %p",
monitor_handle);
gst_object_ref (dupl);
return GST_D3D11_SCREEN_CAPTURE_CAST (dupl);
}
}
self = (GstD3D11DxgiCapture *) g_object_new (GST_TYPE_D3D11_DXGI_CAPTURE,
"d3d11device", device, "monitor-handle", monitor_handle, nullptr);
if (!self->device) {
GST_WARNING_OBJECT (self, "Couldn't configure desktop dup object");
gst_object_unref (self);
return nullptr;
}
g_object_weak_ref (G_OBJECT (self),
(GWeakNotify) gst_d3d11_dxgi_capture_weak_ref_notify, nullptr);
dupl_list = g_list_append (dupl_list, self);
return GST_D3D11_SCREEN_CAPTURE_CAST (self);
}
static GstFlowReturn
gst_d3d11_dxgi_capture_prepare (GstD3D11ScreenCapture * capture)
{
GstD3D11DxgiCapture *self = GST_D3D11_DXGI_CAPTURE (capture);
GstFlowReturn ret;
GstD3D11CSLockGuard lk (&self->lock);
if (self->prepared) {
GST_DEBUG_OBJECT (self, "Already prepared");
return GST_FLOW_OK;
}
self->dupl_obj = new D3D11DesktopDupObject ();
ret = self->dupl_obj->Init (self->device, self->monitor_handle);
if (ret != GST_FLOW_OK) {
GST_WARNING_OBJECT (capture,
"Couldn't prepare capturing, %sexpected failure",
ret == GST_D3D11_SCREEN_CAPTURE_FLOW_EXPECTED_ERROR ? "" : "un");
delete self->dupl_obj;
self->dupl_obj = nullptr;
return ret;
}
self->prepared = TRUE;
return GST_FLOW_OK;
}
static gboolean
gst_d3d11_dxgi_capture_get_size_unlocked (GstD3D11DxgiCapture * self,
guint * width, guint * height)
{
*width = 0;
*height = 0;
if (self->dupl_obj) {
self->dupl_obj->GetSize (&self->cached_width, &self->cached_height);
}
*width = self->cached_width;
*height = self->cached_height;
return TRUE;
}
static gboolean
gst_d3d11_dxgi_capture_get_size (GstD3D11ScreenCapture * capture,
guint * width, guint * height)
{
GstD3D11DxgiCapture *self = GST_D3D11_DXGI_CAPTURE (capture);
GstD3D11CSLockGuard lk (&self->lock);
return gst_d3d11_dxgi_capture_get_size_unlocked (self, width, height);
}
static GstFlowReturn
gst_d3d11_dxgi_capture_do_capture (GstD3D11ScreenCapture * capture,
GstD3D11Device * device, ID3D11Texture2D * texture,
ID3D11RenderTargetView * rtv, ShaderResource * resource,
D3D11_BOX * crop_box, gboolean draw_mouse)
{
GstD3D11DxgiCapture *self = GST_D3D11_DXGI_CAPTURE (capture);
GstFlowReturn ret = GST_FLOW_OK;
gboolean shared_device = FALSE;
guint width, height;
if (device != self->device) {
gint64 luid;
g_object_get (device, "adapter-luid", &luid, nullptr);
/* source element must hold d3d11 device for the same GPU already
* by DXGI duplication API design */
if (luid != self->adapter_luid) {
GST_ERROR_OBJECT (self, "Trying to capture from different device");
return GST_FLOW_ERROR;
}
shared_device = TRUE;
}
GstD3D11CSLockGuard lk (&self->lock);
if (!self->prepared)
ret = gst_d3d11_dxgi_capture_prepare (capture);
if (ret != GST_FLOW_OK) {
GST_WARNING_OBJECT (self, "We are not prepared");
return ret;
}
gst_d3d11_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_D3D11_SCREEN_CAPTURE_FLOW_SIZE_CHANGED;
}
gst_d3d11_device_lock (self->device);
ret = self->dupl_obj->Capture ();
if (ret != GST_FLOW_OK) {
gst_d3d11_device_unlock (self->device);
delete self->dupl_obj;
self->dupl_obj = nullptr;
self->prepared = FALSE;
if (ret == GST_D3D11_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");
if (shared_device)
gst_d3d11_device_lock (device);
ret = self->dupl_obj->CopyToTexture (device, texture, crop_box);
if (ret != GST_FLOW_OK)
goto out;
if (draw_mouse)
self->dupl_obj->DrawMouse (device, rtv, resource, crop_box);
out:
if (shared_device)
gst_d3d11_device_unlock (device);
gst_d3d11_device_unlock (self->device);
return ret;
}