mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-18 15:51:11 +00:00
0f7af4b143
Move d3d11 device, memory, buffer pool and minimal method to gst-libs so that other plugins can access d3d11 resource. Since Direct3D is primary graphics API on Windows, we need this infrastructure for various plugins can share GPU resource without downloading GPU memory. Note that this implementation is public only for -bad scope for now. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/464>
1852 lines
59 KiB
C++
1852 lines
59 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 "gstd3d11desktopdup.h"
|
|
#include "gstd3d11shader.h"
|
|
#include <string.h>
|
|
|
|
#include <wrl.h>
|
|
|
|
using namespace Microsoft::WRL;
|
|
|
|
G_BEGIN_DECLS
|
|
|
|
GST_DEBUG_CATEGORY_EXTERN (gst_d3d11_desktop_dup_debug);
|
|
#define GST_CAT_DEFAULT gst_d3d11_desktop_dup_debug
|
|
|
|
G_END_DECLS
|
|
|
|
/* List of GstD3D11DesktopDup weakref */
|
|
G_LOCK_DEFINE_STATIC (dupl_list_lock);
|
|
static GList *dupl_list = nullptr;
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_D3D11_DEVICE,
|
|
PROP_OUTPUT_INDEX,
|
|
};
|
|
|
|
#define DEFAULT_MONITOR_INDEX -1
|
|
|
|
/* Below implemenation were taken from Microsoft sample
|
|
* https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/DXGIDesktopDuplication
|
|
*/
|
|
#define NUMVERTICES 6
|
|
#define BPP 4
|
|
|
|
/* Define our own MyFLOAT3 and MyFLOAT2 struct, since MinGW doesn't support
|
|
* DirectXMath.h
|
|
*/
|
|
struct MyFLOAT3
|
|
{
|
|
float x;
|
|
float y;
|
|
float z;
|
|
|
|
MyFLOAT3() = default;
|
|
|
|
MyFLOAT3(const MyFLOAT3&) = default;
|
|
MyFLOAT3& operator=(const MyFLOAT3&) = default;
|
|
|
|
MyFLOAT3(MyFLOAT3&&) = default;
|
|
MyFLOAT3& operator=(MyFLOAT3&&) = default;
|
|
|
|
constexpr MyFLOAT3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
|
|
explicit MyFLOAT3(const float *pArray) : x(pArray[0]), y(pArray[1]), z(pArray[2]) {}
|
|
};
|
|
|
|
struct MyFLOAT2
|
|
{
|
|
float x;
|
|
float y;
|
|
|
|
MyFLOAT2() = default;
|
|
|
|
MyFLOAT2(const MyFLOAT2&) = default;
|
|
MyFLOAT2& operator=(const MyFLOAT2&) = default;
|
|
|
|
MyFLOAT2(MyFLOAT2&&) = default;
|
|
MyFLOAT2& operator=(MyFLOAT2&&) = default;
|
|
|
|
constexpr MyFLOAT2(float _x, float _y) : x(_x), y(_y) {}
|
|
explicit MyFLOAT2(const float *pArray) : x(pArray[0]), y(pArray[1]) {}
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
MyFLOAT3 Pos;
|
|
MyFLOAT2 TexCoord;
|
|
} VERTEX;
|
|
|
|
/* 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_UNSUPPORTED,
|
|
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_desktop_dup_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_DESKTOP_DUP_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_;
|
|
|
|
gst_clear_object (&device_);
|
|
}
|
|
|
|
GstFlowReturn
|
|
Init (GstD3D11Device * device, ID3D11Texture2D * texture, UINT monitor_index)
|
|
{
|
|
GstFlowReturn ret;
|
|
|
|
if (!InitShader (device))
|
|
return GST_FLOW_ERROR;
|
|
|
|
ret = InitDupl (device, monitor_index);
|
|
if (ret != GST_FLOW_OK)
|
|
return ret;
|
|
|
|
GST_INFO ("Init done");
|
|
|
|
shared_texture_ = texture;
|
|
device_ = (GstD3D11Device *) gst_object_ref (device);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
GstFlowReturn
|
|
Capture (gboolean draw_mouse)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (draw_mouse) {
|
|
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_desktop_dup_return_from_hr (nullptr, hr, FrameInfoExpectedErrors);
|
|
}
|
|
|
|
GST_TRACE ("Capture done");
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
bool DrawMouse (ID3D11RenderTargetView * rtv)
|
|
{
|
|
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] =
|
|
{
|
|
{MyFLOAT3(-1.0f, -1.0f, 0), MyFLOAT2(0.0f, 1.0f)},
|
|
{MyFLOAT3(-1.0f, 1.0f, 0), MyFLOAT2(0.0f, 0.0f)},
|
|
{MyFLOAT3(1.0f, -1.0f, 0), MyFLOAT2(1.0f, 1.0f)},
|
|
{MyFLOAT3(1.0f, -1.0f, 0), MyFLOAT2(1.0f, 1.0f)},
|
|
{MyFLOAT3(-1.0f, 1.0f, 0), MyFLOAT2(0.0f, 0.0f)},
|
|
{MyFLOAT3(1.0f, 1.0f, 0), MyFLOAT2(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 draw */
|
|
if (PtrWidth == 0 || PtrHeight == 0) {
|
|
if (InitBuffer)
|
|
delete[] InitBuffer;
|
|
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
ID3D11SamplerState *samplers = sampler_.Get();
|
|
ID3D11ShaderResourceView *srv = ShaderRes.Get();
|
|
ID3D11Buffer *vert_buf = VertexBufferMouse.Get();
|
|
|
|
context_handle->IASetVertexBuffers(0, 1, &vert_buf, &Stride, &Offset);
|
|
context_handle->OMSetBlendState(blend_.Get(), BlendFactor, 0xFFFFFFFF);
|
|
context_handle->OMSetRenderTargets(1, &rtv, nullptr);
|
|
context_handle->VSSetShader(vs_.Get(), nullptr, 0);
|
|
context_handle->PSSetShader(ps_.Get(), nullptr, 0);
|
|
context_handle->PSSetShaderResources(0, 1, &srv);
|
|
context_handle->PSSetSamplers(0, 1, &samplers);
|
|
context_handle->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
|
context_handle->IASetInputLayout(layout_.Get());
|
|
|
|
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;
|
|
}
|
|
|
|
private:
|
|
/* This method is not expected to be failed unless un-recoverable error case */
|
|
bool
|
|
InitShader (GstD3D11Device * device)
|
|
{
|
|
static const gchar vs_str[] =
|
|
"struct VS_INPUT {\n"
|
|
" float4 Position: POSITION;\n"
|
|
" float2 Texture: TEXCOORD;\n"
|
|
"};\n"
|
|
"\n"
|
|
"struct VS_OUTPUT {\n"
|
|
" float4 Position: SV_POSITION;\n"
|
|
" float2 Texture: TEXCOORD;\n"
|
|
"};\n"
|
|
"\n"
|
|
"VS_OUTPUT main (VS_INPUT input)\n"
|
|
"{\n"
|
|
" return input;\n"
|
|
"}";
|
|
|
|
static const gchar ps_str[] =
|
|
"Texture2D shaderTexture;\n"
|
|
"SamplerState samplerState;\n"
|
|
"\n"
|
|
"struct PS_INPUT {\n"
|
|
" float4 Position: SV_POSITION;\n"
|
|
" float2 Texture: TEXCOORD;\n"
|
|
"};\n"
|
|
"\n"
|
|
"struct PS_OUTPUT {\n"
|
|
" float4 Plane: SV_Target;\n"
|
|
"};\n"
|
|
"\n"
|
|
"PS_OUTPUT main(PS_INPUT input)\n"
|
|
"{\n"
|
|
" PS_OUTPUT output;\n"
|
|
" output.Plane = shaderTexture.Sample(samplerState, input.Texture);\n"
|
|
" return output;\n"
|
|
"}";
|
|
|
|
D3D11_INPUT_ELEMENT_DESC input_desc[] = {
|
|
{"POSITION",
|
|
0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
|
|
{"TEXCOORD",
|
|
0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
|
|
};
|
|
|
|
ComPtr<ID3D11VertexShader> vs;
|
|
ComPtr<ID3D11InputLayout> layout;
|
|
if (!gst_d3d11_create_vertex_shader (device,
|
|
vs_str, input_desc, G_N_ELEMENTS (input_desc), &vs, &layout)) {
|
|
GST_ERROR ("Failed to create vertex shader");
|
|
return false;
|
|
}
|
|
|
|
ComPtr<ID3D11PixelShader> ps;
|
|
if (!gst_d3d11_create_pixel_shader (device, ps_str, &ps)) {
|
|
GST_ERROR ("Failed to create pixel shader");
|
|
return false;
|
|
}
|
|
|
|
D3D11_SAMPLER_DESC sampler_desc;
|
|
memset (&sampler_desc, 0, sizeof (D3D11_SAMPLER_DESC));
|
|
sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
|
sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
|
|
sampler_desc.MinLOD = 0;
|
|
sampler_desc.MaxLOD = D3D11_FLOAT32_MAX;
|
|
|
|
ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device);
|
|
ComPtr<ID3D11SamplerState> sampler;
|
|
HRESULT hr = device_handle->CreateSamplerState (&sampler_desc, &sampler);
|
|
if (!gst_d3d11_result (hr, device)) {
|
|
GST_ERROR ("Failed to create sampler state, hr 0x%x", (guint) hr);
|
|
return false;
|
|
}
|
|
|
|
/* For blending mouse pointer texture */
|
|
D3D11_BLEND_DESC blend_desc;
|
|
blend_desc.AlphaToCoverageEnable = FALSE;
|
|
blend_desc.IndependentBlendEnable = FALSE;
|
|
blend_desc.RenderTarget[0].BlendEnable = TRUE;
|
|
blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
|
|
blend_desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
|
blend_desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
|
blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
|
blend_desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
|
|
blend_desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
|
blend_desc.RenderTarget[0].RenderTargetWriteMask =
|
|
D3D11_COLOR_WRITE_ENABLE_ALL;
|
|
|
|
ComPtr<ID3D11BlendState> blend;
|
|
hr = device_handle->CreateBlendState (&blend_desc, &blend);
|
|
if (!gst_d3d11_result (hr, device)) {
|
|
GST_ERROR ("Failed to create blend state, hr 0x%x", (guint) hr);
|
|
return false;
|
|
}
|
|
|
|
/* Everything is prepared now */
|
|
vs_ = vs;
|
|
ps_ = ps;
|
|
layout_ = layout;
|
|
sampler_ = sampler;
|
|
blend_ = blend;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Maybe returning expected error code depending on desktop status */
|
|
GstFlowReturn
|
|
InitDupl (GstD3D11Device * device, UINT monitor_index)
|
|
{
|
|
ComPtr<ID3D11Device> d3d11_device;
|
|
ComPtr<IDXGIDevice> dxgi_device;
|
|
ComPtr<IDXGIAdapter> adapter;
|
|
ComPtr<IDXGIOutput> output;
|
|
ComPtr<IDXGIOutput1> output1;
|
|
|
|
d3d11_device = gst_d3d11_device_get_device_handle (device);
|
|
|
|
HRESULT hr = d3d11_device.As (&dxgi_device);
|
|
if (!gst_d3d11_result (hr, device)) {
|
|
GST_ERROR ("Couldn't get IDXGIDevice interface, hr 0x%x", (guint) hr);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
hr = dxgi_device->GetParent (IID_PPV_ARGS (&adapter));
|
|
if (!gst_d3d11_result (hr, device)) {
|
|
return gst_d3d11_desktop_dup_return_from_hr (d3d11_device.Get(),
|
|
hr, SystemTransitionsExpectedErrors);
|
|
}
|
|
|
|
hr = adapter->EnumOutputs(monitor_index, &output);
|
|
if (!gst_d3d11_result (hr, device)) {
|
|
return gst_d3d11_desktop_dup_return_from_hr (d3d11_device.Get(),
|
|
hr, EnumOutputsExpectedErrors);
|
|
}
|
|
|
|
output->GetDesc(&output_desc_);
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
return gst_d3d11_desktop_dup_return_from_hr (d3d11_device.Get(), hr,
|
|
CreateDuplicationExpectedErrors);
|
|
}
|
|
|
|
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;
|
|
|
|
/* Mouse is invisible */
|
|
if (!ptr_info->Visible)
|
|
return GST_FLOW_OK;
|
|
|
|
/* No new shape */
|
|
if (frame_info->PointerShapeBufferSize == 0)
|
|
return GST_FLOW_OK;
|
|
|
|
/* Realloc buffer if needed */
|
|
ptr_info->MaybeReallocBuffer (frame_info->PointerShapeBufferSize);
|
|
|
|
/* Get shape */
|
|
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_desktop_dup_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_desktop_dup_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_desktop_dup_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_desktop_dup_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->left, rects->top, rects->right, rects->bottom);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
*texture = acquired_texture.Detach();
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
void
|
|
SetMoveRect (RECT* SrcRect, RECT* DestRect, DXGI_OUTPUT_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_OUTPUT_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_OUTPUT_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 =
|
|
MyFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[1].TexCoord =
|
|
MyFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[2].TexCoord =
|
|
MyFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[5].TexCoord =
|
|
MyFLOAT2(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 =
|
|
MyFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[1].TexCoord =
|
|
MyFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[2].TexCoord =
|
|
MyFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[5].TexCoord =
|
|
MyFLOAT2(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 =
|
|
MyFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[1].TexCoord =
|
|
MyFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[2].TexCoord =
|
|
MyFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[5].TexCoord =
|
|
MyFLOAT2(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 =
|
|
MyFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[1].TexCoord =
|
|
MyFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[2].TexCoord =
|
|
MyFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));
|
|
Vertices[5].TexCoord =
|
|
MyFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width),
|
|
Dirty->top / static_cast<FLOAT>(ThisDesc->Height));
|
|
break;
|
|
}
|
|
|
|
/* Set positions */
|
|
Vertices[0].Pos =
|
|
MyFLOAT3(
|
|
(DestDirty.left - CenterX) / static_cast<FLOAT>(CenterX),
|
|
-1 * (DestDirty.bottom - CenterY) / static_cast<FLOAT>(CenterY),
|
|
0.0f);
|
|
Vertices[1].Pos =
|
|
MyFLOAT3(
|
|
(DestDirty.left - CenterX) / static_cast<FLOAT>(CenterX),
|
|
-1 * (DestDirty.top - CenterY) / static_cast<FLOAT>(CenterY),
|
|
0.0f);
|
|
Vertices[2].Pos =
|
|
MyFLOAT3(
|
|
(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 =
|
|
MyFLOAT3(
|
|
(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_OUTPUT_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 DiretyRects (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 renter 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_desktop_dup_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->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_OUTPUT_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_OUTPUT_DESC output_desc_;
|
|
GstD3D11Device * device_;
|
|
|
|
ComPtr<ID3D11Texture2D> shared_texture_;
|
|
ComPtr<ID3D11RenderTargetView> rtv_;
|
|
ComPtr<ID3D11Texture2D> move_texture_;
|
|
ComPtr<ID3D11VertexShader> vs_;
|
|
ComPtr<ID3D11PixelShader> ps_;
|
|
ComPtr<ID3D11InputLayout> layout_;
|
|
ComPtr<ID3D11SamplerState> sampler_;
|
|
ComPtr<IDXGIOutputDuplication> dupl_;
|
|
ComPtr<ID3D11BlendState> blend_;
|
|
|
|
/* frame metadata */
|
|
BYTE *metadata_buffer_;
|
|
UINT metadata_buffer_size_;
|
|
|
|
/* vertex buffers */
|
|
BYTE *vertex_buffer_;
|
|
UINT vertex_buffer_size_;
|
|
};
|
|
|
|
struct _GstD3D11DesktopDup
|
|
{
|
|
GstObject parent;
|
|
|
|
GstD3D11Device *device;
|
|
guint width;
|
|
guint height;
|
|
|
|
ID3D11Texture2D *texture;
|
|
D3D11DesktopDupObject *dupl_obj;
|
|
|
|
gboolean primary;
|
|
gint monitor_index;
|
|
RECT desktop_coordinates;
|
|
gboolean prepared;
|
|
|
|
GRecMutex lock;
|
|
};
|
|
|
|
static void gst_d3d11_desktop_dup_constructed (GObject * object);
|
|
static void gst_d3d11_desktop_dup_dispose (GObject * object);
|
|
static void gst_d3d11_desktop_dup_finalize (GObject * object);
|
|
static void gst_d3d11_desktop_dup_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
|
|
#define gst_d3d11_desktop_dup_parent_class parent_class
|
|
G_DEFINE_TYPE (GstD3D11DesktopDup, gst_d3d11_desktop_dup, GST_TYPE_OBJECT);
|
|
|
|
static void
|
|
gst_d3d11_desktop_dup_class_init (GstD3D11DesktopDupClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->constructed = gst_d3d11_desktop_dup_constructed;
|
|
gobject_class->dispose = gst_d3d11_desktop_dup_dispose;
|
|
gobject_class->finalize = gst_d3d11_desktop_dup_finalize;
|
|
gobject_class->set_property = gst_d3d11_desktop_dup_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_OUTPUT_INDEX,
|
|
g_param_spec_int ("monitor-index", "Monitor Index",
|
|
"Zero-based index for monitor to capture (-1 = primary monitor)",
|
|
-1, G_MAXINT, DEFAULT_MONITOR_INDEX,
|
|
(GParamFlags)
|
|
(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS)));
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_desktop_dup_init (GstD3D11DesktopDup * self)
|
|
{
|
|
self->monitor_index = DEFAULT_MONITOR_INDEX;
|
|
g_rec_mutex_init (&self->lock);
|
|
|
|
memset (&self->desktop_coordinates, 0, sizeof (RECT));
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d11_desktop_dup_get_monitor_size (GstD3D11DesktopDup * self,
|
|
HMONITOR hmonitor, RECT * size)
|
|
{
|
|
MONITORINFOEX monitor_info;
|
|
DEVMODE dev_mode;
|
|
|
|
monitor_info.cbSize = sizeof (MONITORINFOEX);
|
|
if (!GetMonitorInfo (hmonitor, (LPMONITORINFO) &monitor_info)) {
|
|
GST_WARNING_OBJECT (self, "Couldn't get monitor info");
|
|
return FALSE;
|
|
}
|
|
|
|
dev_mode.dmSize = sizeof (DEVMODE);
|
|
dev_mode.dmDriverExtra = sizeof (POINTL);
|
|
dev_mode.dmFields = DM_POSITION;
|
|
if (!EnumDisplaySettings
|
|
(monitor_info.szDevice, ENUM_CURRENT_SETTINGS, &dev_mode)) {
|
|
GST_WARNING_OBJECT (self, "Couldn't enumerate display settings");
|
|
return FALSE;
|
|
}
|
|
|
|
size->left = dev_mode.dmPosition.x;
|
|
size->top = dev_mode.dmPosition.y;
|
|
size->right = size->left + dev_mode.dmPelsWidth;
|
|
size->bottom = size->top + dev_mode.dmPelsHeight;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_desktop_dup_constructed (GObject * object)
|
|
{
|
|
GstD3D11DesktopDup *self = GST_D3D11_DESKTOP_DUP (object);
|
|
ComPtr<IDXGIDevice> dxgi_device;
|
|
ComPtr<IDXGIAdapter> adapter;
|
|
ComPtr<IDXGIOutput> output;
|
|
ComPtr<IDXGIOutput1> output1;
|
|
ID3D11Device *device_handle;
|
|
HRESULT hr;
|
|
gboolean ret = FALSE;
|
|
DXGI_OUTPUT_DESC output_desc;
|
|
|
|
if (!self->device) {
|
|
GST_WARNING_OBJECT (self, "D3D11 device is unavailable");
|
|
return;
|
|
}
|
|
|
|
device_handle = gst_d3d11_device_get_device_handle (self->device);
|
|
|
|
/* Below code is just for getting resolution of IDXGIOutput (i.e., monitor)
|
|
* and we will setup IDXGIOutputDuplication interface later.
|
|
*/
|
|
hr = device_handle->QueryInterface (IID_PPV_ARGS (&dxgi_device));
|
|
if (!gst_d3d11_result (hr, self->device))
|
|
goto out;
|
|
|
|
hr = dxgi_device->GetParent (IID_PPV_ARGS (&adapter));
|
|
if (!gst_d3d11_result (hr, self->device))
|
|
goto out;
|
|
|
|
if (self->monitor_index < 0) {
|
|
guint index = 0;
|
|
/* Enumerate all outputs to find primary monitor */
|
|
do {
|
|
hr = adapter->EnumOutputs (index, output.ReleaseAndGetAddressOf());
|
|
if (!gst_d3d11_result (hr, self->device))
|
|
goto out;
|
|
|
|
output->GetDesc (&output_desc);
|
|
if (output_desc.DesktopCoordinates.left == 0 &&
|
|
output_desc.DesktopCoordinates.top == 0) {
|
|
GST_DEBUG_OBJECT (self, "Found primary output, index %d", index);
|
|
self->monitor_index = index;
|
|
self->primary = TRUE;
|
|
break;
|
|
}
|
|
index++;
|
|
} while (gst_d3d11_result (hr, self->device));
|
|
} else {
|
|
hr = adapter->EnumOutputs (self->monitor_index, &output);
|
|
if (!gst_d3d11_result (hr, self->device)) {
|
|
GST_WARNING_OBJECT (self, "No available output");
|
|
goto out;
|
|
}
|
|
|
|
output->GetDesc (&output_desc);
|
|
if (output_desc.DesktopCoordinates.left == 0 &&
|
|
output_desc.DesktopCoordinates.top == 0) {
|
|
GST_DEBUG_OBJECT (self, "We are primary output");
|
|
self->primary = TRUE;
|
|
}
|
|
}
|
|
|
|
hr = output.As (&output1);
|
|
if (!gst_d3d11_result (hr, self->device)) {
|
|
GST_WARNING_OBJECT (self, "IDXGIOutput1 interface is unavailble");
|
|
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... */
|
|
if (!gst_d3d11_desktop_dup_get_monitor_size (self,
|
|
output_desc.Monitor, &self->desktop_coordinates)) {
|
|
goto out;
|
|
}
|
|
|
|
self->width =
|
|
self->desktop_coordinates.right - self->desktop_coordinates.left;
|
|
self->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->width, self->height);
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
if (!ret)
|
|
gst_clear_object (&self->device);
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_desktop_dup_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstD3D11DesktopDup *self = GST_D3D11_DESKTOP_DUP (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_D3D11_DEVICE:
|
|
self->device = (GstD3D11Device *) g_value_dup_object (value);
|
|
break;
|
|
case PROP_OUTPUT_INDEX:
|
|
self->monitor_index = g_value_get_int (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_desktop_dup_dispose (GObject * object)
|
|
{
|
|
GstD3D11DesktopDup *self = GST_D3D11_DESKTOP_DUP (object);
|
|
|
|
if (self->texture) {
|
|
self->texture->Release ();
|
|
self->texture = nullptr;
|
|
}
|
|
|
|
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_desktop_dup_finalize (GObject * object)
|
|
{
|
|
GstD3D11DesktopDup *self = GST_D3D11_DESKTOP_DUP (object);
|
|
|
|
g_rec_mutex_clear (&self->lock);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static gboolean
|
|
gst_d3d11_desktop_dup_setup_texture (GstD3D11DesktopDup * self)
|
|
{
|
|
D3D11_TEXTURE2D_DESC texture_desc = { 0, };
|
|
ID3D11Device *device_handle;
|
|
HRESULT hr;
|
|
ComPtr<ID3D11Texture2D> texture;
|
|
|
|
/* This texture is for copying/updating only updated region from previously
|
|
* captured frame (like a reference frame) */
|
|
device_handle = gst_d3d11_device_get_device_handle (self->device);
|
|
|
|
texture_desc.Width = self->width;
|
|
texture_desc.Height = self->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;
|
|
texture_desc.MiscFlags = 0;
|
|
|
|
hr = device_handle->CreateTexture2D (&texture_desc, nullptr, &texture);
|
|
if (!gst_d3d11_result (hr, self->device)) {
|
|
GST_ERROR_OBJECT (self, "Couldn't create texture, hr 0x%x", (guint) hr);
|
|
return FALSE;
|
|
}
|
|
|
|
self->texture = texture.Detach();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_desktop_dup_weak_ref_notify (gpointer data, GstD3D11DesktopDup * dupl)
|
|
{
|
|
G_LOCK (dupl_list_lock);
|
|
dupl_list = g_list_remove (dupl_list, dupl);
|
|
G_UNLOCK (dupl_list_lock);
|
|
}
|
|
|
|
GstD3D11DesktopDup *
|
|
gst_d3d11_desktop_dup_new (GstD3D11Device * device, gint monitor_index)
|
|
{
|
|
GstD3D11DesktopDup *self = nullptr;
|
|
GList *iter;
|
|
|
|
g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), nullptr);
|
|
|
|
/* Check if we have dup object corresponding to monitor_index, 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
|
|
*/
|
|
G_LOCK (dupl_list_lock);
|
|
for (iter = dupl_list; iter; iter = g_list_next (iter)) {
|
|
GstD3D11DesktopDup *dupl = (GstD3D11DesktopDup *) iter->data;
|
|
|
|
if (dupl->monitor_index == monitor_index ||
|
|
(monitor_index < 0 && dupl->primary)) {
|
|
GST_DEBUG ("Found configured desktop dup object for output index %d",
|
|
monitor_index);
|
|
self = (GstD3D11DesktopDup *) gst_object_ref (dupl);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (self) {
|
|
G_UNLOCK (dupl_list_lock);
|
|
return self;
|
|
}
|
|
|
|
self = (GstD3D11DesktopDup *) g_object_new (GST_TYPE_D3D11_DESKTOP_DUP,
|
|
"d3d11device", device, "monitor-index", monitor_index, nullptr);
|
|
|
|
if (!self->device) {
|
|
GST_WARNING_OBJECT (self, "Couldn't configure desktop dup object");
|
|
gst_object_unref (self);
|
|
G_UNLOCK (dupl_list_lock);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
g_object_weak_ref (G_OBJECT (self),
|
|
(GWeakNotify) gst_d3d11_desktop_dup_weak_ref_notify, nullptr);
|
|
dupl_list = g_list_append (dupl_list, self);
|
|
|
|
G_UNLOCK (dupl_list_lock);
|
|
|
|
return self;
|
|
}
|
|
|
|
GstFlowReturn
|
|
gst_d3d11_desktop_dup_prepare (GstD3D11DesktopDup * desktop)
|
|
{
|
|
GstFlowReturn ret;
|
|
|
|
g_return_val_if_fail (GST_IS_D3D11_DESKTOP_DUP (desktop), GST_FLOW_ERROR);
|
|
g_return_val_if_fail (desktop->device != nullptr, GST_FLOW_ERROR);
|
|
|
|
g_rec_mutex_lock (&desktop->lock);
|
|
if (desktop->prepared) {
|
|
GST_DEBUG_OBJECT (desktop, "Already prepared");
|
|
g_rec_mutex_unlock (&desktop->lock);
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
if (!desktop->texture && !gst_d3d11_desktop_dup_setup_texture (desktop)) {
|
|
GST_ERROR_OBJECT (desktop, "Couldn't setup internal texture");
|
|
g_rec_mutex_unlock (&desktop->lock);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
desktop->dupl_obj = new D3D11DesktopDupObject ();
|
|
ret = desktop->dupl_obj->Init (desktop->device, desktop->texture,
|
|
desktop->monitor_index);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (desktop,
|
|
"Couldn't prepare capturing, %sexpected failure",
|
|
ret == GST_D3D11_DESKTOP_DUP_FLOW_EXPECTED_ERROR ? "" : "un");
|
|
|
|
delete desktop->dupl_obj;
|
|
desktop->dupl_obj = nullptr;
|
|
g_rec_mutex_unlock (&desktop->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
desktop->prepared = TRUE;
|
|
g_rec_mutex_unlock (&desktop->lock);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
gboolean
|
|
gst_d3d11_desktop_dup_get_coordinates (GstD3D11DesktopDup * desktop,
|
|
RECT * desktop_coordinates)
|
|
{
|
|
g_return_val_if_fail (GST_IS_D3D11_DESKTOP_DUP (desktop), FALSE);
|
|
g_return_val_if_fail (desktop_coordinates != nullptr, FALSE);
|
|
|
|
*desktop_coordinates = desktop->desktop_coordinates;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GstFlowReturn
|
|
gst_d3d11_desktop_dup_capture (GstD3D11DesktopDup * desktop,
|
|
ID3D11Texture2D * texture, ID3D11RenderTargetView * rtv,
|
|
gboolean draw_mouse)
|
|
{
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
ID3D11DeviceContext *device_context_handle;
|
|
|
|
g_return_val_if_fail (GST_IS_D3D11_DESKTOP_DUP (desktop), GST_FLOW_ERROR);
|
|
g_return_val_if_fail (texture != nullptr, GST_FLOW_ERROR);
|
|
|
|
g_rec_mutex_lock (&desktop->lock);
|
|
if (!desktop->prepared)
|
|
ret = gst_d3d11_desktop_dup_prepare (desktop);
|
|
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (desktop, "We are not prepared");
|
|
g_rec_mutex_unlock (&desktop->lock);
|
|
return ret;
|
|
}
|
|
|
|
gst_d3d11_device_lock (desktop->device);
|
|
ret = desktop->dupl_obj->Capture (draw_mouse);
|
|
if (ret != GST_FLOW_OK) {
|
|
gst_d3d11_device_unlock (desktop->device);
|
|
|
|
delete desktop->dupl_obj;
|
|
desktop->dupl_obj = nullptr;
|
|
desktop->prepared = FALSE;
|
|
|
|
if (ret == GST_D3D11_DESKTOP_DUP_FLOW_EXPECTED_ERROR) {
|
|
GST_WARNING_OBJECT (desktop,
|
|
"Couldn't capture frame, but expected failure");
|
|
} else {
|
|
GST_ERROR_OBJECT (desktop, "Unexpected failure during capture");
|
|
}
|
|
|
|
g_rec_mutex_unlock (&desktop->lock);
|
|
return ret;
|
|
}
|
|
|
|
GST_LOG_OBJECT (desktop, "Capture done");
|
|
|
|
device_context_handle =
|
|
gst_d3d11_device_get_device_context_handle (desktop->device);
|
|
device_context_handle->CopySubresourceRegion (texture, 0, 0, 0, 0,
|
|
desktop->texture, 0, nullptr);
|
|
if (draw_mouse)
|
|
desktop->dupl_obj->DrawMouse (rtv);
|
|
gst_d3d11_device_unlock (desktop->device);
|
|
g_rec_mutex_unlock (&desktop->lock);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|