mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-04 06:12:04 +00:00
Use calibrated copy-queue timestamps to separate source and effect waits from the actual framebuffer copy. Exclude producer readiness waits from client import timing so the same interval is not counted in both Copy and Import.
1569 lines
43 KiB
C
1569 lines
43 KiB
C
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 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 "common/time.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>
|
|
#include <math.h>
|
|
|
|
// definitions
|
|
struct D12Interface
|
|
{
|
|
HMODULE d3d12;
|
|
|
|
IDXGIFactory2 ** factory;
|
|
ID3D12Device3 ** device;
|
|
|
|
DISPLAYCONFIG_PATH_INFO displayPathInfo;
|
|
DXGI_OUTPUT_DESC1 outputDesc;
|
|
|
|
ID3D12CommandQueue ** copyQueue;
|
|
ID3D12CommandQueue ** computeQueue;
|
|
D12CommandGroup copyCommand;
|
|
D12CommandGroup computeCommand;
|
|
|
|
struct
|
|
{
|
|
ID3D12QueryHeap ** heap;
|
|
ID3D12Resource ** readback;
|
|
UINT64 * map;
|
|
UINT64 timestampFrequency;
|
|
UINT64 qpcFrequency;
|
|
UINT64 calibrationGPU;
|
|
UINT64 calibrationCPU;
|
|
bool supported;
|
|
}
|
|
copyTiming;
|
|
|
|
void * ivshmemBase;
|
|
ID3D12Heap ** ivshmemHeap;
|
|
bool indirectCopy;
|
|
|
|
CaptureGetPointerBuffer getPointerBufferFn;
|
|
CapturePostPointerBuffer postPointerBufferFn;
|
|
|
|
D12Backend * backend;
|
|
Vector effects;
|
|
bool effectsActive;
|
|
|
|
// capture format tracking
|
|
D12FrameFormat captureFormat;
|
|
unsigned formatVer;
|
|
unsigned pitch;
|
|
unsigned bpp;
|
|
|
|
// 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;
|
|
// the mapped resource if indirectCopy is in use
|
|
void * map;
|
|
}
|
|
frameBuffers[0];
|
|
};
|
|
|
|
// gloabls
|
|
|
|
struct DX12 DX12 = {0};
|
|
ComScope * d12_comScope = NULL;
|
|
|
|
// defines
|
|
|
|
// locals
|
|
|
|
static struct D12Interface * this = NULL;
|
|
|
|
static bool d12_setHDRMetadata(CaptureFrame * frame,
|
|
const DXGI_OUTPUT_DESC1 * desc)
|
|
{
|
|
const float coordinates[] =
|
|
{
|
|
desc->RedPrimary [0], desc->RedPrimary [1],
|
|
desc->GreenPrimary[0], desc->GreenPrimary[1],
|
|
desc->BluePrimary [0], desc->BluePrimary [1],
|
|
desc->WhitePoint [0], desc->WhitePoint [1],
|
|
};
|
|
|
|
for (unsigned i = 0; i < ARRAY_LENGTH(coordinates); ++i)
|
|
if (coordinates[i] < 0.0f || coordinates[i] > 1.0f)
|
|
return false;
|
|
|
|
if (desc->RedPrimary[1] == 0.0f || desc->GreenPrimary[1] == 0.0f ||
|
|
desc->BluePrimary[1] == 0.0f || desc->WhitePoint[1] == 0.0f ||
|
|
desc->MaxLuminance <= 0.0f)
|
|
return false;
|
|
|
|
frame->hdrDisplayPrimary[0][0] = (uint16_t)lroundf(
|
|
desc->RedPrimary[0] * 50000.0f);
|
|
frame->hdrDisplayPrimary[0][1] = (uint16_t)lroundf(
|
|
desc->RedPrimary[1] * 50000.0f);
|
|
frame->hdrDisplayPrimary[1][0] = (uint16_t)lroundf(
|
|
desc->GreenPrimary[0] * 50000.0f);
|
|
frame->hdrDisplayPrimary[1][1] = (uint16_t)lroundf(
|
|
desc->GreenPrimary[1] * 50000.0f);
|
|
frame->hdrDisplayPrimary[2][0] = (uint16_t)lroundf(
|
|
desc->BluePrimary[0] * 50000.0f);
|
|
frame->hdrDisplayPrimary[2][1] = (uint16_t)lroundf(
|
|
desc->BluePrimary[1] * 50000.0f);
|
|
frame->hdrWhitePoint[0] = (uint16_t)lroundf(
|
|
desc->WhitePoint[0] * 50000.0f);
|
|
frame->hdrWhitePoint[1] = (uint16_t)lroundf(
|
|
desc->WhitePoint[1] * 50000.0f);
|
|
frame->hdrMaxDisplayLuminance = (uint32_t)lroundf(desc->MaxLuminance);
|
|
frame->hdrMinDisplayLuminance = desc->MinLuminance > 0.0f ?
|
|
(uint32_t)lroundf(desc->MinLuminance * 10000.0f) : 0;
|
|
|
|
if ((uint64_t)frame->hdrMaxDisplayLuminance * 10000 <=
|
|
frame->hdrMinDisplayLuminance)
|
|
return false;
|
|
|
|
frame->hdrMetadata = true;
|
|
return true;
|
|
}
|
|
|
|
// 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,
|
|
void ** map);
|
|
|
|
static bool d12_copyTimingInit(
|
|
ID3D12Device3 * device, ID3D12CommandQueue * queue);
|
|
|
|
static void d12_copyTimingDeinit(void);
|
|
static void d12_copyTimingUpdateCalibration(void);
|
|
static bool d12_copyTimingGetStart(uint64_t * start);
|
|
|
|
// implementation
|
|
|
|
static const char * d12_getName(void)
|
|
{
|
|
return "D12";
|
|
}
|
|
|
|
static uint64_t d12_scaleTicks(
|
|
uint64_t ticks, uint64_t targetFrequency, uint64_t sourceFrequency)
|
|
{
|
|
return ticks / sourceFrequency * targetFrequency +
|
|
ticks % sourceFrequency * targetFrequency / sourceFrequency;
|
|
}
|
|
|
|
static bool d12_copyTimingInit(
|
|
ID3D12Device3 * device, ID3D12CommandQueue * queue)
|
|
{
|
|
D3D12_FEATURE_DATA_D3D12_OPTIONS3 options = {0};
|
|
HRESULT hr = ID3D12Device3_CheckFeatureSupport(device,
|
|
D3D12_FEATURE_D3D12_OPTIONS3, &options, sizeof(options));
|
|
if (FAILED(hr) || !options.CopyQueueTimestampQueriesSupported)
|
|
return false;
|
|
|
|
UINT64 timestampFrequency;
|
|
hr = ID3D12CommandQueue_GetTimestampFrequency(queue, ×tampFrequency);
|
|
if (FAILED(hr) || !timestampFrequency)
|
|
return false;
|
|
|
|
LARGE_INTEGER qpcFrequency;
|
|
if (!QueryPerformanceFrequency(&qpcFrequency) || !qpcFrequency.QuadPart)
|
|
return false;
|
|
|
|
UINT64 calibrationGPU;
|
|
UINT64 calibrationCPU;
|
|
hr = ID3D12CommandQueue_GetClockCalibration(
|
|
queue, &calibrationGPU, &calibrationCPU);
|
|
if (FAILED(hr))
|
|
return false;
|
|
|
|
bool result = false;
|
|
comRef_scopePush(2);
|
|
|
|
D3D12_QUERY_HEAP_DESC queryDesc =
|
|
{
|
|
.Type = D3D12_QUERY_HEAP_TYPE_COPY_QUEUE_TIMESTAMP,
|
|
.Count = 1
|
|
};
|
|
|
|
comRef_defineLocal(ID3D12QueryHeap, heap);
|
|
hr = ID3D12Device3_CreateQueryHeap(device, &queryDesc,
|
|
&IID_ID3D12QueryHeap, (void **)heap);
|
|
if (FAILED(hr))
|
|
goto exit;
|
|
|
|
D3D12_HEAP_PROPERTIES heapProps =
|
|
{
|
|
.Type = D3D12_HEAP_TYPE_READBACK,
|
|
.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
|
|
.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
|
|
.CreationNodeMask = 1,
|
|
.VisibleNodeMask = 1
|
|
};
|
|
|
|
D3D12_RESOURCE_DESC resourceDesc =
|
|
{
|
|
.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
|
|
.Width = sizeof(UINT64),
|
|
.Height = 1,
|
|
.DepthOrArraySize = 1,
|
|
.MipLevels = 1,
|
|
.Format = DXGI_FORMAT_UNKNOWN,
|
|
.SampleDesc.Count = 1,
|
|
.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
|
|
.Flags = D3D12_RESOURCE_FLAG_NONE
|
|
};
|
|
|
|
comRef_defineLocal(ID3D12Resource, readback);
|
|
hr = ID3D12Device3_CreateCommittedResource(
|
|
device,
|
|
&heapProps,
|
|
D3D12_HEAP_FLAG_NONE,
|
|
&resourceDesc,
|
|
D3D12_RESOURCE_STATE_COPY_DEST,
|
|
NULL,
|
|
&IID_ID3D12Resource,
|
|
(void **)readback);
|
|
if (FAILED(hr))
|
|
goto exit;
|
|
|
|
D3D12_RANGE readRange = {0, sizeof(UINT64)};
|
|
void * timestampMap = NULL;
|
|
hr = ID3D12Resource_Map(*readback, 0, &readRange, ×tampMap);
|
|
if (FAILED(hr))
|
|
goto exit;
|
|
|
|
comRef_toGlobal(this->copyTiming.heap , heap );
|
|
comRef_toGlobal(this->copyTiming.readback, readback);
|
|
this->copyTiming.map = timestampMap;
|
|
this->copyTiming.timestampFrequency = timestampFrequency;
|
|
this->copyTiming.qpcFrequency = qpcFrequency.QuadPart;
|
|
this->copyTiming.calibrationGPU = calibrationGPU;
|
|
this->copyTiming.calibrationCPU = calibrationCPU;
|
|
this->copyTiming.supported = true;
|
|
result = true;
|
|
|
|
exit:
|
|
comRef_scopePop();
|
|
return result;
|
|
}
|
|
|
|
static void d12_copyTimingDeinit(void)
|
|
{
|
|
if (this->copyTiming.map)
|
|
{
|
|
const D3D12_RANGE writeRange = {0, 0};
|
|
ID3D12Resource_Unmap(
|
|
*this->copyTiming.readback, 0, &writeRange);
|
|
}
|
|
|
|
memset(&this->copyTiming, 0, sizeof(this->copyTiming));
|
|
}
|
|
|
|
static void d12_copyTimingUpdateCalibration(void)
|
|
{
|
|
if (!this->copyTiming.supported)
|
|
return;
|
|
|
|
LARGE_INTEGER now;
|
|
if (QueryPerformanceCounter(&now) &&
|
|
(UINT64)now.QuadPart >= this->copyTiming.calibrationCPU &&
|
|
(UINT64)now.QuadPart - this->copyTiming.calibrationCPU <
|
|
this->copyTiming.qpcFrequency)
|
|
return;
|
|
|
|
UINT64 calibrationGPU;
|
|
UINT64 calibrationCPU;
|
|
if (SUCCEEDED(ID3D12CommandQueue_GetClockCalibration(
|
|
*this->copyQueue, &calibrationGPU, &calibrationCPU)))
|
|
{
|
|
this->copyTiming.calibrationGPU = calibrationGPU;
|
|
this->copyTiming.calibrationCPU = calibrationCPU;
|
|
}
|
|
}
|
|
|
|
static bool d12_copyTimingGetStart(uint64_t * start)
|
|
{
|
|
if (!this->copyTiming.supported)
|
|
return false;
|
|
|
|
const UINT64 gpuStart = *this->copyTiming.map;
|
|
UINT64 cpuStart;
|
|
if (gpuStart < this->copyTiming.calibrationGPU)
|
|
{
|
|
const UINT64 delta = d12_scaleTicks(
|
|
this->copyTiming.calibrationGPU - gpuStart,
|
|
this->copyTiming.qpcFrequency,
|
|
this->copyTiming.timestampFrequency);
|
|
if (delta > this->copyTiming.calibrationCPU)
|
|
return false;
|
|
|
|
cpuStart = this->copyTiming.calibrationCPU - delta;
|
|
}
|
|
else
|
|
{
|
|
const UINT64 delta = d12_scaleTicks(
|
|
gpuStart - this->copyTiming.calibrationGPU,
|
|
this->copyTiming.qpcFrequency,
|
|
this->copyTiming.timestampFrequency);
|
|
if (UINT64_MAX - this->copyTiming.calibrationCPU < delta)
|
|
return false;
|
|
|
|
cpuStart = this->copyTiming.calibrationCPU + delta;
|
|
}
|
|
|
|
*start = d12_scaleTicks(
|
|
cpuStart, 1000000000ULL, this->copyTiming.qpcFrequency);
|
|
return true;
|
|
}
|
|
|
|
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
|
|
},
|
|
{
|
|
.module = "d12",
|
|
.name = "indirectCopy",
|
|
.description = "Force the less optimal indirect copy method",
|
|
.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" );
|
|
this->indirectCopy = option_get_bool("d12", "indirectCopy");
|
|
|
|
DEBUG_INFO(
|
|
"debug:%d trackDamage:%d indirectCopy:%d",
|
|
this->debug, this->trackDamage, this->indirectCopy);
|
|
|
|
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");
|
|
|
|
if (!DX12.D3D12CreateDevice ||
|
|
!DX12.D3D12GetDebugInterface ||
|
|
!DX12.D3D12SerializeVersionedRootSignature)
|
|
{
|
|
DEBUG_ERROR("Failed to get required exports from d3d12.dll");
|
|
CloseHandle(this->d3d12);
|
|
free(this);
|
|
return false;
|
|
}
|
|
|
|
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 reInit = false;
|
|
bool result = false;
|
|
comRef_initGlobalScope(100, d12_comScope);
|
|
comRef_scopePush(10);
|
|
|
|
// create a DXGI factory
|
|
comRef_defineLocal(IDXGIFactory2, factory);
|
|
DEBUG_TRACE("CreateDXGIFactory2");
|
|
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);
|
|
this->outputDesc = 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);
|
|
DEBUG_TRACE("D3D12CreateDevice");
|
|
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);
|
|
DEBUG_TRACE("D3D12Device3_CreateCommandQueue");
|
|
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);
|
|
DEBUG_TRACE("D3D12Device3_CreateCommandQueue");
|
|
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");
|
|
|
|
DEBUG_TRACE("d12_commandGroupCreate Copy");
|
|
if (!d12_commandGroupCreate(
|
|
*device, D3D12_COMMAND_LIST_TYPE_COPY, &this->copyCommand, L"Copy"))
|
|
goto exit;
|
|
|
|
DEBUG_TRACE("d12_commandGroupCreate Compute");
|
|
if (!d12_commandGroupCreate(
|
|
*device, D3D12_COMMAND_LIST_TYPE_COMPUTE, &this->computeCommand, L"Compute"))
|
|
goto exit;
|
|
|
|
if (!d12_copyTimingInit(*device, *copyQueue))
|
|
DEBUG_WARN("Copy queue GPU timing is unavailable");
|
|
|
|
comRef_defineLocal(ID3D12Heap, ivshmemHeap);
|
|
if (!this->indirectCopy)
|
|
{
|
|
// Create the IVSHMEM heap
|
|
this->ivshmemBase = ivshmemBase;
|
|
DEBUG_TRACE("ID3D12Device3_OpenExistingHeapFromAddress");
|
|
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 */
|
|
DEBUG_TRACE("d12_heapTest");
|
|
if (!d12_heapTest(*device, *ivshmemHeap))
|
|
{
|
|
this->indirectCopy = true;
|
|
DEBUG_WARN("Unable to create resources in the IVSHMEM heap, "
|
|
"falling back to indirect copy");
|
|
|
|
// we need to retry from scratch this failure acts like the GPU crashed
|
|
reInit = true;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// initialize the backend
|
|
DEBUG_TRACE("d12_backendInit");
|
|
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 );
|
|
|
|
if (!this->indirectCopy)
|
|
comRef_toGlobal(this->ivshmemHeap, ivshmemHeap);
|
|
|
|
DEBUG_TRACE("Init success");
|
|
result = true;
|
|
|
|
exit:
|
|
comRef_scopePop();
|
|
if (!result)
|
|
{
|
|
DEBUG_TRACE("Init failed");
|
|
d12_copyTimingDeinit();
|
|
D12Effect * effect;
|
|
vector_forEach(effect, &this->effects)
|
|
d12_effectFree(&effect);
|
|
vector_destroy(&this->effects);
|
|
comRef_freeScope(&d12_comScope);
|
|
|
|
if (reInit)
|
|
{
|
|
DEBUG_INFO("Attempting to reinitialize with indirectCopy=1");
|
|
return d12_init(ivshmemBase, alignSize);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void d12_stop(void)
|
|
{
|
|
}
|
|
|
|
static bool d12_deinit(void)
|
|
{
|
|
bool result = true;
|
|
|
|
if (this->indirectCopy)
|
|
{
|
|
for(int i = 0; i < this->frameBufferCount; ++i)
|
|
if (this->frameBuffers[i].map)
|
|
ID3D12Resource_Unmap(*this->frameBuffers[i].resource, 0, NULL);
|
|
}
|
|
|
|
D12Effect * effect;
|
|
vector_forEach(effect, &this->effects)
|
|
d12_effectFree(&effect);
|
|
vector_destroy(&this->effects);
|
|
|
|
DEBUG_TRACE("Backend deinit");
|
|
if (!d12_backendDeinit(this->backend))
|
|
result = false;
|
|
|
|
DEBUG_TRACE("commandGroupFree");
|
|
d12_commandGroupFree(&this->copyCommand );
|
|
d12_commandGroupFree(&this->computeCommand);
|
|
|
|
d12_copyTimingDeinit();
|
|
|
|
DEBUG_TRACE("comRef_freeScope");
|
|
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)
|
|
{
|
|
DEBUG_TRACE("d12_backendFree");
|
|
d12_backendFree(&this->backend);
|
|
FreeLibrary(this->d3d12);
|
|
free(this);
|
|
this = NULL;
|
|
}
|
|
|
|
static CaptureResult d12_capture(
|
|
unsigned frameBufferIndex, FrameBuffer * frameBuffer)
|
|
{
|
|
DEBUG_TRACE("d12_backendCapture");
|
|
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);
|
|
DEBUG_TRACE("d12_backendFetch");
|
|
*src = d12_backendFetch(this->backend, frameBufferIndex, &desc);
|
|
if (!*src)
|
|
{
|
|
DEBUG_ERROR("D12 backend failed to produce an expected frame: %u",
|
|
frameBufferIndex);
|
|
goto exit;
|
|
}
|
|
|
|
const D3D12_RESOURCE_DESC srcDesc = ID3D12Resource_GetDesc(*src);
|
|
D12FrameFormat srcFormat =
|
|
{
|
|
.desc = srcDesc,
|
|
.colorSpace = desc.colorSpace,
|
|
.width = srcDesc.Width,
|
|
.height = srcDesc.Height,
|
|
.hdr = desc.colorSpace ==
|
|
DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020,
|
|
.hdrPQ = false,
|
|
};
|
|
|
|
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 (srcFormat.hdr)
|
|
{
|
|
if (srcFormat.format == CAPTURE_FMT_RGBA10)
|
|
srcFormat.hdrPQ = true;
|
|
else if (srcFormat.format != CAPTURE_FMT_RGBA16F)
|
|
{
|
|
DEBUG_ERROR("HDR capture did not provide an FP16 scRGB or PQ texture");
|
|
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 ||
|
|
srcFormat.hdr != this->captureFormat.hdr ||
|
|
srcFormat.hdrPQ != this->captureFormat.hdrPQ)
|
|
{
|
|
DEBUG_TRACE("Capture format changed");
|
|
|
|
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 ||
|
|
dstFormat.hdr != this->dstFormat.hdr ||
|
|
dstFormat.hdrPQ != this->dstFormat.hdrPQ)
|
|
{
|
|
DEBUG_TRACE("Output format changed");
|
|
++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;
|
|
this->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 / this->bpp;
|
|
frame->format = this->dstFormat.format;
|
|
frame->hdr = this->dstFormat.hdr;
|
|
frame->hdrPQ = this->dstFormat.hdrPQ;
|
|
frame->sdrWhiteLevel = (uint32_t)lroundf(
|
|
display_getSDRWhiteLevel(&this->displayPathInfo));
|
|
if (frame->hdr)
|
|
d12_setHDRMetadata(frame, &this->outputDesc);
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Refresh before getFrame so calibration work does not perturb Post/Copy.
|
|
d12_copyTimingUpdateCalibration();
|
|
|
|
result = CAPTURE_RESULT_OK;
|
|
|
|
exit:
|
|
comRef_scopePop();
|
|
return result;
|
|
}
|
|
|
|
static CaptureResult d12_getFrame(
|
|
unsigned frameBufferIndex,
|
|
FrameBuffer * frameBuffer,
|
|
const size_t maxFrameSize,
|
|
CaptureFrame * captureFrame)
|
|
{
|
|
const uint64_t postProcessStart = nanotime();
|
|
CaptureResult result = CAPTURE_RESULT_ERROR;
|
|
comRef_scopePush(3);
|
|
|
|
D12FrameDesc desc;
|
|
|
|
comRef_defineLocal(ID3D12Resource, src);
|
|
DEBUG_TRACE("d12_backendFetch");
|
|
*src = d12_backendFetch(this->backend, frameBufferIndex, &desc);
|
|
unsigned rectCount = 0;
|
|
FrameDamageRect allRects[this->nbDirtyRects + desc.nbDirtyRects];
|
|
|
|
if (!*src)
|
|
{
|
|
DEBUG_ERROR("D12 backend failed to produce an expected frame: %u",
|
|
frameBufferIndex);
|
|
goto exit;
|
|
}
|
|
|
|
void * map;
|
|
comRef_defineLocal(ID3D12Resource, dst)
|
|
DEBUG_TRACE("d12_frameBufferToResource");
|
|
*dst = d12_frameBufferToResource(frameBufferIndex, frameBuffer, maxFrameSize,
|
|
&map);
|
|
if (!*dst)
|
|
goto exit;
|
|
|
|
// place a fence into the queue
|
|
DEBUG_TRACE("d12_backendSync");
|
|
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;
|
|
|
|
DEBUG_TRACE("d12_effectRun: %s", effect->name);
|
|
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
|
|
}
|
|
}
|
|
};
|
|
|
|
// Queue waits run before this command list. This timestamp is therefore the
|
|
// boundary between source/effect readiness and the framebuffer copy.
|
|
if (this->copyTiming.supported)
|
|
ID3D12GraphicsCommandList_EndQuery(
|
|
*this->copyCommand.gfxList,
|
|
*this->copyTiming.heap,
|
|
D3D12_QUERY_TYPE_TIMESTAMP,
|
|
0);
|
|
|
|
// if full frame damage
|
|
if (desc.nbDirtyRects == 0)
|
|
{
|
|
DEBUG_TRACE("Full frame damage");
|
|
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)
|
|
{
|
|
DEBUG_TRACE("Full frame update");
|
|
|
|
/* the prior frame was fully damaged, we must update everything */
|
|
ID3D12GraphicsCommandList_CopyTextureRegion(
|
|
*this->copyCommand.gfxList, &dstLoc, 0, 0, 0, &srcLoc, NULL);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_TRACE("Damage aware update");
|
|
|
|
/* 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[rectCount++] = (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[rectCount++] = (FrameDamageRect){
|
|
.x = rect->left,
|
|
.y = rect->top,
|
|
.width = rect->right - rect->left,
|
|
.height = rect->bottom - rect->top
|
|
};
|
|
|
|
/* resolve the rects */
|
|
rectCount = rectsMergeOverlapping(allRects, rectCount);
|
|
|
|
/* copy all the rects */
|
|
for(FrameDamageRect * rect = allRects; rect < allRects + rectCount; ++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;
|
|
}
|
|
|
|
if (this->copyTiming.supported)
|
|
ID3D12GraphicsCommandList_ResolveQueryData(
|
|
*this->copyCommand.gfxList,
|
|
*this->copyTiming.heap,
|
|
D3D12_QUERY_TYPE_TIMESTAMP,
|
|
0,
|
|
1,
|
|
*this->copyTiming.readback,
|
|
0);
|
|
|
|
// execute the compute commands
|
|
if (this->effectsActive)
|
|
{
|
|
DEBUG_TRACE("Execute compute commands");
|
|
d12_commandGroupExecute(*this->computeQueue, &this->computeCommand);
|
|
|
|
// insert a fence to wait for the compute commands to finish
|
|
DEBUG_TRACE("Fence wait");
|
|
ID3D12CommandQueue_Wait(*this->copyQueue,
|
|
*this->computeCommand.fence, this->computeCommand.fenceValue);
|
|
}
|
|
|
|
const uint64_t fallbackCopyStart = nanotime();
|
|
|
|
// execute the copy commands
|
|
DEBUG_TRACE("Execute copy commands");
|
|
d12_commandGroupExecute(*this->copyQueue, &this->copyCommand);
|
|
|
|
// wait for the copy to complete
|
|
DEBUG_TRACE("Fence wait");
|
|
d12_commandGroupWait(&this->copyCommand);
|
|
|
|
const uint64_t copyComplete = nanotime();
|
|
uint64_t copyStart;
|
|
if (!d12_copyTimingGetStart(©Start) ||
|
|
copyStart < postProcessStart || copyStart > copyComplete)
|
|
copyStart = fallbackCopyStart;
|
|
|
|
// The caller derives Copy from its getFrame wall time minus Post, preserving
|
|
// the exact producer total while moving the real queue wait to Post.
|
|
captureFrame->postProcessTime = copyStart - postProcessStart;
|
|
|
|
if (this->indirectCopy)
|
|
{
|
|
if (rectCount == 0)
|
|
{
|
|
framebuffer_write(frameBuffer, map,
|
|
this->pitch * this->dstFormat.desc.Height);
|
|
}
|
|
else
|
|
{
|
|
/* copy all the rects */
|
|
rectsBufferToFramebuffer(allRects, rectCount, this->bpp, frameBuffer,
|
|
this->pitch, this->dstFormat.desc.Height, map, this->pitch);
|
|
|
|
// signal the frame is complete
|
|
framebuffer_set_write_ptr(frameBuffer,
|
|
this->dstFormat.desc.Height * this->pitch);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// signal the frame is complete
|
|
framebuffer_set_write_ptr(frameBuffer,
|
|
this->dstFormat.desc.Height * this->pitch);
|
|
}
|
|
|
|
// reset the command queues
|
|
if (this->effectsActive)
|
|
{
|
|
DEBUG_TRACE("Reset compute command group");
|
|
if (!d12_commandGroupReset(&this->computeCommand))
|
|
goto exit;
|
|
}
|
|
|
|
DEBUG_TRACE("Reset copy command group");
|
|
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; ; ++n, comRef_release(output))
|
|
{
|
|
HRESULT hr = IDXGIAdapter1_EnumOutputs(*adapter, n, output);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
if (hr != DXGI_ERROR_NOT_FOUND)
|
|
DEBUG_WINERROR("Failed to IDXGIAdapter::EnumOutputs", hr);
|
|
break;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* the above may succeed even if there was a fault, as such we also need to
|
|
* check if the device was removed */
|
|
hr = ID3D12Device3_GetDeviceRemovedReason(device);
|
|
if (hr != S_OK)
|
|
{
|
|
DEBUG_WINERROR("Device Removed", hr);
|
|
goto exit;
|
|
}
|
|
|
|
result = true;
|
|
|
|
exit:
|
|
comRef_scopePop();
|
|
return result;
|
|
}
|
|
|
|
static ID3D12Resource * d12_frameBufferToResource(unsigned frameBufferIndex,
|
|
FrameBuffer * frameBuffer, unsigned size, void ** map)
|
|
{
|
|
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;
|
|
*map = fb->map;
|
|
ID3D12Resource_AddRef(result);
|
|
goto exit;
|
|
}
|
|
|
|
fb->size = size;
|
|
fb->frameBuffer = frameBuffer;
|
|
|
|
comRef_defineLocal(ID3D12Resource, resource);
|
|
HRESULT hr;
|
|
if (this->indirectCopy)
|
|
{
|
|
if (fb->map)
|
|
{
|
|
ID3D12Resource_Unmap(*fb->resource, 0, NULL);
|
|
fb->map = NULL;
|
|
}
|
|
|
|
D3D12_HEAP_PROPERTIES heapProps =
|
|
{
|
|
.Type = D3D12_HEAP_TYPE_READBACK,
|
|
.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
|
|
.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
|
|
.CreationNodeMask = 1,
|
|
.VisibleNodeMask = 1
|
|
};
|
|
|
|
D3D12_RESOURCE_DESC desc =
|
|
{
|
|
.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
|
|
.Alignment = 0,
|
|
.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_NONE
|
|
};
|
|
|
|
hr = ID3D12Device3_CreateCommittedResource(
|
|
*this->device,
|
|
&heapProps,
|
|
D3D12_HEAP_FLAG_NONE,
|
|
&desc,
|
|
D3D12_RESOURCE_STATE_COPY_DEST,
|
|
NULL,
|
|
&IID_ID3D12Resource,
|
|
(void **)resource);
|
|
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_WINERROR("Failed to create the intermediate ID3D12Resource", hr);
|
|
goto exit;
|
|
}
|
|
|
|
fb->map = NULL;
|
|
D3D12_RANGE range = {0, 0};
|
|
HRESULT hr = ID3D12Resource_Map(*resource, 0, &range, &fb->map);
|
|
if (FAILED(hr) || !fb->map)
|
|
{
|
|
DEBUG_WINERROR("Failed to map readback resource", hr);
|
|
goto exit;
|
|
}
|
|
|
|
*map = fb->map;
|
|
}
|
|
else
|
|
{
|
|
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
|
|
};
|
|
|
|
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
|
|
};
|