[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:
Geoffrey McRae
2024-01-29 21:47:02 +11:00
parent e376e6fb53
commit 72b25b99bc
20 changed files with 1919 additions and 352 deletions

View 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"
)

View 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

View 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
};

View 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
};