[host] d12: implement damage aware copy

This commit is contained in:
Geoffrey McRae 2024-02-23 10:54:08 +11:00
parent 1098b7e6bd
commit 9de047d9cb
3 changed files with 209 additions and 15 deletions

View File

@ -25,6 +25,8 @@
#include <d3d12.h> #include <d3d12.h>
#include "interface/capture.h" #include "interface/capture.h"
#define D12_MAX_DIRTY_RECTS 256
typedef struct D12Backend D12Backend; typedef struct D12Backend D12Backend;
struct D12Backend struct D12Backend
@ -35,6 +37,9 @@ struct D12Backend
// internal name // internal name
const char * codeName; const char * codeName;
// enable damage tracking
bool trackDamage;
// creation/init/free // creation/init/free
bool (*create)(D12Backend ** instance, unsigned frameBuffers); bool (*create)(D12Backend ** instance, unsigned frameBuffers);
bool (*init)( bool (*init)(
@ -54,7 +59,9 @@ struct D12Backend
ID3D12CommandQueue * commandQueue); ID3D12CommandQueue * commandQueue);
ID3D12Resource * (*fetch)(D12Backend * instance, ID3D12Resource * (*fetch)(D12Backend * instance,
unsigned frameBufferIndex); unsigned frameBufferIndex,
const RECT * dirtyRects[static D12_MAX_DIRTY_RECTS],
unsigned * nbDirtyRects);
}; };
static inline bool d12_backendCreate(const D12Backend * backend, static inline bool d12_backendCreate(const D12Backend * backend,
@ -67,8 +74,12 @@ static inline bool d12_backendCreate(const D12Backend * backend,
} }
static inline bool d12_backendInit(D12Backend * instance, bool debug, static inline bool d12_backendInit(D12Backend * instance, bool debug,
ID3D12Device3 * device, IDXGIAdapter1 * adapter, IDXGIOutput * output) ID3D12Device3 * device, IDXGIAdapter1 * adapter, IDXGIOutput * output,
{ return instance->init(instance, debug, device, adapter, output); } bool trackDamage)
{
instance->trackDamage = trackDamage;
return instance->init(instance, debug, device, adapter, output);
}
static inline bool d12_backendDeinit(D12Backend * instance) static inline bool d12_backendDeinit(D12Backend * instance)
{ return instance->deinit(instance); } { return instance->deinit(instance); }
@ -85,8 +96,10 @@ static inline CaptureResult d12_backendSync(D12Backend * instance,
{ return instance->sync(instance, commandQueue); } { return instance->sync(instance, commandQueue); }
static inline ID3D12Resource * d12_backendFetch(D12Backend * instance, static inline ID3D12Resource * d12_backendFetch(D12Backend * instance,
unsigned frameBufferIndex) unsigned frameBufferIndex, const RECT * dirtyRects[static D12_MAX_DIRTY_RECTS],
{ return instance->fetch(instance, frameBufferIndex); } unsigned * nbDirtyRects)
{ return instance->fetch(instance, frameBufferIndex, dirtyRects,
nbDirtyRects); }
// Backend defines // Backend defines

View File

@ -51,6 +51,9 @@ typedef struct DDCacheInfo
ID3D12Fence ** d12Fence; ID3D12Fence ** d12Fence;
UINT64 fenceValue; UINT64 fenceValue;
bool ready; bool ready;
RECT dirtyRects[D12_MAX_DIRTY_RECTS];
unsigned nbDirtyRects;
} }
DDCacheInfo; DDCacheInfo;
@ -416,13 +419,17 @@ static CaptureResult d12_dd_sync(D12Backend * instance,
} }
static ID3D12Resource * d12_dd_fetch(D12Backend * instance, static ID3D12Resource * d12_dd_fetch(D12Backend * instance,
unsigned frameBufferIndex) unsigned frameBufferIndex, const RECT * dirtyRects[static D12_MAX_DIRTY_RECTS],
unsigned * nbDirtyRects)
{ {
DDInstance * this = UPCAST(DDInstance, instance); DDInstance * this = UPCAST(DDInstance, instance);
if (!this->current) if (!this->current)
return NULL; return NULL;
*dirtyRects = this->current->dirtyRects;
*nbDirtyRects = this->current->nbDirtyRects;
ID3D12Resource_AddRef(*this->current->d12Res); ID3D12Resource_AddRef(*this->current->d12Res);
return *this->current->d12Res; return *this->current->d12Res;
} }
@ -478,6 +485,75 @@ static bool d12_dd_handleFrameUpdate(DDInstance * this, IDXGIResource * res)
ID3D11DeviceContext4_Signal( ID3D11DeviceContext4_Signal(
*this->context, *this->current->fence, this->current->fenceValue); *this->context, *this->current->fence, this->current->fenceValue);
// handle damage tracking
this->current->nbDirtyRects = 0;
if (this->base.trackDamage)
{
/* Get the frame damage, if there is too many damage rects, we disable
* damage tracking for the frame and assume full frame damage */
UINT requiredSize;
hr = IDXGIOutputDuplication_GetFrameDirtyRects(*this->dup,
sizeof(this->current->dirtyRects),
this->current->dirtyRects,
&requiredSize);
if (FAILED(hr))
{
if (hr != DXGI_ERROR_MORE_DATA)
{
DEBUG_WINERROR("GetFrameDirtyRects failed", hr);
goto exit;
}
}
else
this->current->nbDirtyRects =
requiredSize / sizeof(*this->current->dirtyRects);
DXGI_OUTDUPL_MOVE_RECT moveRects[
(ARRAY_LENGTH(this->current->dirtyRects) - this->current->nbDirtyRects) / 2
];
hr = IDXGIOutputDuplication_GetFrameMoveRects(*this->dup,
sizeof(moveRects), moveRects, &requiredSize);
if (FAILED(hr))
{
this->current->nbDirtyRects = 0;
if (hr != DXGI_ERROR_MORE_DATA)
{
DEBUG_WINERROR("GetFrameMoveRects failed", hr);
goto exit;
}
}
/* Move rects are seemingly not generated on Windows 10, but incase it
* becomes a thing in the future we still need to implement this */
const unsigned moveRectCount = requiredSize / sizeof(*moveRects);
for(DXGI_OUTDUPL_MOVE_RECT *moveRect = moveRects; moveRect < moveRects +
moveRectCount; ++moveRect)
{
/* According to WebRTC source comments, the DirectX capture API may
* randomly return unmoved rects, which should be skipped to avoid
* unnecessary work */
if (moveRect->SourcePoint.x == moveRect->DestinationRect.left &&
moveRect->SourcePoint.y == moveRect->DestinationRect.top)
continue;
/* Add the source rect to the dirty array */
this->current->dirtyRects[this->current->nbDirtyRects++] = (RECT)
{
.left = moveRect->SourcePoint.x,
.top = moveRect->SourcePoint.y,
.right = moveRect->SourcePoint.x +
(moveRect->DestinationRect.right - moveRect->DestinationRect.left),
.bottom = moveRect->SourcePoint.y +
(moveRect->DestinationRect.bottom - moveRect->DestinationRect.top)
};
/* Add the destination rect to the dirty array */
this->current->dirtyRects[this->current->nbDirtyRects++] =
moveRect->DestinationRect;
}
}
result = true; result = true;
exit: exit:

