mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-24 14:27:20 +00:00
989fe2bb0b
AMD GPUs and older NVidia GPUs can initialize fine but fail when we start to create resources in the shared memory heap, we must test it early to detect this so we can fallback to a working capture method.
1071 lines
29 KiB
C
1071 lines
29 KiB
C
/**
|
|
* 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
|
|
*/
|
|
|
|
#include "d12.h"
|
|
|
|
#include "interface/capture.h"
|
|
|
|
#include "common/array.h"
|
|
#include "common/debug.h"
|
|
#include "common/windebug.h"
|
|
#include "common/option.h"
|
|
#include "common/rects.h"
|
|
#include "common/vector.h"
|
|
#include "common/display.h"
|
|
#include "com_ref.h"
|
|
|
|
#include "backend.h"
|
|
#include "effects.h"
|
|
#include "command_group.h"
|
|
|
|
#include <dxgi.h>
|
|
#include <dxgi1_3.h>
|
|
#include <dxgi1_6.h>
|
|
#include <d3dcommon.h>
|
|
|
|
// definitions
|
|
struct D12Interface
|
|
{
|
|
HMODULE d3d12;
|
|
|
|
IDXGIFactory2 ** factory;
|
|
ID3D12Device3 ** device;
|
|
|
|
DISPLAYCONFIG_PATH_INFO displayPathInfo;
|
|
|
|
ID3D12CommandQueue ** copyQueue;
|
|
ID3D12CommandQueue ** computeQueue;
|
|
D12CommandGroup copyCommand;
|
|
D12CommandGroup computeCommand;
|
|
|
|
void * ivshmemBase;
|
|
ID3D12Heap ** ivshmemHeap;
|
|
|
|
CaptureGetPointerBuffer getPointerBufferFn;
|
|
CapturePostPointerBuffer postPointerBufferFn;
|
|
|
|
D12Backend * backend;
|
|
Vector effects;
|
|
bool effectsActive;
|
|
|
|
// capture format tracking
|
|
D12FrameFormat captureFormat;
|
|
unsigned formatVer;
|
|
unsigned pitch;
|
|
|
|
// output format tracking
|
|
D12FrameFormat dstFormat;
|
|
|
|
// prior frame dirty rects
|
|
RECT dirtyRects[D12_MAX_DIRTY_RECTS];
|
|
unsigned nbDirtyRects;
|
|
|
|
// options
|
|
bool debug;
|
|
bool trackDamage;
|
|
|
|
unsigned frameBufferCount;
|
|
// 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];
|
|
};
|
|
|
|
// gloabls
|
|
|
|
struct DX12 DX12 = {0};
|
|
ComScope * d12_comScope = NULL;
|
|
|
|
// defines
|
|
|
|
// locals
|
|
|
|
static struct D12Interface * this = NULL;
|
|
|
|
// forwards
|
|
|
|
static bool d12_enumerateDevices(
|
|
IDXGIFactory2 ** factory,
|
|
IDXGIAdapter1 ** adapter,
|
|
IDXGIOutput ** output);
|
|
|
|
static bool d12_heapTest(ID3D12Device3 * device, ID3D12Heap * heap);
|
|
|
|
static ID3D12Resource * d12_frameBufferToResource(
|
|
unsigned frameBufferIndex,
|
|
FrameBuffer * frameBuffer,
|
|
unsigned size);
|
|
|
|
// implementation
|
|
|
|
static const char * d12_getName(void)
|
|
{
|
|
return "D12";
|
|
}
|
|
|
|
static void d12_initOptions(void)
|
|
{
|
|
struct Option options[] =
|
|
{
|
|
{
|
|
.module = "d12",
|
|
.name = "adapter",
|
|
.description = "The name of the adapter to capture",
|
|
.type = OPTION_TYPE_STRING,
|
|
.value.x_string = NULL
|
|
},
|
|
{
|
|
.module = "d12",
|
|
.name = "output",
|
|
.description = "The name of the adapter's output to capture",
|
|
.type = OPTION_TYPE_STRING,
|
|
.value.x_string = NULL
|
|
},
|
|
{
|
|
.module = "d12",
|
|
.name = "trackDamage",
|
|
.description = "Perform damage-aware copies (saves bandwidth)",
|
|
.type = OPTION_TYPE_BOOL,
|
|
.value.x_bool = true
|
|
},
|
|
{
|
|
.module = "d12",
|
|
.name = "debug",
|
|
.description = "Enable DirectX12 debugging and validation (SLOW!)",
|
|
.type = OPTION_TYPE_BOOL,
|
|
.value.x_bool = false
|
|
},
|
|
{0}
|
|
};
|
|
|
|
option_register(options);
|
|
|
|
for(const D12Effect ** effect = D12Effects; *effect; ++effect)
|
|
d12_effectInitOptions(*effect);
|
|
}
|
|
|
|
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 = option_get_bool("d12", "debug" );
|
|
this->trackDamage = option_get_bool("d12", "trackDamage" );
|
|
|
|
DEBUG_INFO(
|
|
"debug:%d trackDamage:%d",
|
|
this->debug, this->trackDamage);
|
|
|
|
this->d3d12 = LoadLibrary("d3d12.dll");
|
|
if (!this->d3d12)
|
|
{
|
|
DEBUG_ERROR("failed to load d3d12.dll");
|
|
free(this);
|
|
return false;
|
|
}
|
|
|
|
DX12.D3D12CreateDevice = (typeof(DX12.D3D12CreateDevice))
|
|
GetProcAddress(this->d3d12, "D3D12CreateDevice");
|
|
|
|
DX12.D3D12GetDebugInterface = (typeof(DX12.D3D12GetDebugInterface))
|
|
GetProcAddress(this->d3d12, "D3D12GetDebugInterface");
|
|
|
|
DX12.D3D12SerializeVersionedRootSignature =
|
|
(typeof(DX12.D3D12SerializeVersionedRootSignature))
|
|
GetProcAddress(this->d3d12, "D3D12SerializeVersionedRootSignature");
|
|
|
|
this->getPointerBufferFn = getPointerBufferFn;
|
|
this->postPointerBufferFn = postPointerBufferFn;
|
|
|
|
if (!d12_backendCreate(&D12Backend_DD, &this->backend, frameBuffers))
|
|
{
|
|
DEBUG_ERROR("backend \"%s\" failed to create", this->backend->codeName);
|
|
CloseHandle(this->d3d12);
|
|
free(this);
|
|
return false;
|
|
}
|
|
|
|
this->frameBufferCount = frameBuffers;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool d12_init(void * ivshmemBase, unsigned * alignSize)
|
|
{
|
|
bool result = false;
|
|
comRef_initGlobalScope(100, d12_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 = DX12.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);
|
|
}
|
|
|
|
// get the display path info
|
|
comRef_defineLocal(IDXGIOutput6, output6);
|
|
hr = IDXGIOutput_QueryInterface(*output, &IID_IDXGIOutput6, (void **)output6);
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_WINERROR("Failed to obtain the IDXGIOutput6 interface", hr);
|
|
goto exit;
|
|
}
|
|
|
|
DXGI_OUTPUT_DESC1 desc1;
|
|
IDXGIOutput6_GetDesc1(*output6, &desc1);
|
|
if (!display_getPathInfo(desc1.Monitor, &this->displayPathInfo))
|
|
{
|
|
DEBUG_ERROR("Failed to get the display path info");
|
|
goto exit;
|
|
}
|
|
|
|
// create the D3D12 device
|
|
comRef_defineLocal(ID3D12Device3, device);
|
|
hr = DX12.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;
|
|
}
|
|
|
|
D3D12_COMMAND_QUEUE_DESC copyQueueDesc =
|
|
{
|
|
.Type = D3D12_COMMAND_LIST_TYPE_COPY,
|
|
.Priority = D3D12_COMMAND_QUEUE_PRIORITY_HIGH,
|
|
.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE
|
|
};
|
|
|
|
comRef_defineLocal(ID3D12CommandQueue, copyQueue);
|
|
hr = ID3D12Device3_CreateCommandQueue(
|
|
*device, ©QueueDesc, &IID_ID3D12CommandQueue, (void **)copyQueue);
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_WINERROR("Failed to create ID3D12CommandQueue (copy)", hr);
|
|
goto exit;
|
|
}
|
|
ID3D12CommandQueue_SetName(*copyQueue, L"Copy");
|
|
|
|
// create the compute queue
|
|
D3D12_COMMAND_QUEUE_DESC computeQueueDesc =
|
|
{
|
|
.Type = D3D12_COMMAND_LIST_TYPE_COMPUTE,
|
|
.Priority = D3D12_COMMAND_QUEUE_PRIORITY_HIGH,
|
|
.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE
|
|
};
|
|
|
|
comRef_defineLocal(ID3D12CommandQueue, computeQueue);
|
|
hr = ID3D12Device3_CreateCommandQueue(
|
|
*device, &computeQueueDesc, &IID_ID3D12CommandQueue, (void **)computeQueue);
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_WINERROR("Failed to create the ID3D12CommandQueue (compute)", hr);
|
|
goto exit;
|
|
}
|
|
ID3D12CommandQueue_SetName(*computeQueue, L"Compute");
|
|
|
|
if (!d12_commandGroupCreate(
|
|
*device, D3D12_COMMAND_LIST_TYPE_COPY, &this->copyCommand, L"Copy"))
|
|
goto exit;
|
|
|
|
if (!d12_commandGroupCreate(
|
|
*device, D3D12_COMMAND_LIST_TYPE_COMPUTE, &this->computeCommand, L"Compute"))
|
|
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;
|
|
|
|
/* Ensure we can create resources in the ivshmem heap
|
|
* NOTE: It is safe to do this as the application has not yet setup the KVMFR
|
|
* headers, so we can just attempt to create a resource at the start of the
|
|
* heap without corrupting anything */
|
|
if (!d12_heapTest(*device, *ivshmemHeap))
|
|
{
|
|
DEBUG_ERROR(
|
|
"Unable to create resources in the IVSHMEM heap, is REBAR working?");
|
|
goto exit;
|
|
}
|
|
|
|
// initialize the backend
|
|
if (!d12_backendInit(this->backend, this->debug, *device, *adapter, *output,
|
|
this->trackDamage))
|
|
goto exit;
|
|
|
|
// create the vector of effects
|
|
vector_create(&this->effects, sizeof(D12Effect *), 0);
|
|
|
|
// create all the effects
|
|
for(const D12Effect ** effect = D12Effects; *effect; ++effect)
|
|
{
|
|
D12Effect * instance;
|
|
switch(d12_effectCreate(*effect, &instance, *device, &this->displayPathInfo))
|
|
{
|
|
case D12_EFFECT_STATUS_OK:
|
|
DEBUG_INFO("D12 Created Effect: %s", (*effect)->name);
|
|
vector_push(&this->effects, &instance);
|
|
break;
|
|
|
|
case D12_EFFECT_STATUS_BYPASS:
|
|
continue;
|
|
|
|
case D12_EFFECT_STATUS_ERROR:
|
|
DEBUG_ERROR("Failed to create effect: %s", (*effect)->name);
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
comRef_toGlobal(this->factory , factory );
|
|
comRef_toGlobal(this->device , device );
|
|
comRef_toGlobal(this->copyQueue , copyQueue );
|
|
comRef_toGlobal(this->computeQueue, computeQueue );
|
|
comRef_toGlobal(this->ivshmemHeap , ivshmemHeap );
|
|
|
|
result = true;
|
|
|
|
exit:
|
|
comRef_scopePop();
|
|
if (!result)
|
|
{
|
|
D12Effect * effect;
|
|
vector_forEach(effect, &this->effects)
|
|
d12_effectFree(&effect);
|
|
vector_destroy(&this->effects);
|
|
comRef_freeScope(&d12_comScope);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void d12_stop(void)
|
|
{
|
|
}
|
|
|
|
static bool d12_deinit(void)
|
|
{
|
|
bool result = true;
|
|
|
|
D12Effect * effect;
|
|
vector_forEach(effect, &this->effects)
|
|
d12_effectFree(&effect);
|
|
vector_destroy(&this->effects);
|
|
|
|
if (!d12_backendDeinit(this->backend))
|
|
result = false;
|
|
|
|
d12_commandGroupFree(&this->copyCommand );
|
|
d12_commandGroupFree(&this->computeCommand);
|
|
|
|
IDXGIFactory2 * factory = *this->factory;
|
|
IDXGIFactory2_AddRef(factory);
|
|
comRef_freeScope(&d12_comScope);
|
|
if (IDXGIFactory2_Release(factory) != 0)
|
|
DEBUG_WARN("MEMORY LEAK");
|
|
|
|
// zero the framebuffers
|
|
memset(this->frameBuffers, 0,
|
|
sizeof(*this->frameBuffers) * this->frameBufferCount);
|
|
|
|
/* zero the formats so we properly reinit otherwise we wont detect the format
|
|
change and setup the effect chain */
|
|
memset(&this->captureFormat, 0, sizeof(this->captureFormat));
|
|
memset(&this->dstFormat , 0, sizeof(this->dstFormat ));
|
|
|
|
/* dirty rect history is no longer valid */
|
|
this->nbDirtyRects = 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
static void d12_free(void)
|
|
{
|
|
d12_backendFree(&this->backend);
|
|
FreeLibrary(this->d3d12);
|
|
free(this);
|
|
this = NULL;
|
|
}
|
|
|
|
static CaptureResult d12_capture(
|
|
unsigned frameBufferIndex, FrameBuffer * frameBuffer)
|
|
{
|
|
return d12_backendCapture(this->backend, frameBufferIndex);
|
|
}
|
|
|
|
static CaptureResult d12_waitFrame(unsigned frameBufferIndex,
|
|
CaptureFrame * frame, const size_t maxFrameSize)
|
|
{
|
|
CaptureResult result = CAPTURE_RESULT_ERROR;
|
|
comRef_scopePush(1);
|
|
|
|
D12FrameDesc desc;
|
|
|
|
comRef_defineLocal(ID3D12Resource, src);
|
|
*src = d12_backendFetch(this->backend, frameBufferIndex, &desc);
|
|
if (!*src)
|
|
{
|
|
DEBUG_ERROR("D12 backend failed to produce an expected frame: %u",
|
|
frameBufferIndex);
|
|
goto exit;
|
|
}
|
|
|
|
D12FrameFormat srcFormat =
|
|
{
|
|
.desc = ID3D12Resource_GetDesc(*src),
|
|
.colorSpace = desc.colorSpace,
|
|
.width = srcFormat.desc.Width,
|
|
.height = srcFormat.desc.Height
|
|
};
|
|
|
|
switch(srcFormat.desc.Format)
|
|
{
|
|
case DXGI_FORMAT_B8G8R8A8_UNORM:
|
|
srcFormat.format = CAPTURE_FMT_BGRA;
|
|
break;
|
|
|
|
case DXGI_FORMAT_R8G8B8A8_UNORM:
|
|
srcFormat.format = CAPTURE_FMT_RGBA;
|
|
break;
|
|
|
|
case DXGI_FORMAT_R10G10B10A2_UNORM:
|
|
srcFormat.format = CAPTURE_FMT_RGBA10;
|
|
break;
|
|
|
|
case DXGI_FORMAT_R16G16B16A16_FLOAT:
|
|
srcFormat.format = CAPTURE_FMT_RGBA16F;
|
|
break;
|
|
|
|
default:
|
|
DEBUG_ERROR("Unsupported source format");
|
|
goto exit;
|
|
}
|
|
|
|
// if the input format changed, reconfigure the effects
|
|
if (srcFormat.desc.Width == 0 ||
|
|
srcFormat.desc.Width != this->captureFormat.desc.Width ||
|
|
srcFormat.desc.Height != this->captureFormat.desc.Height ||
|
|
srcFormat.desc.Format != this->captureFormat.desc.Format ||
|
|
srcFormat.colorSpace != this->captureFormat.colorSpace)
|
|
{
|
|
D12FrameFormat dstFormat = this->dstFormat;
|
|
this->captureFormat = srcFormat;
|
|
this->effectsActive = false;
|
|
|
|
D12Effect * effect;
|
|
D12FrameFormat curFormat = srcFormat;
|
|
vector_forEach(effect, &this->effects)
|
|
{
|
|
dstFormat = curFormat;
|
|
switch(d12_effectSetFormat(effect, *this->device, &curFormat, &dstFormat))
|
|
{
|
|
case D12_EFFECT_STATUS_OK:
|
|
this->effectsActive = true;
|
|
curFormat = dstFormat;
|
|
effect->enabled = true;
|
|
DEBUG_INFO("D12 Effect Active: %s", effect->name);
|
|
break;
|
|
|
|
case D12_EFFECT_STATUS_ERROR:
|
|
DEBUG_ERROR("Failed to set the effect input format");
|
|
goto exit;
|
|
|
|
case D12_EFFECT_STATUS_BYPASS:
|
|
effect->enabled = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if the output format changed
|
|
if (dstFormat.desc.Width != this->dstFormat.desc.Width ||
|
|
dstFormat.desc.Height != this->dstFormat.desc.Height ||
|
|
dstFormat.desc.Format != this->dstFormat.desc.Format ||
|
|
dstFormat.colorSpace != this->dstFormat.colorSpace ||
|
|
dstFormat.width != this->dstFormat.width ||
|
|
dstFormat.height != this->dstFormat.height ||
|
|
dstFormat.format != this->dstFormat.format)
|
|
{
|
|
++this->formatVer;
|
|
this->dstFormat = dstFormat;
|
|
}
|
|
}
|
|
|
|
D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout;
|
|
ID3D12Device3_GetCopyableFootprints(*this->device,
|
|
&this->dstFormat.desc,
|
|
0 , // FirstSubresource
|
|
1 , // NumSubresources
|
|
0 , // BaseOffset,
|
|
&layout , // pLayouts
|
|
NULL , // pNumRows,
|
|
NULL , // pRowSizeInBytes,
|
|
NULL); // pTotalBytes
|
|
this->pitch = layout.Footprint.RowPitch;
|
|
|
|
const unsigned maxRows = maxFrameSize / layout.Footprint.RowPitch;
|
|
const unsigned bpp = this->dstFormat.format == CAPTURE_FMT_RGBA16F ? 8 : 4;
|
|
|
|
frame->formatVer = this->formatVer;
|
|
frame->screenWidth = srcFormat.width;
|
|
frame->screenHeight = srcFormat.height;
|
|
frame->dataWidth = this->dstFormat.desc.Width;
|
|
frame->dataHeight = min(maxRows, this->dstFormat.desc.Height);
|
|
frame->frameWidth = this->dstFormat.width;
|
|
frame->frameHeight = this->dstFormat.height;
|
|
frame->truncated = maxRows < this->dstFormat.desc.Height;
|
|
frame->pitch = this->pitch;
|
|
frame->stride = this->pitch / bpp;
|
|
frame->format = this->dstFormat.format;
|
|
frame->hdr = this->dstFormat.colorSpace ==
|
|
DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
|
|
frame->hdrPQ = false;
|
|
frame->rotation = desc.rotation;
|
|
|
|
D12Effect * effect;
|
|
vector_forEach(effect, &this->effects)
|
|
if (effect->enabled)
|
|
d12_effectAdjustDamage(effect, desc.dirtyRects, &desc.nbDirtyRects);
|
|
|
|
{
|
|
// create a clean list of rects
|
|
FrameDamageRect allRects[desc.nbDirtyRects];
|
|
unsigned count = 0;
|
|
for(const RECT * rect = desc.dirtyRects;
|
|
rect < desc.dirtyRects + desc.nbDirtyRects; ++rect)
|
|
allRects[count++] = (FrameDamageRect){
|
|
.x = rect->left,
|
|
.y = rect->top,
|
|
.width = (rect->right - rect->left),
|
|
.height = (rect->bottom - rect->top)
|
|
};
|
|
|
|
count = rectsMergeOverlapping(allRects, count);
|
|
|
|
// if there are too many rects
|
|
if (unlikely(count > ARRAY_LENGTH(frame->damageRects)))
|
|
frame->damageRectsCount = 0;
|
|
else
|
|
{
|
|
// send the list of dirty rects for this frame
|
|
frame->damageRectsCount = count;
|
|
memcpy(frame->damageRects, allRects, sizeof(*allRects) * count);
|
|
}
|
|
}
|
|
|
|
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(3);
|
|
|
|
D12FrameDesc desc;
|
|
|
|
comRef_defineLocal(ID3D12Resource, src);
|
|
*src = d12_backendFetch(this->backend, frameBufferIndex, &desc);
|
|
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;
|
|
|
|
// place a fence into the queue
|
|
result = d12_backendSync(this->backend,
|
|
this->effectsActive ? *this->computeQueue : *this->copyQueue);
|
|
|
|
if (result != CAPTURE_RESULT_OK)
|
|
goto exit;
|
|
|
|
ID3D12Resource * next = *src;
|
|
D12Effect * effect;
|
|
vector_forEach(effect, &this->effects)
|
|
{
|
|
if (!effect->enabled)
|
|
continue;
|
|
|
|
next = d12_effectRun(effect,
|
|
*this->device,
|
|
*this->computeCommand.gfxList,
|
|
next,
|
|
desc.dirtyRects,
|
|
&desc.nbDirtyRects);
|
|
}
|
|
|
|
// copy into the framebuffer resource
|
|
D3D12_TEXTURE_COPY_LOCATION srcLoc =
|
|
{
|
|
.pResource = next,
|
|
.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 = this->dstFormat.desc.Format,
|
|
.Width = this->dstFormat.desc.Width,
|
|
.Height = this->dstFormat.desc.Height,
|
|
.Depth = 1,
|
|
.RowPitch = this->pitch
|
|
}
|
|
}
|
|
};
|
|
|
|
// if full frame damage
|
|
if (desc.nbDirtyRects == 0)
|
|
{
|
|
this->nbDirtyRects = 0;
|
|
ID3D12GraphicsCommandList_CopyTextureRegion(
|
|
*this->copyCommand.gfxList, &dstLoc, 0, 0, 0, &srcLoc, NULL);
|
|
}
|
|
else
|
|
{
|
|
/* if the prior frame was a full update */
|
|
if (this->nbDirtyRects == 0)
|
|
{
|
|
/* the prior frame was fully damaged, we must update everything */
|
|
ID3D12GraphicsCommandList_CopyTextureRegion(
|
|
*this->copyCommand.gfxList, &dstLoc, 0, 0, 0, &srcLoc, NULL);
|
|
}
|
|
else
|
|
{
|
|
FrameDamageRect allRects[this->nbDirtyRects + desc.nbDirtyRects];
|
|
unsigned count = 0;
|
|
|
|
/* we must update the rects that were dirty in the prior frame also,
|
|
* otherwise the frame in memory will not be consistent when areas need to
|
|
* be redrawn by the client, such as under the cursor */
|
|
for(const RECT * rect = this->dirtyRects;
|
|
rect < this->dirtyRects + this->nbDirtyRects; ++rect)
|
|
allRects[count++] = (FrameDamageRect){
|
|
.x = rect->left,
|
|
.y = rect->top,
|
|
.width = rect->right - rect->left,
|
|
.height = rect->bottom - rect->top
|
|
};
|
|
|
|
/* add the new dirtyRects to the array */
|
|
for(const RECT * rect = desc.dirtyRects;
|
|
rect < desc.dirtyRects + desc.nbDirtyRects; ++rect)
|
|
allRects[count++] = (FrameDamageRect){
|
|
.x = rect->left,
|
|
.y = rect->top,
|
|
.width = rect->right - rect->left,
|
|
.height = rect->bottom - rect->top
|
|
};
|
|
|
|
/* resolve the rects */
|
|
count = rectsMergeOverlapping(allRects, count);
|
|
|
|
/* copy all the rects */
|
|
for(FrameDamageRect * rect = allRects; rect < allRects + count; ++rect)
|
|
{
|
|
D3D12_BOX box =
|
|
{
|
|
.left = rect->x,
|
|
.top = rect->y,
|
|
.front = 0,
|
|
.back = 1,
|
|
.right = rect->x + rect->width,
|
|
.bottom = rect->y + rect->height
|
|
};
|
|
|
|
ID3D12GraphicsCommandList_CopyTextureRegion(
|
|
*this->copyCommand.gfxList, &dstLoc,
|
|
box.left, box.top, 0, &srcLoc, &box);
|
|
}
|
|
}
|
|
|
|
/* store the dirty rects for the next frame */
|
|
memcpy(this->dirtyRects, desc.dirtyRects,
|
|
desc.nbDirtyRects * sizeof(*this->dirtyRects));
|
|
this->nbDirtyRects = desc.nbDirtyRects;
|
|
}
|
|
|
|
// execute the compute commands
|
|
if (this->effectsActive)
|
|
{
|
|
d12_commandGroupExecute(*this->computeQueue, &this->computeCommand);
|
|
|
|
// insert a fence to wait for the compute commands to finish
|
|
ID3D12CommandQueue_Wait(*this->copyQueue,
|
|
*this->computeCommand.fence, this->computeCommand.fenceValue);
|
|
}
|
|
|
|
// execute the copy commands
|
|
d12_commandGroupExecute(*this->copyQueue, &this->copyCommand);
|
|
|
|
// wait for the copy to complete
|
|
d12_commandGroupWait(&this->copyCommand);
|
|
|
|
// signal the frame is complete
|
|
framebuffer_set_write_ptr(frameBuffer,
|
|
this->dstFormat.desc.Height * this->pitch);
|
|
|
|
// reset the command queues
|
|
if (this->effectsActive && !d12_commandGroupReset(&this->computeCommand))
|
|
goto exit;
|
|
|
|
if (!d12_commandGroupReset(&this->copyCommand))
|
|
goto exit;
|
|
|
|
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;
|
|
|
|
const char * _optAdapter = option_get_string("d12", "adapter");
|
|
const char * _optOutput = option_get_string("d12", "output" );
|
|
|
|
wchar_t * optAdapter = NULL;
|
|
wchar_t * optOutput = NULL;
|
|
if (_optAdapter)
|
|
{
|
|
optAdapter = malloc((strlen(_optAdapter) + 1) * 2);
|
|
mbstowcs(optAdapter, _optAdapter, strlen(_optAdapter));
|
|
}
|
|
|
|
if (_optOutput)
|
|
{
|
|
optOutput = malloc((strlen(_optOutput) + 1) * 2);
|
|
mbstowcs(optOutput, _optOutput, strlen(_optOutput));
|
|
}
|
|
|
|
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;
|
|
|
|
if (optAdapter)
|
|
{
|
|
if (wcsstr(adapterDesc.Description, optAdapter) == NULL)
|
|
{
|
|
DEBUG_INFO("Not using adapter: %ls", adapterDesc.Description);
|
|
continue;
|
|
}
|
|
DEBUG_INFO("Adapter matched, trying: %ls", adapterDesc.Description);
|
|
}
|
|
|
|
for(
|
|
int n = 0;
|
|
IDXGIAdapter1_EnumOutputs(*adapter, n, output) != DXGI_ERROR_NOT_FOUND;
|
|
++n, comRef_release(output))
|
|
{
|
|
IDXGIOutput_GetDesc(*output, &outputDesc);
|
|
|
|
if (optOutput)
|
|
{
|
|
if (wcsstr(outputDesc.DeviceName, optOutput) == NULL)
|
|
{
|
|
DEBUG_INFO("Not using adapter output: %ls", outputDesc.DeviceName);
|
|
continue;
|
|
}
|
|
|
|
DEBUG_INFO("Adapter output matched, trying: %ls", outputDesc.DeviceName);
|
|
}
|
|
|
|
if (outputDesc.AttachedToDesktop)
|
|
break;
|
|
}
|
|
|
|
if (*output)
|
|
break;
|
|
}
|
|
|
|
free(optAdapter);
|
|
free(optOutput );
|
|
|
|
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_heapTest(ID3D12Device3 * device, ID3D12Heap * heap)
|
|
{
|
|
bool result = false;
|
|
comRef_scopePush(1);
|
|
|
|
D3D12_RESOURCE_DESC desc =
|
|
{
|
|
.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
|
|
.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT,
|
|
.Width = 1048576,
|
|
.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(
|
|
device,
|
|
heap,
|
|
0,
|
|
&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;
|
|
}
|
|
|
|
result = true;
|
|
|
|
exit:
|
|
comRef_scopePop();
|
|
return result;
|
|
}
|
|
|
|
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
|
|
};
|