From 2ca298d4594508fa12a6b5e15083a2fd1c7abe5b Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Sat, 21 Nov 2020 03:20:36 +0900 Subject: [PATCH] d3d11: Re-implement Desktop Duplication source Add a new video source element "d3d11desktopdupsrc" for capturing desktop image via Desktop Duplication based on Microsoft's Desktop Duplication sample available at https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/DXGIDesktopDuplication This element is expected to be a replacement of existing dxgiscreencapsrc element in winscreencap plugin. Currently this element can support (but dxgiscreencapsrc cannot) - Copying captured D3D11 texture to output buffer without download - Support desktop session transition e.g., can capture desktop without error even in case that "Lock desktop" and "Permission dialog" - Multiple d3d11desktopdupsrc elements can capture the same monitor Not yet implemented features - Cropping rect is not implemented, but that can be handled by downstream - Mult-monitor is not supported. But that is also can be implemented by downstream element for example via multiple d3d11desktopdup elements with d3d11compositor Part-of: --- sys/d3d11/gstd3d11desktopdup.cpp | 1855 +++++++++++++++++++++++++++++++++++++ sys/d3d11/gstd3d11desktopdup.h | 51 + sys/d3d11/gstd3d11desktopdupsrc.c | 653 +++++++++++++ sys/d3d11/gstd3d11desktopdupsrc.h | 37 + sys/d3d11/meson.build | 21 +- sys/d3d11/plugin.c | 16 + 6 files changed, 2632 insertions(+), 1 deletion(-) create mode 100644 sys/d3d11/gstd3d11desktopdup.cpp create mode 100644 sys/d3d11/gstd3d11desktopdup.h create mode 100644 sys/d3d11/gstd3d11desktopdupsrc.c create mode 100644 sys/d3d11/gstd3d11desktopdupsrc.h diff --git a/sys/d3d11/gstd3d11desktopdup.cpp b/sys/d3d11/gstd3d11desktopdup.cpp new file mode 100644 index 0000000..96f0861 --- /dev/null +++ b/sys/d3d11/gstd3d11desktopdup.cpp @@ -0,0 +1,1855 @@ +/* + * GStreamer + * Copyright (C) 2020 Seungha Yang + * + * 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 "gstd3d11device.h" +#include "gstd3d11memory.h" +#include "gstd3d11utils.h" +#include "gstd3d11shader.h" +#include + +#include + +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(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(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(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 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 MouseTex; + ComPtr ShaderRes; + ComPtr 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(ptr_info_.shape_info.Width); + PtrHeight = static_cast(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 vs; + ComPtr 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 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 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 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 d3d11_device; + ComPtr dxgi_device; + ComPtr adapter; + ComPtr output; + ComPtr 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 resource; + ComPtr 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(ThisDesc->Width), + Dirty->bottom / static_cast(ThisDesc->Height)); + Vertices[1].TexCoord = + MyFLOAT2(Dirty->left / static_cast(ThisDesc->Width), + Dirty->bottom / static_cast(ThisDesc->Height)); + Vertices[2].TexCoord = + MyFLOAT2(Dirty->right / static_cast(ThisDesc->Width), + Dirty->top / static_cast(ThisDesc->Height)); + Vertices[5].TexCoord = + MyFLOAT2(Dirty->left / static_cast(ThisDesc->Width), + Dirty->top / static_cast(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(ThisDesc->Width), + Dirty->top / static_cast(ThisDesc->Height)); + Vertices[1].TexCoord = + MyFLOAT2(Dirty->right / static_cast(ThisDesc->Width), + Dirty->bottom / static_cast(ThisDesc->Height)); + Vertices[2].TexCoord = + MyFLOAT2(Dirty->left / static_cast(ThisDesc->Width), + Dirty->top / static_cast(ThisDesc->Height)); + Vertices[5].TexCoord = + MyFLOAT2(Dirty->left / static_cast(ThisDesc->Width), + Dirty->bottom / static_cast(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(ThisDesc->Width), + Dirty->top / static_cast(ThisDesc->Height)); + Vertices[1].TexCoord = + MyFLOAT2(Dirty->right / static_cast(ThisDesc->Width), + Dirty->top / static_cast(ThisDesc->Height)); + Vertices[2].TexCoord = + MyFLOAT2(Dirty->left / static_cast(ThisDesc->Width), + Dirty->bottom / static_cast(ThisDesc->Height)); + Vertices[5].TexCoord = + MyFLOAT2(Dirty->right / static_cast(ThisDesc->Width), + Dirty->bottom / static_cast(ThisDesc->Height)); + break; + case DXGI_MODE_ROTATION_UNSPECIFIED: + case DXGI_MODE_ROTATION_IDENTITY: + default: + Vertices[0].TexCoord = + MyFLOAT2(Dirty->left / static_cast(ThisDesc->Width), + Dirty->bottom / static_cast(ThisDesc->Height)); + Vertices[1].TexCoord = + MyFLOAT2(Dirty->left / static_cast(ThisDesc->Width), + Dirty->top / static_cast(ThisDesc->Height)); + Vertices[2].TexCoord = + MyFLOAT2(Dirty->right / static_cast(ThisDesc->Width), + Dirty->bottom / static_cast(ThisDesc->Height)); + Vertices[5].TexCoord = + MyFLOAT2(Dirty->right / static_cast(ThisDesc->Width), + Dirty->top / static_cast(ThisDesc->Height)); + break; + } + + /* Set positions */ + Vertices[0].Pos = + MyFLOAT3( + (DestDirty.left - CenterX) / static_cast(CenterX), + -1 * (DestDirty.bottom - CenterY) / static_cast(CenterY), + 0.0f); + Vertices[1].Pos = + MyFLOAT3( + (DestDirty.left - CenterX) / static_cast(CenterX), + -1 * (DestDirty.top - CenterY) / static_cast(CenterY), + 0.0f); + Vertices[2].Pos = + MyFLOAT3( + (DestDirty.right - CenterX) / static_cast(CenterX), + -1 * (DestDirty.bottom - CenterY) / static_cast(CenterY), + 0.0f); + Vertices[3].Pos = Vertices[2].Pos; + Vertices[4].Pos = Vertices[1].Pos; + Vertices[5].Pos = + MyFLOAT3( + (DestDirty.right - CenterX) / static_cast(CenterX), + -1 * (DestDirty.top - CenterY) / static_cast(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 ShaderResource; + ComPtr 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(FullDesc.Width); + VP.Height = static_cast(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(PtrInfo->shape_info.Width); + } else if ((GivenLeft + static_cast(PtrInfo->shape_info.Width)) > DesktopWidth) { + *PtrWidth = DesktopWidth - GivenLeft; + } else { + *PtrWidth = static_cast(PtrInfo->shape_info.Width); + } + + if (IsMono) + PtrInfo->shape_info.Height = PtrInfo->shape_info.Height / 2; + + if (GivenTop < 0) { + *PtrHeight = GivenTop + static_cast(PtrInfo->shape_info.Height); + } else if ((GivenTop + static_cast(PtrInfo->shape_info.Height)) > DesktopHeight) { + *PtrHeight = DesktopHeight - GivenTop; + } else { + *PtrHeight = static_cast(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 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 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(*InitBuffer); + UINT* Desktop32 = reinterpret_cast(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(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 shared_texture_; + ComPtr rtv_; + ComPtr move_texture_; + ComPtr vs_; + ComPtr ps_; + ComPtr layout_; + ComPtr sampler_; + ComPtr dupl_; + ComPtr 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 dxgi_device; + ComPtr adapter; + ComPtr output; + ComPtr 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 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; +} diff --git a/sys/d3d11/gstd3d11desktopdup.h b/sys/d3d11/gstd3d11desktopdup.h new file mode 100644 index 0000000..2ea0ab7b --- /dev/null +++ b/sys/d3d11/gstd3d11desktopdup.h @@ -0,0 +1,51 @@ +/* + * GStreamer + * Copyright (C) 2020 Seungha Yang + * + * 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. + */ + +#ifndef __GST_D3D11_DESKTOP_DUP_H__ +#define __GST_D3D11_DESKTOP_DUP_H__ + +#include +#include +#include "gstd3d11_fwd.h" + +G_BEGIN_DECLS + +#define GST_D3D11_DESKTOP_DUP_FLOW_EXPECTED_ERROR GST_FLOW_CUSTOM_SUCCESS + +#define GST_TYPE_D3D11_DESKTOP_DUP (gst_d3d11_desktop_dup_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D11DesktopDup, gst_d3d11_desktop_dup, + GST, D3D11_DESKTOP_DUP, GstObject); + +GstD3D11DesktopDup * gst_d3d11_desktop_dup_new (GstD3D11Device * device, + gint output_index); + +GstFlowReturn gst_d3d11_desktop_dup_prepare (GstD3D11DesktopDup * desktop); + +gboolean gst_d3d11_desktop_dup_get_coordinates (GstD3D11DesktopDup * desktop, + RECT * desktop_coordinates); + +GstFlowReturn gst_d3d11_desktop_dup_capture (GstD3D11DesktopDup * desktop, + ID3D11Texture2D * texture, + ID3D11RenderTargetView *rtv, + gboolean draw_mouse); + +G_END_DECLS + +#endif /* __GST_D3D11_DESKTOP_DUP_H__ */ diff --git a/sys/d3d11/gstd3d11desktopdupsrc.c b/sys/d3d11/gstd3d11desktopdupsrc.c new file mode 100644 index 0000000..592f397 --- /dev/null +++ b/sys/d3d11/gstd3d11desktopdupsrc.c @@ -0,0 +1,653 @@ +/* + * GStreamer + * Copyright (C) 2020 Seungha Yang + * + * 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. + */ + +/** + * SECTION:element-d3d11desktopdupsrc + * @title: d3d11desktopdupsrc + * + * This element uses DXGI Desktop Duplication API. + * The default is capturing the whole desktop, but #GstD3D11DesktopDupSrc:x, + * #GstD3D11DesktopDupSrc:y, #GstD3D11DesktopDupSrc:width and + * #GstD3D11DesktopDupSrc:height can be used to select a particular region. + * + * ## Example pipelines + * |[ + * gst-launch-1.0 d3d11desktopdupsrc ! queue ! d3d11videosink + * ]| Capture the desktop and display it. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d11memory.h" +#include "gstd3d11utils.h" +#include "gstd3d11bufferpool.h" +#include "gstd3d11format.h" +#include "gstd3d11device.h" + +#include "gstd3d11desktopdupsrc.h" +#include "gstd3d11desktopdup.h" + +#include + +GST_DEBUG_CATEGORY_EXTERN (gst_d3d11_desktop_dup_debug); +#define GST_CAT_DEFAULT gst_d3d11_desktop_dup_debug + +enum +{ + PROP_0, + PROP_MONITOR_INDEX, + PROP_SHOW_CURSOR, + + PROP_LAST, +}; + +static GParamSpec *properties[PROP_LAST]; + +#define DEFAULT_MONITOR_INDEX -1 +#define DEFAULT_SHOW_CURSOR FALSE + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, "BGRA") + )); + +struct _GstD3D11DesktopDupSrc +{ + GstPushSrc src; + + guint64 last_frame_no; + GstClockID clock_id; + GstVideoInfo video_info; + + GstD3D11Device *device; + GstD3D11DesktopDup *dupl; + + gint adapter; + gint monitor_index; + gboolean show_cursor; + + gboolean flushing; + GstClockTime min_latency; + GstClockTime max_latency; +}; + +static void gst_d3d11_desktop_dup_src_dispose (GObject * object); +static void gst_d3d11_desktop_dup_src_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_d3d11_desktop_dup_src_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static void gst_d3d11_desktop_dup_src_set_context (GstElement * element, + GstContext * context); + +static GstCaps *gst_d3d11_desktop_dup_src_get_caps (GstBaseSrc * bsrc, + GstCaps * filter); +static GstCaps *gst_d3d11_desktop_dup_src_fixate (GstBaseSrc * bsrc, + GstCaps * caps); +static gboolean gst_d3d11_desktop_dup_src_set_caps (GstBaseSrc * bsrc, + GstCaps * caps); +static gboolean gst_d3d11_desktop_dup_src_decide_allocation (GstBaseSrc * bsrc, + GstQuery * query); +static gboolean gst_d3d11_desktop_dup_src_start (GstBaseSrc * bsrc); +static gboolean gst_d3d11_desktop_dup_src_stop (GstBaseSrc * bsrc); +static gboolean gst_d3d11_desktop_dup_src_unlock (GstBaseSrc * bsrc); +static gboolean gst_d3d11_desktop_dup_src_unlock_stop (GstBaseSrc * bsrc); +static gboolean +gst_d3d11_desktop_dup_src_src_query (GstBaseSrc * bsrc, GstQuery * query); + +static GstFlowReturn gst_d3d11_desktop_dup_src_fill (GstPushSrc * pushsrc, + GstBuffer * buffer); + +#define gst_d3d11_desktop_dup_src_parent_class parent_class +G_DEFINE_TYPE (GstD3D11DesktopDupSrc, gst_d3d11_desktop_dup_src, + GST_TYPE_PUSH_SRC); + +static void +gst_d3d11_desktop_dup_src_class_init (GstD3D11DesktopDupSrcClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstBaseSrcClass *basesrc_class = GST_BASE_SRC_CLASS (klass); + GstPushSrcClass *pushsrc_class = GST_PUSH_SRC_CLASS (klass); + + gobject_class->dispose = gst_d3d11_desktop_dup_src_dispose; + gobject_class->set_property = gst_d3d11_desktop_dup_src_set_property; + gobject_class->get_property = gst_d3d11_desktop_dup_src_get_property; + + properties[PROP_MONITOR_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, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | G_PARAM_STATIC_STRINGS); + + properties[PROP_SHOW_CURSOR] = + g_param_spec_boolean ("show-cursor", + "Show Mouse Cursor", "Whether to show mouse cursor", + DEFAULT_SHOW_CURSOR, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, properties); + + element_class->set_context = + GST_DEBUG_FUNCPTR (gst_d3d11_desktop_dup_src_set_context); + + gst_element_class_set_static_metadata (element_class, + "Direct3D11 desktop duplication src", "Source/Video", + "Capture desktop image by using Desktop Duplication API", + "Seungha Yang "); + + gst_element_class_add_static_pad_template (element_class, &src_template); + + basesrc_class->get_caps = + GST_DEBUG_FUNCPTR (gst_d3d11_desktop_dup_src_get_caps); + basesrc_class->fixate = GST_DEBUG_FUNCPTR (gst_d3d11_desktop_dup_src_fixate); + basesrc_class->set_caps = + GST_DEBUG_FUNCPTR (gst_d3d11_desktop_dup_src_set_caps); + basesrc_class->decide_allocation = + GST_DEBUG_FUNCPTR (gst_d3d11_desktop_dup_src_decide_allocation); + basesrc_class->start = GST_DEBUG_FUNCPTR (gst_d3d11_desktop_dup_src_start); + basesrc_class->stop = GST_DEBUG_FUNCPTR (gst_d3d11_desktop_dup_src_stop); + basesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_d3d11_desktop_dup_src_unlock); + basesrc_class->unlock_stop = + GST_DEBUG_FUNCPTR (gst_d3d11_desktop_dup_src_unlock_stop); + basesrc_class->query = + GST_DEBUG_FUNCPTR (gst_d3d11_desktop_dup_src_src_query); + + pushsrc_class->fill = GST_DEBUG_FUNCPTR (gst_d3d11_desktop_dup_src_fill); +} + +static void +gst_d3d11_desktop_dup_src_init (GstD3D11DesktopDupSrc * self) +{ + gst_base_src_set_live (GST_BASE_SRC (self), TRUE); + gst_base_src_set_format (GST_BASE_SRC (self), GST_FORMAT_TIME); + + /* FIXME: investigate non-zero adapter use case */ + self->adapter = 0; + self->monitor_index = DEFAULT_MONITOR_INDEX; + self->show_cursor = DEFAULT_SHOW_CURSOR; + self->min_latency = GST_CLOCK_TIME_NONE; + self->max_latency = GST_CLOCK_TIME_NONE; +} + +static void +gst_d3d11_desktop_dup_src_dispose (GObject * object) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (object); + + gst_clear_object (&self->dupl); + gst_clear_object (&self->device); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_d3d11_desktop_dup_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (object); + + switch (prop_id) { + case PROP_MONITOR_INDEX: + self->monitor_index = g_value_get_int (value); + break; + case PROP_SHOW_CURSOR: + self->show_cursor = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; +} + +static void +gst_d3d11_desktop_dup_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (object); + + switch (prop_id) { + case PROP_MONITOR_INDEX: + g_value_set_int (value, self->monitor_index); + break; + case PROP_SHOW_CURSOR: + g_value_set_boolean (value, self->show_cursor); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; +} + +static void +gst_d3d11_desktop_dup_src_set_context (GstElement * element, + GstContext * context) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (element); + + gst_d3d11_handle_set_context (element, context, self->adapter, &self->device); + + GST_ELEMENT_CLASS (parent_class)->set_context (element, context); +} + +static GstCaps * +gst_d3d11_desktop_dup_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (bsrc); + GstCaps *caps = NULL; + guint width, height; + RECT desktop_coordinates; + + if (!self->dupl) { + GST_DEBUG_OBJECT (self, "Duplication object is not configured yet"); + return gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (bsrc)); + } + + if (!gst_d3d11_desktop_dup_get_coordinates (self->dupl, &desktop_coordinates)) { + GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, + ("Cannot query supported resolution"), (NULL)); + return NULL; + } + + width = desktop_coordinates.right - desktop_coordinates.left; + height = desktop_coordinates.bottom - desktop_coordinates.top; + + caps = + gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING, "BGRA", + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + "framerate", GST_TYPE_FRACTION_RANGE, 1, 1, G_MAXINT, 1, NULL); + gst_caps_set_features (caps, 0, + gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, NULL)); + + if (filter) { + GstCaps *tmp = + gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (caps); + + caps = tmp; + } + + return caps; +} + +static GstCaps * +gst_d3d11_desktop_dup_src_fixate (GstBaseSrc * bsrc, GstCaps * caps) +{ + GstStructure *structure; + + caps = gst_caps_make_writable (caps); + + structure = gst_caps_get_structure (caps, 0); + + gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1); + + return GST_BASE_SRC_CLASS (parent_class)->fixate (bsrc, caps); +} + +static gboolean +gst_d3d11_desktop_dup_src_set_caps (GstBaseSrc * bsrc, GstCaps * caps) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (bsrc); + + GST_DEBUG_OBJECT (self, "Set caps %" GST_PTR_FORMAT, caps); + + gst_video_info_from_caps (&self->video_info, caps); + + return TRUE; +} + +static gboolean +gst_d3d11_desktop_dup_src_decide_allocation (GstBaseSrc * bsrc, + GstQuery * query) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (bsrc); + GstBufferPool *pool = NULL; + GstStructure *config; + GstD3D11AllocationParams *d3d11_params; + GstCaps *caps; + guint min, max, size; + gboolean update_pool; + GstVideoInfo vinfo; + + gst_query_parse_allocation (query, &caps, NULL); + + if (!caps) { + GST_ERROR_OBJECT (self, "No output caps"); + return FALSE; + } + + gst_video_info_from_caps (&vinfo, caps); + + if (gst_query_get_n_allocation_pools (query) > 0) { + gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); + update_pool = TRUE; + } else { + size = GST_VIDEO_INFO_SIZE (&vinfo); + + min = max = 0; + update_pool = FALSE; + } + + if (!pool || !GST_IS_D3D11_BUFFER_POOL (pool)) { + gst_clear_object (&pool); + pool = gst_d3d11_buffer_pool_new (self->device); + } + + config = gst_buffer_pool_get_config (pool); + + gst_buffer_pool_config_set_params (config, caps, size, min, max); + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + + d3d11_params = gst_buffer_pool_config_get_d3d11_allocation_params (config); + if (!d3d11_params) { + d3d11_params = gst_d3d11_allocation_params_new (self->device, &vinfo, 0, + D3D11_BIND_RENDER_TARGET); + } else { + d3d11_params->desc[0].BindFlags |= D3D11_BIND_RENDER_TARGET; + } + + gst_buffer_pool_config_set_d3d11_allocation_params (config, d3d11_params); + gst_d3d11_allocation_params_free (d3d11_params); + + gst_buffer_pool_set_config (pool, config); + + /* buffer size might be recalculated by pool depending on + * device's stride/padding constraints */ + size = GST_D3D11_BUFFER_POOL (pool)->buffer_size; + + if (update_pool) + gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max); + else + gst_query_add_allocation_pool (query, pool, size, min, max); + + gst_object_unref (pool); + + return TRUE; +} + +static gboolean +gst_d3d11_desktop_dup_src_start (GstBaseSrc * bsrc) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (bsrc); + + /* FIXME: this element will use only the first adapter, but + * this might cause issue in case of multi-gpu environment and + * some monitor is connected to non-default adapter */ + if (!gst_d3d11_ensure_element_data (GST_ELEMENT_CAST (self), self->adapter, + &self->device)) { + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("D3D11 device with adapter index %d is unavailble", self->adapter), + (NULL)); + + return FALSE; + } + + self->dupl = gst_d3d11_desktop_dup_new (self->device, self->monitor_index); + if (!self->dupl) { + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("Failed to prepare duplication for output index %d", + self->monitor_index), (NULL)); + + return FALSE; + } + + self->last_frame_no = -1; + self->min_latency = self->max_latency = GST_CLOCK_TIME_NONE; + + return TRUE; +} + +static gboolean +gst_d3d11_desktop_dup_src_stop (GstBaseSrc * bsrc) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (bsrc); + + gst_clear_object (&self->dupl); + gst_clear_object (&self->device); + + return TRUE; +} + +static gboolean +gst_d3d11_desktop_dup_src_unlock (GstBaseSrc * bsrc) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (bsrc); + + GST_OBJECT_LOCK (self); + if (self->clock_id) { + GST_DEBUG_OBJECT (self, "Waking up waiting clock"); + gst_clock_id_unschedule (self->clock_id); + } + self->flushing = TRUE; + GST_OBJECT_UNLOCK (self); + + return TRUE; +} + +static gboolean +gst_d3d11_desktop_dup_src_unlock_stop (GstBaseSrc * bsrc) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (bsrc); + + GST_OBJECT_LOCK (self); + self->flushing = FALSE; + GST_OBJECT_UNLOCK (self); + + return TRUE; +} + +static gboolean +gst_d3d11_desktop_dup_src_src_query (GstBaseSrc * bsrc, GstQuery * query) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (bsrc); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONTEXT: + if (gst_d3d11_handle_context_query (GST_ELEMENT_CAST (self), query, + self->device)) { + return TRUE; + } + break; + case GST_QUERY_LATENCY: + if (GST_CLOCK_TIME_IS_VALID (self->min_latency)) { + gst_query_set_latency (query, + TRUE, self->min_latency, self->max_latency); + return TRUE; + } + break; + default: + break; + } + + return GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query); +} + +static GstFlowReturn +gst_d3d11_desktop_dup_src_fill (GstPushSrc * pushsrc, GstBuffer * buffer) +{ + GstD3D11DesktopDupSrc *self = GST_D3D11_DESKTOP_DUP_SRC (pushsrc); + ID3D11Texture2D *texture; + ID3D11RenderTargetView *rtv = NULL; + gint fps_n, fps_d; + GstMapInfo info; + GstMemory *mem; + GstD3D11Memory *dmem; + GstFlowReturn ret = GST_FLOW_OK; + GstClock *clock = NULL; + GstClockTime base_time; + GstClockTime next_capture_ts; + GstClockTime before_capture; + GstClockTime after_capture; + GstClockTime latency; + GstClockTime dur; + gboolean update_latency = FALSE; + gint64 next_frame_no; + gboolean draw_mouse; + + if (!self->dupl) { + GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, + ("Couldn't configure DXGI Desktop Duplication capture object"), (NULL)); + return GST_FLOW_NOT_NEGOTIATED; + } + + fps_n = GST_VIDEO_INFO_FPS_N (&self->video_info); + fps_d = GST_VIDEO_INFO_FPS_D (&self->video_info); + + if (fps_n <= 0 || fps_d <= 0) + return GST_FLOW_NOT_NEGOTIATED; + +again: + clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); + if (!clock) { + GST_ELEMENT_ERROR (self, RESOURCE, FAILED, + ("Cannot operate without a clock"), (NULL)); + return GST_FLOW_ERROR; + } + + /* Check flushing before waiting clock because we are might be doing + * retry */ + GST_OBJECT_LOCK (self); + if (self->flushing) { + ret = GST_FLOW_FLUSHING; + GST_OBJECT_UNLOCK (self); + goto out; + } + + base_time = GST_ELEMENT_CAST (self)->base_time; + next_capture_ts = gst_clock_get_time (clock); + next_capture_ts -= base_time; + + next_frame_no = gst_util_uint64_scale (next_capture_ts, + fps_n, GST_SECOND * fps_d); + + if (next_frame_no == self->last_frame_no) { + GstClockID id; + GstClockReturn ret; + + /* Need to wait for the next frame */ + next_frame_no += 1; + + /* Figure out what the next frame time is */ + next_capture_ts = gst_util_uint64_scale (next_frame_no, + fps_d * GST_SECOND, fps_n); + + id = gst_clock_new_single_shot_id (GST_ELEMENT_CLOCK (self), + next_capture_ts + base_time); + self->clock_id = id; + + /* release the object lock while waiting */ + GST_OBJECT_UNLOCK (self); + + GST_LOG_OBJECT (self, "Waiting for next frame time %" GST_TIME_FORMAT, + GST_TIME_ARGS (next_capture_ts)); + ret = gst_clock_id_wait (id, NULL); + GST_OBJECT_LOCK (self); + + gst_clock_id_unref (id); + self->clock_id = NULL; + + if (ret == GST_CLOCK_UNSCHEDULED) { + /* Got woken up by the unlock function */ + ret = GST_FLOW_FLUSHING; + GST_OBJECT_UNLOCK (self); + goto out; + } + /* Duration is a complete 1/fps frame duration */ + dur = gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n); + } else { + GstClockTime next_frame_ts; + + GST_LOG_OBJECT (self, "No need to wait for next frame time %" + GST_TIME_FORMAT " next frame = %" G_GINT64_FORMAT " prev = %" + G_GINT64_FORMAT, GST_TIME_ARGS (next_capture_ts), next_frame_no, + self->last_frame_no); + + next_frame_ts = gst_util_uint64_scale (next_frame_no + 1, + fps_d * GST_SECOND, fps_n); + /* Frame duration is from now until the next expected capture time */ + dur = next_frame_ts - next_capture_ts; + } + + self->last_frame_no = next_frame_no; + GST_OBJECT_UNLOCK (self); + + /* FIXME: handle fallback case + * (e.g., texture belongs to other device, RTV is unavailable) */ + mem = gst_buffer_peek_memory (buffer, 0); + dmem = (GstD3D11Memory *) mem; + draw_mouse = self->show_cursor; + rtv = gst_d3d11_memory_get_render_target_view (dmem, 0); + if (draw_mouse && !rtv) { + GST_ERROR_OBJECT (self, "Render target view is unavailable"); + ret = GST_FLOW_ERROR; + goto out; + } + + if (!gst_memory_map (mem, &info, GST_MAP_WRITE | GST_MAP_D3D11)) { + GST_ERROR_OBJECT (self, "Failed to map d3d11 memory"); + ret = GST_FLOW_ERROR; + goto out; + } + + texture = (ID3D11Texture2D *) info.data; + before_capture = gst_clock_get_time (clock); + ret = gst_d3d11_desktop_dup_capture (self->dupl, texture, rtv, draw_mouse); + gst_memory_unmap (mem, &info); + + GST_BUFFER_DTS (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_PTS (buffer) = next_capture_ts; + GST_BUFFER_DURATION (buffer) = dur; + + if (ret == GST_D3D11_DESKTOP_DUP_FLOW_EXPECTED_ERROR) { + GST_WARNING_OBJECT (self, "Got expected error, try again"); + gst_clear_object (&clock); + goto again; + } + + after_capture = gst_clock_get_time (clock); + latency = after_capture - before_capture; + if (!GST_CLOCK_TIME_IS_VALID (self->min_latency)) { + self->min_latency = self->max_latency = latency; + update_latency = TRUE; + GST_DEBUG_OBJECT (self, "Initial latency %" GST_TIME_FORMAT, + GST_TIME_ARGS (latency)); + } + + if (latency > self->max_latency) { + self->max_latency = latency; + update_latency = TRUE; + GST_DEBUG_OBJECT (self, "Updating max latency %" GST_TIME_FORMAT, + GST_TIME_ARGS (latency)); + } + + if (update_latency) { + gst_element_post_message (GST_ELEMENT_CAST (self), + gst_message_new_latency (GST_OBJECT_CAST (self))); + } + +out: + gst_clear_object (&clock); + + return ret; +} diff --git a/sys/d3d11/gstd3d11desktopdupsrc.h b/sys/d3d11/gstd3d11desktopdupsrc.h new file mode 100644 index 0000000..e55d8ba --- /dev/null +++ b/sys/d3d11/gstd3d11desktopdupsrc.h @@ -0,0 +1,37 @@ +/* + * GStreamer + * Copyright (C) 2020 Seungha Yang + * + * 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. + */ + +#ifndef __GST_D3D11_DESKTOP_DUP_SRC_H__ +#define __GST_D3D11_DESKTOP_DUP_SRC_H__ + +#include +#include +#include +#include "gstd3d11_fwd.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D11_DESKTOP_DUP_SRC (gst_d3d11_desktop_dup_src_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D11DesktopDupSrc, gst_d3d11_desktop_dup_src, + GST, D3D11_DESKTOP_DUP_SRC, GstPushSrc); + +G_END_DECLS + +#endif /* __GST_D3D11_DESKTOP_DUP_H__ */ diff --git a/sys/d3d11/meson.build b/sys/d3d11/meson.build index 2ba72f3..947c3a9 100644 --- a/sys/d3d11/meson.build +++ b/sys/d3d11/meson.build @@ -47,6 +47,7 @@ d3d11_headers = [ have_d3d11 = false extra_c_args = ['-DCOBJMACROS'] +extra_cpp_args = [] have_dxgi_header = false have_d3d11_header = false have_d3d11sdk_h = false @@ -131,6 +132,13 @@ if winapi_app_only extra_dep += [runtimeobject_lib, d3dcompiler_lib] else d3d11_sources += ['gstd3d11window_win32.cpp'] + if d3d11_conf.get('DXGI_HEADER_VERSION') >= 6 + # Desktop Duplication API is unavailable for UWP + # and MinGW is not supported due to some missing headers + d3d11_conf.set('HAVE_DXGI_DESKTOP_DUP', 1) + d3d11_sources += ['gstd3d11desktopdup.cpp', 'gstd3d11desktopdupsrc.c'] + message('Enable D3D11 Desktop Duplication API') + endif endif d3d11_conf.set10('GST_D3D11_WINAPI_ONLY_APP', winapi_app_only) @@ -200,10 +208,21 @@ configure_file( configuration: d3d11_conf, ) +# MinGW 32bits compiler seems to be complaining about redundant-decls +# when ComPtr is in use. Let's just disable the warning +if cc.get_id() != 'msvc' + extra_args = cc.get_supported_arguments([ + '-Wno-redundant-decls', + ]) + + extra_c_args += extra_args + extra_cpp_args += extra_args +endif + gstd3d11 = library('gstd3d11', d3d11_sources, c_args : gst_plugins_bad_args + extra_c_args, - cpp_args: gst_plugins_bad_args, + cpp_args: gst_plugins_bad_args + extra_cpp_args, include_directories : [configinc], dependencies : [gstbase_dep, gstvideo_dep, gmodule_dep, gstcontroller_dep, d3d11_lib, dxgi_lib] + extra_dep, install : true, diff --git a/sys/d3d11/plugin.c b/sys/d3d11/plugin.c index ee49b7a..8105f7b 100644 --- a/sys/d3d11/plugin.c +++ b/sys/d3d11/plugin.c @@ -39,6 +39,9 @@ #include "gstd3d11vp9dec.h" #include "gstd3d11vp8dec.h" #endif +#ifdef HAVE_DXGI_DESKTOP_DUP +#include "gstd3d11desktopdupsrc.h" +#endif GST_DEBUG_CATEGORY (gst_d3d11_debug); GST_DEBUG_CATEGORY (gst_d3d11_shader_debug); @@ -62,6 +65,10 @@ GST_DEBUG_CATEGORY (gst_d3d11_vp9_dec_debug); GST_DEBUG_CATEGORY (gst_d3d11_vp8_dec_debug); #endif +#ifdef HAVE_DXGI_DESKTOP_DUP +GST_DEBUG_CATEGORY (gst_d3d11_desktop_dup_debug); +#endif + #define GST_CAT_DEFAULT gst_d3d11_debug static gboolean @@ -201,6 +208,15 @@ plugin_init (GstPlugin * plugin) } #endif +#ifdef HAVE_DXGI_DESKTOP_DUP + if (gst_d3d11_is_windows_8_or_greater ()) { + GST_DEBUG_CATEGORY_INIT (gst_d3d11_desktop_dup_debug, + "d3d11desktopdupsrc", 0, "d3d11desktopdupsrc"); + gst_element_register (plugin, + "d3d11desktopdupsrc", GST_RANK_NONE, GST_TYPE_D3D11_DESKTOP_DUP_SRC); + } +#endif + gst_clear_object (&device); return TRUE; -- 2.7.4