View File

@ -65,8 +65,13 @@ struct D12Interface
// output format tracking // output format tracking
D3D12_RESOURCE_DESC dstFormat; D3D12_RESOURCE_DESC dstFormat;
// prior frame dirty rects
RECT dirtyRects[D12_MAX_DIRTY_RECTS];
unsigned nbDirtyRects;
// options // options
bool debug; bool debug;
bool trackDamage;
bool allowRGB24; bool allowRGB24;
unsigned frameBufferCount; unsigned frameBufferCount;
@ -131,6 +136,13 @@ static void d12_initOptions(void)
.type = OPTION_TYPE_STRING, .type = OPTION_TYPE_STRING,
.value.x_string = NULL .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", .module = "d12",
.name = "allowRGB24", .name = "allowRGB24",
@ -139,6 +151,13 @@ static void d12_initOptions(void)
.type = OPTION_TYPE_BOOL, .type = OPTION_TYPE_BOOL,
.value.x_bool = false .value.x_bool = false
}, },
{
.module = "d12",
.name = "debug",
.description = "Enable DirectX12 debugging and validation (SLOW!)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{0} {0}
}; };
@ -158,7 +177,16 @@ static bool d12_create(
return false; return false;
} }
this->debug = false; this->debug = option_get_bool("d12", "debug" );
this->trackDamage = option_get_bool("d12", "trackDamage");
this->allowRGB24 = option_get_bool("d12", "allowRGB24" );
DEBUG_INFO(
"debug:%d trackDamage:%d allowRGB24:%d",
this->debug,
this->trackDamage,
this->allowRGB24);
this->d3d12 = LoadLibrary("d3d12.dll"); this->d3d12 = LoadLibrary("d3d12.dll");
if (!this->d3d12) if (!this->d3d12)
{ {
@ -190,8 +218,6 @@ static bool d12_create(
this->frameBufferCount = frameBuffers; this->frameBufferCount = frameBuffers;
this->allowRGB24 = option_get_bool("d12", "allowRGB24");
return true; return true;
} }
@ -318,7 +344,8 @@ retryCreateCommandQueue:
*alignSize = heapDesc.Alignment; *alignSize = heapDesc.Alignment;
// initialize the backend // initialize the backend
if (!d12_backendInit(this->backend, this->debug, *device, *adapter, *output)) if (!d12_backendInit(this->backend, this->debug, *device, *adapter, *output,
this->trackDamage))
goto exit; goto exit;
if (this->allowRGB24) if (this->allowRGB24)
@ -396,8 +423,12 @@ static CaptureResult d12_waitFrame(unsigned frameBufferIndex,
CaptureResult result = CAPTURE_RESULT_ERROR; CaptureResult result = CAPTURE_RESULT_ERROR;
comRef_scopePush(1); comRef_scopePush(1);
const RECT * dirtyRects;
unsigned nbDirtyRects;
comRef_defineLocal(ID3D12Resource, src); comRef_defineLocal(ID3D12Resource, src);
*src = d12_backendFetch(this->backend, frameBufferIndex); *src = d12_backendFetch(this->backend, frameBufferIndex,
&dirtyRects, &nbDirtyRects);
if (!*src) if (!*src)
{ {
DEBUG_ERROR("D12 backend failed to produce an expected frame: %u", DEBUG_ERROR("D12 backend failed to produce an expected frame: %u",
@ -457,7 +488,23 @@ static CaptureResult d12_waitFrame(unsigned frameBufferIndex,
frame->hdr = false; frame->hdr = false;
frame->hdrPQ = false; frame->hdrPQ = false;
frame->rotation = CAPTURE_ROT_0; frame->rotation = CAPTURE_ROT_0;
frame->damageRectsCount = 0;
// if there are too many rects
if (unlikely(nbDirtyRects > ARRAY_LENGTH(frame->damageRects)))
frame->damageRectsCount = 0;
else
{
// send the list of dirty rects for this frame
frame->damageRectsCount = nbDirtyRects;
for(unsigned i = 0; i < nbDirtyRects; ++i)
frame->damageRects[i] = (FrameDamageRect)
{
.x = dirtyRects[i].left,
.y = dirtyRects[i].top,
.width = dirtyRects[i].right - dirtyRects[i].left,
.height = dirtyRects[i].bottom - dirtyRects[i].top
};
}
result = CAPTURE_RESULT_OK; result = CAPTURE_RESULT_OK;
@ -472,8 +519,12 @@ static CaptureResult d12_getFrame(unsigned frameBufferIndex,
CaptureResult result = CAPTURE_RESULT_ERROR; CaptureResult result = CAPTURE_RESULT_ERROR;
comRef_scopePush(3); comRef_scopePush(3);
const RECT * dirtyRects;
unsigned nbDirtyRects;
comRef_defineLocal(ID3D12Resource, src); comRef_defineLocal(ID3D12Resource, src);
*src = d12_backendFetch(this->backend, frameBufferIndex); *src = d12_backendFetch(this->backend, frameBufferIndex,
&dirtyRects, &nbDirtyRects);
if (!*src) if (!*src)
{ {
DEBUG_ERROR("D12 backend failed to produce an expected frame: %u", DEBUG_ERROR("D12 backend failed to produce an expected frame: %u",
@ -526,8 +577,62 @@ static CaptureResult d12_getFrame(unsigned frameBufferIndex,
} }
}; };
ID3D12GraphicsCommandList_CopyTextureRegion( // if full frame damage
*this->copyCommand.gfxList, &dstLoc, 0, 0, 0, &srcLoc, NULL); if (nbDirtyRects == 0)
{
this->nbDirtyRects = 0;
ID3D12GraphicsCommandList_CopyTextureRegion(
*this->copyCommand.gfxList, &dstLoc, 0, 0, 0, &srcLoc, NULL);
}
else
{
/* 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 */
if (this->nbDirtyRects > 0)
{
for(const RECT * rect = this->dirtyRects;
rect < this->dirtyRects + this->nbDirtyRects; ++rect)
{
D3D12_BOX box =
{
.left = rect->left,
.top = rect->top,
.front = 0,
.back = 1,
.right = rect->right,
.bottom = rect->bottom
};
ID3D12GraphicsCommandList_CopyTextureRegion(
*this->copyCommand.gfxList, &dstLoc,
box.left, box.top, 0, &srcLoc, &box);
}
}
/* update the frame with the new dirty areas */
for(const RECT * rect = dirtyRects; rect < dirtyRects + nbDirtyRects; ++rect)
{
D3D12_BOX box =
{
.left = rect->left,
.top = rect->top,
.front = 0,
.back = 1,
.right = rect->right,
.bottom = rect->bottom
};
ID3D12GraphicsCommandList_CopyTextureRegion(
*this->copyCommand.gfxList, &dstLoc,
box.left, box.top, 0, &srcLoc, &box);
}
/* store the dirty rects for the next frame */
memcpy(this->dirtyRects, dirtyRects,
nbDirtyRects * sizeof(*this->dirtyRects));
this->nbDirtyRects = nbDirtyRects;
}
// execute the compute commands // execute the compute commands
if (this->allowRGB24) if (this->allowRGB24)