mirror of
https://github.com/gnif/LookingGlass.git
synced 2025-10-17 12:58:11 +00:00
[host] add new D12 capture interface
Note, this capture interface is not yet feature complete but does seem to be stable.
This commit is contained in:
23
host/platform/Windows/capture/D12/CMakeLists.txt
Normal file
23
host/platform/Windows/capture/D12/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(capture_D12 LANGUAGES C)
|
||||
|
||||
add_library(capture_D12 STATIC
|
||||
d12.c
|
||||
backend/dd.c
|
||||
)
|
||||
|
||||
add_definitions("-DCOBJMACROS -DINITGUID -DWIDL_C_INLINE_WRAPPERS")
|
||||
|
||||
target_link_libraries(capture_D12
|
||||
lg_common
|
||||
d3d11
|
||||
dxgi
|
||||
dwmapi
|
||||
d3dcompiler
|
||||
)
|
||||
|
||||
target_include_directories(capture_D12
|
||||
PRIVATE
|
||||
.
|
||||
"${PROJECT_TOP}/vendor/directx"
|
||||
)
|
59
host/platform/Windows/capture/D12/backend.h
Normal file
59
host/platform/Windows/capture/D12/backend.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2024 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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 General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_D12_BACKEND_
|
||||
#define _H_D12_BACKEND_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <d3d12.h>
|
||||
#include "interface/capture.h"
|
||||
|
||||
typedef struct D12Backend
|
||||
{
|
||||
// friendly name
|
||||
const char * name;
|
||||
|
||||
// internal name
|
||||
const char * codeName;
|
||||
|
||||
// creation/init/free
|
||||
bool (*create)(unsigned frameBuffers);
|
||||
bool (*init)(
|
||||
bool debug,
|
||||
ID3D12Device3 * device,
|
||||
IDXGIAdapter1 * adapter,
|
||||
IDXGIOutput * output);
|
||||
bool (*deinit)(void);
|
||||
void (*free)(void);
|
||||
|
||||
// capture callbacks
|
||||
CaptureResult (*capture)(unsigned frameBufferIndex);
|
||||
CaptureResult (*sync )(ID3D12CommandQueue * commandQueue);
|
||||
ID3D12Resource * (*fetch )(unsigned frameBufferIndex);
|
||||
}
|
||||
D12Backend;
|
||||
|
||||
// apis for the backend
|
||||
void d12_updatePointer(
|
||||
CapturePointer * pointer, void * shape, size_t shapeSize);
|
||||
|
||||
extern D12Backend D12Backend_DD;
|
||||
|
||||
#endif
|
712
host/platform/Windows/capture/D12/backend/dd.c
Normal file
712
host/platform/Windows/capture/D12/backend/dd.c
Normal file
@@ -0,0 +1,712 @@
|
||||
#include "backend.h"
|
||||
|
||||
#include "com_ref.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/windebug.h"
|
||||
#include "common/array.h"
|
||||
|
||||
#include <d3d11.h>
|
||||
#include <d3d11_4.h>
|
||||
#include <d3d11on12.h>
|
||||
|
||||
#include <dxgi.h>
|
||||
#include <dxgi1_2.h>
|
||||
#include <dxgi1_3.h>
|
||||
#include <dxgi1_5.h>
|
||||
#include <dxgi1_6.h>
|
||||
|
||||
#define CACHE_SIZE 10
|
||||
|
||||
typedef struct DDCacheInfo
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC format;
|
||||
ID3D11Texture2D ** srcTex;
|
||||
ID3D12Resource ** d12Res;
|
||||
ID3D11Fence ** fence;
|
||||
ID3D12Fence ** d12Fence;
|
||||
UINT64 fenceValue;
|
||||
bool ready;
|
||||
}
|
||||
DDCacheInfo;
|
||||
|
||||
struct DDInstance
|
||||
{
|
||||
ComScope * comScope;
|
||||
|
||||
HDESK desktop;
|
||||
|
||||
ID3D12Device3 ** d12device;
|
||||
ID3D11Device5 ** device;
|
||||
ID3D11DeviceContext4 ** context;
|
||||
IDXGIOutputDuplication ** dup;
|
||||
bool release;
|
||||
|
||||
DDCacheInfo cache[CACHE_SIZE];
|
||||
DDCacheInfo * current;
|
||||
|
||||
bool lastPosValid;
|
||||
DXGI_OUTDUPL_POINTER_POSITION lastPos;
|
||||
|
||||
void * shapeBuffer;
|
||||
unsigned shapeBufferSize;
|
||||
};
|
||||
|
||||
struct DDInstance * this = NULL;
|
||||
|
||||
#define comRef_toGlobal(dst, src) \
|
||||
_comRef_toGlobal(this->comScope, dst, src)
|
||||
|
||||
static void d12_dd_openDesktop(void);
|
||||
static bool d12_dd_handleFrameUpdate(IDXGIResource * res);
|
||||
|
||||
static void d12_dd_handlePointerMovement(DXGI_OUTDUPL_POINTER_POSITION * pos,
|
||||
CapturePointer * pointer, bool * changed);
|
||||
static void d12_dd_handlePointerShape(
|
||||
CapturePointer * pointer, size_t size, bool * changed);
|
||||
|
||||
static bool d12_dd_getCache(ID3D11Texture2D * srcTex, DDCacheInfo ** result);
|
||||
static bool d12_dd_convertResource(ID3D11Texture2D * srcTex,
|
||||
DDCacheInfo * cache);
|
||||
|
||||
static bool d12_dd_create(unsigned frameBuffers)
|
||||
{
|
||||
this = calloc(1, sizeof(*this));
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool d12_dd_init(
|
||||
bool debug,
|
||||
ID3D12Device3 * device,
|
||||
IDXGIAdapter1 * adapter,
|
||||
IDXGIOutput * output)
|
||||
{
|
||||
bool result = false;
|
||||
HRESULT hr;
|
||||
|
||||
comRef_initGlobalScope(10 + CACHE_SIZE * 2, this->comScope);
|
||||
comRef_scopePush(10);
|
||||
|
||||
// try to open the desktop so we can capture the secure desktop
|
||||
d12_dd_openDesktop();
|
||||
|
||||
comRef_defineLocal(IDXGIAdapter, _adapter);
|
||||
hr = IDXGIAdapter1_QueryInterface(
|
||||
adapter, &IID_IDXGIAdapter, (void **)_adapter);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_ERROR("Failed to get the IDXGIAdapter interface");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
static const D3D_FEATURE_LEVEL featureLevels[] =
|
||||
{
|
||||
D3D_FEATURE_LEVEL_12_1,
|
||||
D3D_FEATURE_LEVEL_12_0,
|
||||
D3D_FEATURE_LEVEL_11_1,
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
D3D_FEATURE_LEVEL_10_1,
|
||||
D3D_FEATURE_LEVEL_10_0,
|
||||
D3D_FEATURE_LEVEL_9_3,
|
||||
D3D_FEATURE_LEVEL_9_2,
|
||||
D3D_FEATURE_LEVEL_9_1
|
||||
};
|
||||
D3D_FEATURE_LEVEL featureLevel;
|
||||
|
||||
// create a DirectX11 context
|
||||
comRef_defineLocal(ID3D11Device , d11device);
|
||||
comRef_defineLocal(ID3D11DeviceContext, d11context);
|
||||
hr = D3D11CreateDevice(
|
||||
*_adapter,
|
||||
D3D_DRIVER_TYPE_UNKNOWN,
|
||||
NULL,
|
||||
D3D11_CREATE_DEVICE_VIDEO_SUPPORT |
|
||||
(debug ? D3D11_CREATE_DEVICE_DEBUG : 0),
|
||||
featureLevels,
|
||||
ARRAY_LENGTH(featureLevels),
|
||||
D3D11_SDK_VERSION,
|
||||
d11device,
|
||||
&featureLevel,
|
||||
d11context);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create the D3D11Device", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// get the updated interfaces
|
||||
comRef_defineLocal(ID3D11DeviceContext4, d11context4);
|
||||
hr = ID3D11DeviceContext_QueryInterface(
|
||||
*d11context, &IID_ID3D11DeviceContext4, (void **)d11context4);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to get the ID3D11Context4 interface", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
comRef_defineLocal(ID3D11Device5, d11device5);
|
||||
hr = ID3D11Device_QueryInterface(
|
||||
*d11device, &IID_ID3D11Device5, (void **)d11device5);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to get the ID3D11Device5 interface", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// try to reduce the latency
|
||||
comRef_defineLocal(IDXGIDevice1, dxgi1);
|
||||
hr = ID3D11Device_QueryInterface(
|
||||
*d11device, &IID_IDXGIDevice1, (void **)dxgi1);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("failed to query the DXGI interface from the device", hr);
|
||||
goto exit;
|
||||
}
|
||||
IDXGIDevice1_SetMaximumFrameLatency(*dxgi1, 1);
|
||||
|
||||
// duplicate the output
|
||||
comRef_defineLocal(IDXGIOutput5 , output5);
|
||||
comRef_defineLocal(IDXGIOutputDuplication, dup );
|
||||
hr = IDXGIOutput_QueryInterface(output, &IID_IDXGIOutput5, (void **)output5);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WARN("IDXGIOutput5 is not available, "
|
||||
"please update windows for improved performance!");
|
||||
DEBUG_WARN("Falling back to IDXGIOutput1");
|
||||
|
||||
comRef_defineLocal(IDXGIOutput1, output1);
|
||||
hr = IDXGIOutput_QueryInterface(output, &IID_IDXGIOutput1, (void **)output1);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_ERROR("Failed to query IDXGIOutput1 from the output");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// we try this twice in case we still get an error on re-initialization
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
hr = IDXGIOutput1_DuplicateOutput(*output1, *(IUnknown **)d11device, dup);
|
||||
if (SUCCEEDED(hr))
|
||||
break;
|
||||
Sleep(200);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static const DXGI_FORMAT supportedFormats[] =
|
||||
{
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
DXGI_FORMAT_R16G16B16A16_FLOAT
|
||||
};
|
||||
|
||||
// we try this twice in case we still get an error on re-initialization
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
hr = IDXGIOutput5_DuplicateOutput1(
|
||||
*output5,
|
||||
*(IUnknown **)d11device,
|
||||
0,
|
||||
ARRAY_LENGTH(supportedFormats),
|
||||
supportedFormats,
|
||||
dup);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
break;
|
||||
|
||||
// if access is denied we just keep trying until it isn't
|
||||
if (hr == E_ACCESSDENIED)
|
||||
--i;
|
||||
|
||||
Sleep(200);
|
||||
}
|
||||
}
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("DuplicateOutput Failed", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ID3D12Device3_AddRef(device);
|
||||
comRef_toGlobal(this->d12device, &device );
|
||||
comRef_toGlobal(this->device , d11device5 );
|
||||
comRef_toGlobal(this->context , d11context4);
|
||||
comRef_toGlobal(this->dup , dup );
|
||||
result = true;
|
||||
|
||||
exit:
|
||||
comRef_scopePop();
|
||||
if (!result)
|
||||
comRef_freeScope(&this->comScope);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool d12_dd_deinit(void)
|
||||
{
|
||||
if (this->release)
|
||||
{
|
||||
IDXGIOutputDuplication_ReleaseFrame(*this->dup);
|
||||
this->release = false;
|
||||
}
|
||||
|
||||
if (this->desktop)
|
||||
{
|
||||
CloseDesktop(this->desktop);
|
||||
this->desktop = NULL;
|
||||
}
|
||||
|
||||
comRef_freeScope(&this->comScope);
|
||||
memset(this, 0, sizeof(*this));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void d12_dd_free(void)
|
||||
{
|
||||
free(this->shapeBuffer);
|
||||
free(this);
|
||||
this = NULL;
|
||||
}
|
||||
|
||||
static CaptureResult d12_dd_hResultToCaptureResult(const HRESULT status)
|
||||
{
|
||||
switch(status)
|
||||
{
|
||||
case S_OK:
|
||||
return CAPTURE_RESULT_OK;
|
||||
|
||||
case DXGI_ERROR_WAIT_TIMEOUT:
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
case WAIT_ABANDONED:
|
||||
case DXGI_ERROR_ACCESS_LOST:
|
||||
return CAPTURE_RESULT_REINIT;
|
||||
|
||||
default:
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static CaptureResult d12_dd_capture(unsigned frameBufferIndex)
|
||||
{
|
||||
HRESULT hr;
|
||||
CaptureResult result = CAPTURE_RESULT_ERROR;
|
||||
comRef_scopePush(10);
|
||||
|
||||
DXGI_OUTDUPL_FRAME_INFO frameInfo = {0};
|
||||
comRef_defineLocal(IDXGIResource, res);
|
||||
|
||||
retry:
|
||||
if (this->release)
|
||||
{
|
||||
IDXGIOutputDuplication_ReleaseFrame(*this->dup);
|
||||
this->release = false;
|
||||
}
|
||||
|
||||
hr = IDXGIOutputDuplication_AcquireNextFrame(
|
||||
*this->dup, 1000, &frameInfo, res);
|
||||
|
||||
result = d12_dd_hResultToCaptureResult(hr);
|
||||
if (result != CAPTURE_RESULT_OK)
|
||||
{
|
||||
if (result == CAPTURE_RESULT_ERROR)
|
||||
DEBUG_WINERROR("AcquireNextFrame failed", hr);
|
||||
|
||||
if (hr == DXGI_ERROR_ACCESS_LOST)
|
||||
{
|
||||
hr = ID3D11Device5_GetDeviceRemovedReason(*this->device);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Device Removed", hr);
|
||||
result = CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
goto exit;
|
||||
}
|
||||
|
||||
this->release = true;
|
||||
|
||||
// if we have a new frame
|
||||
if (frameInfo.LastPresentTime.QuadPart != 0)
|
||||
if (!d12_dd_handleFrameUpdate(*res))
|
||||
{
|
||||
result = CAPTURE_RESULT_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool postPointer = false;
|
||||
bool postShape = false;
|
||||
CapturePointer pointer = {0};
|
||||
|
||||
// if the pointer has moved
|
||||
if (frameInfo.LastMouseUpdateTime.QuadPart != 0)
|
||||
d12_dd_handlePointerMovement(
|
||||
&frameInfo.PointerPosition, &pointer, &postPointer);
|
||||
|
||||
// if the pointer shape has changed
|
||||
if (frameInfo.PointerShapeBufferSize > 0)
|
||||
d12_dd_handlePointerShape(
|
||||
&pointer, frameInfo.PointerShapeBufferSize, &postShape);
|
||||
|
||||
if (postPointer)
|
||||
d12_updatePointer(&pointer, this->shapeBuffer, this->shapeBufferSize);
|
||||
|
||||
// if this was not a frame update, go back and try again
|
||||
if (frameInfo.LastPresentTime.QuadPart == 0)
|
||||
goto retry;
|
||||
|
||||
exit:
|
||||
comRef_scopePop();
|
||||
return result;
|
||||
}
|
||||
|
||||
static CaptureResult d12_dd_sync(ID3D12CommandQueue * commandQueue)
|
||||
{
|
||||
if (!this->current)
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
DDCacheInfo * cache = this->current;
|
||||
if (ID3D11Fence_GetCompletedValue(*cache->fence) < cache->fenceValue)
|
||||
ID3D12CommandQueue_Wait(commandQueue, *cache->d12Fence, cache->fenceValue);
|
||||
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static ID3D12Resource * d12_dd_fetch(unsigned frameBufferIndex)
|
||||
{
|
||||
if (!this->current)
|
||||
return NULL;
|
||||
|
||||
ID3D12Resource_AddRef(*this->current->d12Res);
|
||||
return *this->current->d12Res;
|
||||
}
|
||||
|
||||
static void d12_dd_openDesktop(void)
|
||||
{
|
||||
this->desktop = OpenInputDesktop(0, FALSE, GENERIC_READ);
|
||||
if (!this->desktop)
|
||||
DEBUG_WINERROR("Failed to open the desktop", GetLastError());
|
||||
else
|
||||
{
|
||||
if (!SetThreadDesktop(this->desktop))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to set the thread desktop", GetLastError());
|
||||
CloseDesktop(this->desktop);
|
||||
this->desktop = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->desktop)
|
||||
{
|
||||
DEBUG_INFO("The above error(s) will prevent LG from being able to capture "
|
||||
"the secure desktop (UAC dialogs)");
|
||||
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
|
||||
DEBUG_INFO("To fix this, install and run the Looking Glass host as a "
|
||||
"service.");
|
||||
DEBUG_INFO("looking-glass-host.exe InstallService");
|
||||
}
|
||||
}
|
||||
|
||||
static bool d12_dd_handleFrameUpdate(IDXGIResource * res)
|
||||
{
|
||||
bool result = false;
|
||||
comRef_scopePush(1);
|
||||
|
||||
comRef_defineLocal(ID3D11Texture2D, srcTex);
|
||||
HRESULT hr = IDXGIResource_QueryInterface(
|
||||
res, &IID_ID3D11Texture2D, (void **)srcTex);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to obtain the D3D11Texture2D interface", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!d12_dd_getCache(*srcTex, &this->current))
|
||||
goto exit;
|
||||
|
||||
/**
|
||||
* Even though we have not performed any copy/draw operations we still need to
|
||||
* use a fence. Because we share this texture with DirectX12 it is able to
|
||||
* read from it before the desktop duplication API has finished updating it.*/
|
||||
++this->current->fenceValue;
|
||||
ID3D11DeviceContext4_Signal(
|
||||
*this->context, *this->current->fence, this->current->fenceValue);
|
||||
|
||||
result = true;
|
||||
|
||||
exit:
|
||||
comRef_scopePop();
|
||||
return result;
|
||||
}
|
||||
|
||||
static void d12_dd_handlePointerMovement(DXGI_OUTDUPL_POINTER_POSITION * pos,
|
||||
CapturePointer * pointer, bool * changed)
|
||||
{
|
||||
bool setPos = false;
|
||||
|
||||
// if the last position is valid, check against it for changes
|
||||
if (this->lastPosValid)
|
||||
{
|
||||
// update the position only if the pointer is visible and it has moved
|
||||
if (pos->Visible && (
|
||||
pos->Position.x != this->lastPos.Position.x ||
|
||||
pos->Position.y != this->lastPos.Position.y))
|
||||
setPos = true;
|
||||
|
||||
// if the visibillity has changed
|
||||
if (pos->Visible != this->lastPos.Visible)
|
||||
*changed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// update the position only if the pointer is visible
|
||||
setPos = pos->Visible;
|
||||
|
||||
// this is the first update, we need to send it
|
||||
*changed = true;
|
||||
}
|
||||
|
||||
pointer->visible = pos->Visible;
|
||||
if (setPos)
|
||||
{
|
||||
pointer->positionUpdate = true;
|
||||
pointer->x = pos->Position.x;
|
||||
pointer->y = pos->Position.y;
|
||||
|
||||
*changed = true;
|
||||
}
|
||||
|
||||
memcpy(&this->lastPos, pos, sizeof(*pos));
|
||||
this->lastPosValid = true;
|
||||
}
|
||||
|
||||
static void d12_dd_handlePointerShape(
|
||||
CapturePointer * pointer, size_t size, bool * changed)
|
||||
{
|
||||
HRESULT hr;
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO info;
|
||||
|
||||
retry:
|
||||
if (this->shapeBufferSize < size)
|
||||
{
|
||||
free(this->shapeBuffer);
|
||||
this->shapeBuffer = malloc(size);
|
||||
if (!this->shapeBuffer)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
this->shapeBufferSize = 0;
|
||||
return;
|
||||
}
|
||||
this->shapeBufferSize = size;
|
||||
}
|
||||
|
||||
UINT s;
|
||||
hr = IDXGIOutputDuplication_GetFramePointerShape(
|
||||
*this->dup,
|
||||
this->shapeBufferSize,
|
||||
this->shapeBuffer,
|
||||
&s,
|
||||
&info);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
if (hr == DXGI_ERROR_MORE_DATA)
|
||||
{
|
||||
size = s;
|
||||
goto retry;
|
||||
}
|
||||
DEBUG_WINERROR("Failed to get the pointer shape", hr);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(info.Type)
|
||||
{
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
|
||||
pointer->format = CAPTURE_FMT_COLOR;
|
||||
break;
|
||||
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
|
||||
pointer->format = CAPTURE_FMT_MASKED;
|
||||
break;
|
||||
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
|
||||
pointer->format = CAPTURE_FMT_MONO;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupporter cursor format");
|
||||
return;
|
||||
}
|
||||
|
||||
pointer->shapeUpdate = true;
|
||||
pointer->width = info.Width;
|
||||
pointer->height = info.Height;
|
||||
pointer->pitch = info.Pitch;
|
||||
pointer->hx = info.HotSpot.x;
|
||||
pointer->hy = info.HotSpot.y;
|
||||
|
||||
*changed = true;
|
||||
}
|
||||
|
||||
static bool d12_dd_getCache(ID3D11Texture2D * srcTex, DDCacheInfo ** result)
|
||||
{
|
||||
*result = NULL;
|
||||
D3D11_TEXTURE2D_DESC srcDesc;
|
||||
ID3D11Texture2D_GetDesc(srcTex, &srcDesc);
|
||||
|
||||
unsigned freeSlot = CACHE_SIZE;
|
||||
for(unsigned i = 0; i < CACHE_SIZE; ++i)
|
||||
{
|
||||
DDCacheInfo * cache = &this->cache[i];
|
||||
if (!cache->ready)
|
||||
{
|
||||
freeSlot = min(freeSlot, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for a resource match
|
||||
if (*cache->srcTex != srcTex)
|
||||
continue;
|
||||
|
||||
// check if the match is not valid
|
||||
if (cache->format.Width != srcDesc.Width ||
|
||||
cache->format.Height != srcDesc.Height ||
|
||||
cache->format.Format != srcDesc.Format)
|
||||
{
|
||||
// break out and allow this entry to be rebuilt
|
||||
cache->ready = false;
|
||||
freeSlot = i;
|
||||
break;
|
||||
}
|
||||
|
||||
// found, so return it
|
||||
*result = cache;
|
||||
return true;
|
||||
}
|
||||
|
||||
// cache is full
|
||||
if (freeSlot == CACHE_SIZE)
|
||||
return false;
|
||||
|
||||
// convert the resource
|
||||
if (!d12_dd_convertResource(srcTex, &this->cache[freeSlot]))
|
||||
return false;
|
||||
|
||||
// return the new cache entry
|
||||
*result = &this->cache[freeSlot];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool d12_dd_convertResource(ID3D11Texture2D * srcTex, DDCacheInfo * cache)
|
||||
{
|
||||
bool result = false;
|
||||
HRESULT hr;
|
||||
comRef_scopePush(10);
|
||||
|
||||
D3D11_TEXTURE2D_DESC srcDesc;
|
||||
ID3D11Texture2D_GetDesc(srcTex, &srcDesc);
|
||||
|
||||
// get the DXGI resource interface so we can create the shared handle
|
||||
comRef_defineLocal(IDXGIResource1, dxgiRes);
|
||||
hr = ID3D11Texture2D_QueryInterface(
|
||||
srcTex, &IID_IDXGIResource1, (void **)dxgiRes);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to obtain the shared ID3D11Resource1 interface", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// create the shared handle
|
||||
HANDLE sharedHandle;
|
||||
hr = IDXGIResource1_CreateSharedHandle(
|
||||
*dxgiRes, NULL, DXGI_SHARED_RESOURCE_READ, NULL, &sharedHandle);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create the shared handle", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// open the resource as a DirectX12 resource
|
||||
comRef_defineLocal(ID3D12Resource, dst);
|
||||
hr = ID3D12Device3_OpenSharedHandle(
|
||||
*this->d12device, sharedHandle, &IID_ID3D12Resource, (void **)dst);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to open the D3D12Resource from the handle", hr);
|
||||
CloseHandle(sharedHandle);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// close the shared handle
|
||||
CloseHandle(sharedHandle);
|
||||
|
||||
// create the sync fence
|
||||
comRef_defineLocal(ID3D11Fence, fence);
|
||||
hr = ID3D11Device5_CreateFence(
|
||||
*this->device, 0, D3D11_FENCE_FLAG_SHARED, &IID_ID3D11Fence, (void **)fence);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create the fence", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// create the fence shared handle
|
||||
hr = ID3D11Fence_CreateSharedHandle(
|
||||
*fence, NULL, GENERIC_ALL, NULL, &sharedHandle);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create the fence shared handle", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// open the fence as a DirectX12 fence
|
||||
comRef_defineLocal(ID3D12Fence, d12Fence);
|
||||
hr = ID3D12Device3_OpenSharedHandle(
|
||||
*this->d12device, sharedHandle, &IID_ID3D12Fence, (void **)d12Fence);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to open the D3D12Fence from the handle", hr);
|
||||
CloseHandle(sharedHandle);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// close the shared handle
|
||||
CloseHandle(sharedHandle);
|
||||
|
||||
// store the details
|
||||
ID3D11Texture2D_AddRef(srcTex);
|
||||
comRef_toGlobal(cache->srcTex , &srcTex );
|
||||
comRef_toGlobal(cache->d12Res , dst );
|
||||
comRef_toGlobal(cache->fence , fence );
|
||||
comRef_toGlobal(cache->d12Fence, d12Fence);
|
||||
memcpy(&cache->format, &srcDesc, sizeof(srcDesc));
|
||||
cache->fenceValue = 0;
|
||||
cache->ready = true;
|
||||
|
||||
result = true;
|
||||
exit:
|
||||
comRef_scopePop();
|
||||
return result;
|
||||
}
|
||||
|
||||
D12Backend D12Backend_DD =
|
||||
{
|
||||
.name = "Desktop Duplication",
|
||||
.codeName = "DD",
|
||||
|
||||
.create = d12_dd_create,
|
||||
.init = d12_dd_init,
|
||||
.deinit = d12_dd_deinit,
|
||||
.free = d12_dd_free,
|
||||
.capture = d12_dd_capture,
|
||||
.sync = d12_dd_sync,
|
||||
.fetch = d12_dd_fetch
|
||||
};
|
743
host/platform/Windows/capture/D12/d12.c
Normal file
743
host/platform/Windows/capture/D12/d12.c
Normal file
@@ -0,0 +1,743 @@
|
||||
#include "interface/capture.h"
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/windebug.h"
|
||||
#include "com_ref.h"
|
||||
|
||||
#include "backend.h"
|
||||
|
||||
#include <dxgi.h>
|
||||
#include <dxgi1_3.h>
|
||||
#include <d3dcommon.h>
|
||||
|
||||
// definitions
|
||||
|
||||
typedef HRESULT (*D3D12CreateDevice_t)(
|
||||
IUnknown *pAdapter,
|
||||
D3D_FEATURE_LEVEL MinimumFeatureLevel,
|
||||
REFIID riid,
|
||||
void **ppDevice
|
||||
);
|
||||
|
||||
typedef HRESULT (*D3D12GetDebugInterface_t)(
|
||||
REFIID riid,
|
||||
void **ppvDebug
|
||||
);
|
||||
|
||||
typedef struct D12CommandGroup
|
||||
{
|
||||
ID3D12CommandAllocator ** allocator;
|
||||
ID3D12GraphicsCommandList ** gfxList;
|
||||
ID3D12CommandList ** cmdList;
|
||||
ID3D12Fence ** fence;
|
||||
HANDLE event;
|
||||
UINT64 fenceValue;
|
||||
}
|
||||
D12CommandGroup;
|
||||
|
||||
struct D12Interface
|
||||
{
|
||||
ComScope * comScope;
|
||||
|
||||
HMODULE d3d12;
|
||||
D3D12CreateDevice_t D3D12CreateDevice;
|
||||
D3D12GetDebugInterface_t D3D12GetDebugInterface;
|
||||
|
||||
IDXGIFactory2 ** factory;
|
||||
ID3D12Device3 ** device;
|
||||
|
||||
ID3D12CommandQueue ** commandQueue;
|
||||
D12CommandGroup copyCommand;
|
||||
|
||||
void * ivshmemBase;
|
||||
ID3D12Heap ** ivshmemHeap;
|
||||
|
||||
CaptureGetPointerBuffer getPointerBufferFn;
|
||||
CapturePostPointerBuffer postPointerBufferFn;
|
||||
|
||||
D12Backend * backend;
|
||||
|
||||
// capture format tracking
|
||||
D3D12_RESOURCE_DESC lastFormat;
|
||||
unsigned formatVer;
|
||||
|
||||
// options
|
||||
bool debug;
|
||||
|
||||
// must be last
|
||||
struct
|
||||
{
|
||||
// the size of the frame buffer
|
||||
unsigned size;
|
||||
// the frame buffer it itself
|
||||
FrameBuffer * frameBuffer;
|
||||
// the resource backed by the framebuffer
|
||||
ID3D12Resource ** resource;
|
||||
}
|
||||
frameBuffers[0];
|
||||
};
|
||||
|
||||
// defines
|
||||
|
||||
#define comRef_toGlobal(dst, src) \
|
||||
_comRef_toGlobal(this->comScope, dst, src)
|
||||
|
||||
// locals
|
||||
|
||||
static struct D12Interface * this = NULL;
|
||||
|
||||
// forwards
|
||||
|
||||
static bool d12_enumerateDevices(
|
||||
IDXGIFactory2 ** factory,
|
||||
IDXGIAdapter1 ** adapter,
|
||||
IDXGIOutput ** output);
|
||||
|
||||
static bool d12_createCommandGroup(
|
||||
ID3D12Device3 * device,
|
||||
D3D12_COMMAND_LIST_TYPE type,
|
||||
D12CommandGroup * dst,
|
||||
LPCWSTR name);
|
||||
|
||||
static void d12_freeCommandGroup(
|
||||
D12CommandGroup * grp);
|
||||
|
||||
static bool d12_executeCommandGroup(
|
||||
D12CommandGroup * grp);
|
||||
|
||||
static ID3D12Resource * d12_frameBufferToResource(
|
||||
unsigned frameBufferIndex,
|
||||
FrameBuffer * frameBuffer,
|
||||
unsigned size);
|
||||
|
||||
// implementation
|
||||
|
||||
static const char * d12_getName(void)
|
||||
{
|
||||
return "D12";
|
||||
}
|
||||
|
||||
static void d12_initOptions(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool d12_create(
|
||||
CaptureGetPointerBuffer getPointerBufferFn,
|
||||
CapturePostPointerBuffer postPointerBufferFn,
|
||||
unsigned frameBuffers)
|
||||
{
|
||||
this = calloc(1, offsetof(struct D12Interface, frameBuffers) +
|
||||
sizeof(this->frameBuffers[0]) * frameBuffers);
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("failed to allocate D12Interface struct");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->debug = false;
|
||||
this->d3d12 = LoadLibrary("d3d12.dll");
|
||||
if (!this->d3d12)
|
||||
{
|
||||
DEBUG_ERROR("failed to load d3d12.dll");
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->D3D12CreateDevice = (D3D12CreateDevice_t)
|
||||
GetProcAddress(this->d3d12, "D3D12CreateDevice");
|
||||
|
||||
this->D3D12GetDebugInterface = (D3D12GetDebugInterface_t)
|
||||
GetProcAddress(this->d3d12, "D3D12GetDebugInterface");
|
||||
|
||||
this->getPointerBufferFn = getPointerBufferFn;
|
||||
this->postPointerBufferFn = postPointerBufferFn;
|
||||
|
||||
this->backend = &D12Backend_DD;
|
||||
if (!this->backend->create(frameBuffers))
|
||||
{
|
||||
DEBUG_ERROR("backend \"%s\" failed to create", this->backend->codeName);
|
||||
CloseHandle(this->d3d12);
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool d12_init(void * ivshmemBase, unsigned * alignSize)
|
||||
{
|
||||
bool result = false;
|
||||
comRef_initGlobalScope(100, this->comScope);
|
||||
comRef_scopePush(10);
|
||||
|
||||
// create a DXGI factory
|
||||
comRef_defineLocal(IDXGIFactory2, factory);
|
||||
HRESULT hr = CreateDXGIFactory2(
|
||||
this->debug ? DXGI_CREATE_FACTORY_DEBUG : 0,
|
||||
&IID_IDXGIFactory2,
|
||||
(void **)factory);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create the DXGI factory", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// find the adapter and output we want to use
|
||||
comRef_defineLocal(IDXGIAdapter1, adapter);
|
||||
comRef_defineLocal(IDXGIOutput , output );
|
||||
if (!d12_enumerateDevices(factory, adapter, output))
|
||||
goto exit;
|
||||
|
||||
if (this->debug)
|
||||
{
|
||||
comRef_defineLocal(ID3D12Debug1, debug);
|
||||
hr = this->D3D12GetDebugInterface(&IID_ID3D12Debug1, (void **)debug);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("D3D12GetDebugInterface", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ID3D12Debug1_EnableDebugLayer(*debug);
|
||||
ID3D12Debug1_SetEnableGPUBasedValidation(*debug, TRUE);
|
||||
ID3D12Debug1_SetEnableSynchronizedCommandQueueValidation(*debug, TRUE);
|
||||
}
|
||||
|
||||
// create the D3D12 device
|
||||
comRef_defineLocal(ID3D12Device3, device);
|
||||
hr = this->D3D12CreateDevice(
|
||||
(IUnknown *)*adapter,
|
||||
D3D_FEATURE_LEVEL_12_0,
|
||||
&IID_ID3D12Device3,
|
||||
(void **)device);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create the DirectX12 device", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* make this static as we downgrade the priority on failure and we want to
|
||||
remember it */
|
||||
static D3D12_COMMAND_QUEUE_DESC queueDesc =
|
||||
{
|
||||
.Type = D3D12_COMMAND_LIST_TYPE_COPY,
|
||||
.Priority = D3D12_COMMAND_QUEUE_PRIORITY_GLOBAL_REALTIME,
|
||||
.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE,
|
||||
};
|
||||
|
||||
comRef_defineLocal(ID3D12CommandQueue, commandQueue);
|
||||
retryCreateCommandQueue:
|
||||
hr = ID3D12Device3_CreateCommandQueue(
|
||||
*device, &queueDesc, &IID_ID3D12CommandQueue, (void **)commandQueue);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
if (queueDesc.Priority == D3D12_COMMAND_QUEUE_PRIORITY_GLOBAL_REALTIME)
|
||||
{
|
||||
DEBUG_WARN("Failed to create queue with real time priority");
|
||||
queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_HIGH;
|
||||
goto retryCreateCommandQueue;
|
||||
}
|
||||
|
||||
DEBUG_WINERROR("Failed to create ID3D12CommandQueue", hr);
|
||||
goto exit;
|
||||
}
|
||||
ID3D12CommandQueue_SetName(*commandQueue, L"Command Queue");
|
||||
|
||||
if (!d12_createCommandGroup(
|
||||
*device, D3D12_COMMAND_LIST_TYPE_COPY, &this->copyCommand, L"Copy"))
|
||||
goto exit;
|
||||
|
||||
// Create the IVSHMEM heap
|
||||
this->ivshmemBase = ivshmemBase;
|
||||
comRef_defineLocal(ID3D12Heap, ivshmemHeap);
|
||||
hr = ID3D12Device3_OpenExistingHeapFromAddress(
|
||||
*device, ivshmemBase, &IID_ID3D12Heap, (void **)ivshmemHeap);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to open the framebuffer as a D3D12Heap", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// Adjust the alignSize based on the required heap alignment
|
||||
D3D12_HEAP_DESC heapDesc = ID3D12Heap_GetDesc(*ivshmemHeap);
|
||||
*alignSize = heapDesc.Alignment;
|
||||
|
||||
// initialize the backend
|
||||
if (!this->backend->init(this->debug, *device, *adapter, *output))
|
||||
goto exit;
|
||||
|
||||
comRef_toGlobal(this->factory , factory );
|
||||
comRef_toGlobal(this->device , device );
|
||||
comRef_toGlobal(this->commandQueue, commandQueue);
|
||||
comRef_toGlobal(this->ivshmemHeap , ivshmemHeap );
|
||||
|
||||
result = true;
|
||||
|
||||
exit:
|
||||
comRef_scopePop();
|
||||
if (!result)
|
||||
comRef_freeScope(&this->comScope);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void d12_stop(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool d12_deinit(void)
|
||||
{
|
||||
bool result = true;
|
||||
if (!this->backend->deinit())
|
||||
result = false;
|
||||
|
||||
d12_freeCommandGroup(&this->copyCommand);
|
||||
comRef_freeScope(&this->comScope);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void d12_free(void)
|
||||
{
|
||||
this->backend->free();
|
||||
FreeLibrary(this->d3d12);
|
||||
free(this);
|
||||
this = NULL;
|
||||
}
|
||||
|
||||
static CaptureResult d12_capture(
|
||||
unsigned frameBufferIndex, FrameBuffer * frameBuffer)
|
||||
{
|
||||
return this->backend->capture(frameBufferIndex);
|
||||
}
|
||||
|
||||
static CaptureResult d12_waitFrame(unsigned frameBufferIndex,
|
||||
CaptureFrame * frame, const size_t maxFrameSize)
|
||||
{
|
||||
CaptureResult result = CAPTURE_RESULT_ERROR;
|
||||
comRef_scopePush(1);
|
||||
|
||||
comRef_defineLocal(ID3D12Resource, src);
|
||||
*src = this->backend->fetch(frameBufferIndex);
|
||||
if (!*src)
|
||||
{
|
||||
DEBUG_ERROR("D12 backend failed to produce an expected frame: %u",
|
||||
frameBufferIndex);
|
||||
result = CAPTURE_RESULT_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
D3D12_RESOURCE_DESC desc = ID3D12Resource_GetDesc(*src);
|
||||
if (desc.Width != this->lastFormat.Width ||
|
||||
desc.Height != this->lastFormat.Height ||
|
||||
desc.Format != this->lastFormat.Format)
|
||||
{
|
||||
++this->formatVer;
|
||||
memcpy(&this->lastFormat, &desc, sizeof(desc));
|
||||
}
|
||||
|
||||
const unsigned int maxRows = maxFrameSize / (desc.Width * 4);
|
||||
|
||||
frame->formatVer = this->formatVer;
|
||||
frame->screenWidth = desc.Width;
|
||||
frame->screenHeight = desc.Height;
|
||||
frame->dataWidth = desc.Width;
|
||||
frame->dataHeight = min(maxRows, desc.Height);
|
||||
frame->frameWidth = desc.Width;
|
||||
frame->frameHeight = desc.Height;
|
||||
frame->truncated = maxRows < desc.Height;
|
||||
frame->pitch = desc.Width * 4;
|
||||
frame->stride = desc.Width;
|
||||
frame->format = CAPTURE_FMT_BGRA;
|
||||
frame->hdr = false;
|
||||
frame->hdrPQ = false;
|
||||
frame->rotation = CAPTURE_ROT_0;
|
||||
frame->damageRectsCount = 0;
|
||||
|
||||
result = CAPTURE_RESULT_OK;
|
||||
|
||||
exit:
|
||||
comRef_scopePop();
|
||||
return result;
|
||||
}
|
||||
|
||||
static CaptureResult d12_getFrame(unsigned frameBufferIndex,
|
||||
FrameBuffer * frameBuffer, const size_t maxFrameSize)
|
||||
{
|
||||
CaptureResult result = CAPTURE_RESULT_ERROR;
|
||||
comRef_scopePush(2);
|
||||
|
||||
comRef_defineLocal(ID3D12Resource, src);
|
||||
*src = this->backend->fetch(frameBufferIndex);
|
||||
if (!*src)
|
||||
{
|
||||
DEBUG_ERROR("D12 backend failed to produce an expected frame: %u",
|
||||
frameBufferIndex);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
comRef_defineLocal(ID3D12Resource, dst)
|
||||
*dst = d12_frameBufferToResource(frameBufferIndex, frameBuffer, maxFrameSize);
|
||||
if (!*dst)
|
||||
goto exit;
|
||||
|
||||
// copy into the framebuffer resource
|
||||
D3D12_RESOURCE_DESC desc = ID3D12Resource_GetDesc(*src);
|
||||
D3D12_TEXTURE_COPY_LOCATION srcLoc =
|
||||
{
|
||||
.pResource = *src,
|
||||
.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
||||
.SubresourceIndex = 0
|
||||
};
|
||||
|
||||
D3D12_TEXTURE_COPY_LOCATION dstLoc =
|
||||
{
|
||||
.pResource = *dst,
|
||||
.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
|
||||
.PlacedFootprint =
|
||||
{
|
||||
.Offset = 0,
|
||||
.Footprint =
|
||||
{
|
||||
.Format = desc.Format,
|
||||
.Width = desc.Width,
|
||||
.Height = desc.Height,
|
||||
.Depth = 1,
|
||||
.RowPitch = desc.Width * 4
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ID3D12GraphicsCommandList_CopyTextureRegion(
|
||||
*this->copyCommand.gfxList, &dstLoc, 0, 0, 0, &srcLoc, NULL);
|
||||
|
||||
// allow the backend to insert a fence into the command queue if it needs it
|
||||
result = this->backend->sync(*this->commandQueue);
|
||||
if (result != CAPTURE_RESULT_OK)
|
||||
goto exit;
|
||||
|
||||
d12_executeCommandGroup(&this->copyCommand);
|
||||
|
||||
framebuffer_set_write_ptr(frameBuffer, desc.Height * desc.Width * 4);
|
||||
result = CAPTURE_RESULT_OK;
|
||||
|
||||
exit:
|
||||
comRef_scopePop();
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool d12_enumerateDevices(
|
||||
IDXGIFactory2 ** factory,
|
||||
IDXGIAdapter1 ** adapter,
|
||||
IDXGIOutput ** output)
|
||||
{
|
||||
DXGI_ADAPTER_DESC1 adapterDesc;
|
||||
DXGI_OUTPUT_DESC outputDesc;
|
||||
|
||||
for(
|
||||
int i = 0;
|
||||
IDXGIFactory2_EnumAdapters1(*factory, i, adapter)
|
||||
!= DXGI_ERROR_NOT_FOUND;
|
||||
++i, comRef_release(adapter))
|
||||
{
|
||||
HRESULT hr = IDXGIAdapter1_GetDesc1(*adapter, &adapterDesc);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to get the device description", hr);
|
||||
comRef_release(adapter);
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for devices without D3D support
|
||||
static const UINT blacklist[][2] =
|
||||
{
|
||||
//VID , PID
|
||||
{0x1414, 0x008c}, // Microsoft Basic Render Driver
|
||||
{0x1b36, 0x000d}, // QXL
|
||||
{0x1234, 0x1111} // QEMU Standard VGA
|
||||
};
|
||||
|
||||
bool skip = false;
|
||||
for(int n = 0; n < ARRAY_LENGTH(blacklist); ++n)
|
||||
{
|
||||
if (adapterDesc.VendorId == blacklist[n][0] &&
|
||||
adapterDesc.DeviceId == blacklist[n][1])
|
||||
{
|
||||
DEBUG_INFO("Not using unsupported adapter: %ls",
|
||||
adapterDesc.Description);
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skip)
|
||||
continue;
|
||||
|
||||
// FIXME: Allow specifying the specific adapter
|
||||
|
||||
for(
|
||||
int n = 0;
|
||||
IDXGIAdapter1_EnumOutputs(*adapter, n, output) != DXGI_ERROR_NOT_FOUND;
|
||||
++n, comRef_release(output))
|
||||
{
|
||||
IDXGIOutput_GetDesc(*output, &outputDesc);
|
||||
// FIXME: Allow specifying the specific output
|
||||
|
||||
if (outputDesc.AttachedToDesktop)
|
||||
break;
|
||||
}
|
||||
|
||||
if (*output)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!*output)
|
||||
{
|
||||
DEBUG_ERROR("Failed to locate a valid output device");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Device Name : %ls" , outputDesc.DeviceName);
|
||||
DEBUG_INFO("Device Description: %ls" , adapterDesc.Description);
|
||||
DEBUG_INFO("Device Vendor ID : 0x%x" , adapterDesc.VendorId);
|
||||
DEBUG_INFO("Device Device ID : 0x%x" , adapterDesc.DeviceId);
|
||||
DEBUG_INFO("Device Video Mem : %u MiB" ,
|
||||
(unsigned)(adapterDesc.DedicatedVideoMemory / 1048576));
|
||||
DEBUG_INFO("Device Sys Mem : %u MiB" ,
|
||||
(unsigned)(adapterDesc.DedicatedSystemMemory / 1048576));
|
||||
DEBUG_INFO("Shared Sys Mem : %u MiB" ,
|
||||
(unsigned)(adapterDesc.SharedSystemMemory / 1048576));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool d12_createCommandGroup(
|
||||
ID3D12Device3 * device,
|
||||
D3D12_COMMAND_LIST_TYPE type,
|
||||
D12CommandGroup * dst,
|
||||
LPCWSTR name)
|
||||
{
|
||||
bool result = false;
|
||||
HRESULT hr;
|
||||
comRef_scopePush(10);
|
||||
|
||||
comRef_defineLocal(ID3D12CommandAllocator, allocator);
|
||||
hr = ID3D12Device3_CreateCommandAllocator(
|
||||
device,
|
||||
type,
|
||||
&IID_ID3D12CommandAllocator,
|
||||
(void **)allocator);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the ID3D12CommandAllocator");
|
||||
goto exit;
|
||||
}
|
||||
ID3D12CommandAllocator_SetName(*allocator, name);
|
||||
|
||||
comRef_defineLocal(ID3D12GraphicsCommandList, gfxList);
|
||||
hr = ID3D12Device3_CreateCommandList(
|
||||
device,
|
||||
0,
|
||||
type,
|
||||
*allocator,
|
||||
NULL,
|
||||
&IID_ID3D12GraphicsCommandList,
|
||||
(void **)gfxList);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create ID3D12GraphicsCommandList", hr);
|
||||
goto exit;
|
||||
}
|
||||
ID3D12GraphicsCommandList_SetName(*gfxList, name);
|
||||
|
||||
comRef_defineLocal(ID3D12CommandList, cmdList);
|
||||
hr = ID3D12GraphicsCommandList_QueryInterface(
|
||||
*gfxList, &IID_ID3D12CommandList, (void **)cmdList);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to query the ID3D12CommandList interface", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
comRef_defineLocal(ID3D12Fence, fence);
|
||||
hr = ID3D12Device3_CreateFence(
|
||||
device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, (void **)fence);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create ID3D12Fence", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// Create the completion event for the fence
|
||||
HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if (!event)
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create the completion event", GetLastError());
|
||||
goto exit;
|
||||
}
|
||||
|
||||
comRef_toGlobal(dst->allocator, allocator);
|
||||
comRef_toGlobal(dst->gfxList , gfxList );
|
||||
comRef_toGlobal(dst->cmdList , cmdList );
|
||||
comRef_toGlobal(dst->fence , fence );
|
||||
dst->event = event;
|
||||
dst->fenceValue = 0;
|
||||
|
||||
result = true;
|
||||
|
||||
exit:
|
||||
comRef_scopePop();
|
||||
return result;
|
||||
}
|
||||
|
||||
static void d12_freeCommandGroup(
|
||||
D12CommandGroup * grp)
|
||||
{
|
||||
// com objet release is handled by comRef, but the handle is not
|
||||
if (grp->event)
|
||||
{
|
||||
CloseHandle(grp->event);
|
||||
grp->event = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool d12_executeCommandGroup(
|
||||
D12CommandGroup * grp)
|
||||
{
|
||||
HRESULT hr = ID3D12GraphicsCommandList_Close(*grp->gfxList);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to close the command list", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
ID3D12CommandQueue_ExecuteCommandLists(
|
||||
*this->commandQueue, 1, grp->cmdList);
|
||||
|
||||
hr = ID3D12CommandQueue_Signal(
|
||||
*this->commandQueue, *grp->fence, ++grp->fenceValue);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to set the fence signal", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ID3D12Fence_GetCompletedValue(*grp->fence) < grp->fenceValue)
|
||||
{
|
||||
ID3D12Fence_SetEventOnCompletion(*grp->fence, grp->fenceValue, grp->event);
|
||||
WaitForSingleObject(grp->event, INFINITE);
|
||||
}
|
||||
|
||||
hr = ID3D12CommandAllocator_Reset(*grp->allocator);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to reset the command allocator", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = ID3D12GraphicsCommandList_Reset(*grp->gfxList, *grp->allocator, NULL);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to reset the graphics command list", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static ID3D12Resource * d12_frameBufferToResource(unsigned frameBufferIndex,
|
||||
FrameBuffer * frameBuffer, unsigned size)
|
||||
{
|
||||
ID3D12Resource * result = NULL;
|
||||
comRef_scopePush(10);
|
||||
|
||||
typeof(this->frameBuffers[0]) * fb = &this->frameBuffers[frameBufferIndex];
|
||||
|
||||
// nothing to do if the resource is already setup and is big enough
|
||||
if (fb->resource && fb->frameBuffer == frameBuffer && fb->size >= size)
|
||||
{
|
||||
result = *fb->resource;
|
||||
ID3D12Resource_AddRef(result);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
fb->size = size;
|
||||
fb->frameBuffer = frameBuffer;
|
||||
|
||||
D3D12_RESOURCE_DESC desc =
|
||||
{
|
||||
.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
|
||||
.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT,
|
||||
.Width = size,
|
||||
.Height = 1,
|
||||
.DepthOrArraySize = 1,
|
||||
.MipLevels = 1,
|
||||
.Format = DXGI_FORMAT_UNKNOWN,
|
||||
.SampleDesc.Count = 1,
|
||||
.SampleDesc.Quality = 0,
|
||||
.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
|
||||
.Flags = D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER
|
||||
};
|
||||
|
||||
comRef_defineLocal(ID3D12Resource, resource);
|
||||
HRESULT hr = ID3D12Device3_CreatePlacedResource(
|
||||
*this->device,
|
||||
*this->ivshmemHeap,
|
||||
(uintptr_t)framebuffer_get_data(frameBuffer) - (uintptr_t)this->ivshmemBase,
|
||||
&desc,
|
||||
D3D12_RESOURCE_STATE_COPY_DEST,
|
||||
NULL,
|
||||
&IID_ID3D12Resource,
|
||||
(void **)resource);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create the FrameBuffer ID3D12Resource", hr);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// cache the resource
|
||||
comRef_toGlobal(fb->resource, resource);
|
||||
result = *fb->resource;
|
||||
ID3D12Resource_AddRef(result);
|
||||
|
||||
exit:
|
||||
comRef_scopePop();
|
||||
return result;
|
||||
}
|
||||
|
||||
void d12_updatePointer(CapturePointer * pointer, void * shape, size_t shapeSize)
|
||||
{
|
||||
if (pointer->shapeUpdate)
|
||||
{
|
||||
void * dst;
|
||||
UINT dstSize;
|
||||
if (!this->getPointerBufferFn(&dst, &dstSize))
|
||||
{
|
||||
DEBUG_ERROR("Failed to obtain a buffer for the pointer shape");
|
||||
pointer->shapeUpdate = false;
|
||||
}
|
||||
|
||||
size_t copySize = min(dstSize, shapeSize);
|
||||
memcpy(dst, shape, copySize);
|
||||
}
|
||||
|
||||
this->postPointerBufferFn(pointer);
|
||||
}
|
||||
|
||||
struct CaptureInterface Capture_D12 =
|
||||
{
|
||||
.shortName = "D12",
|
||||
.asyncCapture = false,
|
||||
.getName = d12_getName,
|
||||
.initOptions = d12_initOptions,
|
||||
.create = d12_create,
|
||||
.init = d12_init,
|
||||
.stop = d12_stop,
|
||||
.deinit = d12_deinit,
|
||||
.free = d12_free,
|
||||
.capture = d12_capture,
|
||||
.waitFrame = d12_waitFrame,
|
||||
.getFrame = d12_getFrame
|
||||
};
|
Reference in New Issue
Block a user