Skip to content

Commit

Permalink
[host] d12: implement damage aware copy
Browse files Browse the repository at this point in the history
  • Loading branch information
gnif committed Feb 22, 2024
1 parent 1098b7e commit 9de047d
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 15 deletions.
23 changes: 18 additions & 5 deletions host/platform/Windows/capture/D12/backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#include <d3d12.h>
#include "interface/capture.h"

#define D12_MAX_DIRTY_RECTS 256

typedef struct D12Backend D12Backend;

struct D12Backend
Expand All @@ -35,6 +37,9 @@ struct D12Backend
// internal name
const char * codeName;

// enable damage tracking
bool trackDamage;

// creation/init/free
bool (*create)(D12Backend ** instance, unsigned frameBuffers);
bool (*init)(
Expand All @@ -54,7 +59,9 @@ struct D12Backend
ID3D12CommandQueue * commandQueue);

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,
Expand All @@ -67,8 +74,12 @@ static inline bool d12_backendCreate(const D12Backend * backend,
}

static inline bool d12_backendInit(D12Backend * instance, bool debug,
ID3D12Device3 * device, IDXGIAdapter1 * adapter, IDXGIOutput * output)
{ return instance->init(instance, debug, device, adapter, output); }
ID3D12Device3 * device, IDXGIAdapter1 * adapter, IDXGIOutput * output,
bool trackDamage)
{
instance->trackDamage = trackDamage;
return instance->init(instance, debug, device, adapter, output);
}

static inline bool d12_backendDeinit(D12Backend * instance)
{ return instance->deinit(instance); }
Expand All @@ -85,8 +96,10 @@ static inline CaptureResult d12_backendSync(D12Backend * instance,
{ return instance->sync(instance, commandQueue); }

static inline ID3D12Resource * d12_backendFetch(D12Backend * instance,
unsigned frameBufferIndex)
{ return instance->fetch(instance, frameBufferIndex); }
unsigned frameBufferIndex, const RECT * dirtyRects[static D12_MAX_DIRTY_RECTS],
unsigned * nbDirtyRects)
{ return instance->fetch(instance, frameBufferIndex, dirtyRects,
nbDirtyRects); }

// Backend defines

Expand Down
78 changes: 77 additions & 1 deletion host/platform/Windows/capture/D12/backend/dd.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ typedef struct DDCacheInfo
ID3D12Fence ** d12Fence;
UINT64 fenceValue;
bool ready;

RECT dirtyRects[D12_MAX_DIRTY_RECTS];
unsigned nbDirtyRects;
}
DDCacheInfo;

Expand Down Expand Up @@ -416,13 +419,17 @@ static CaptureResult d12_dd_sync(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);

if (!this->current)
return NULL;

*dirtyRects = this->current->dirtyRects;
*nbDirtyRects = this->current->nbDirtyRects;

ID3D12Resource_AddRef(*this->current->d12Res);
return *this->current->d12Res;
}
Expand Down Expand Up @@ -478,6 +485,75 @@ static bool d12_dd_handleFrameUpdate(DDInstance * this, IDXGIResource * res)
ID3D11DeviceContext4_Signal(
*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;

exit:
Expand Down
123 changes: 114 additions & 9 deletions host/platform/Windows/capture/D12/d12.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,13 @@ struct D12Interface
// output format tracking
D3D12_RESOURCE_DESC dstFormat;

// prior frame dirty rects
RECT dirtyRects[D12_MAX_DIRTY_RECTS];
unsigned nbDirtyRects;

// options
bool debug;
bool trackDamage;
bool allowRGB24;

unsigned frameBufferCount;
Expand Down Expand Up @@ -131,6 +136,13 @@ static void d12_initOptions(void)
.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 = "allowRGB24",
Expand All @@ -139,6 +151,13 @@ static void d12_initOptions(void)
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{
.module = "d12",
.name = "debug",
.description = "Enable DirectX12 debugging and validation (SLOW!)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{0}
};

Expand All @@ -158,7 +177,16 @@ static bool d12_create(
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");
if (!this->d3d12)
{
Expand Down Expand Up @@ -190,8 +218,6 @@ static bool d12_create(

this->frameBufferCount = frameBuffers;

this->allowRGB24 = option_get_bool("d12", "allowRGB24");

return true;
}

Expand Down Expand Up @@ -318,7 +344,8 @@ static bool d12_init(void * ivshmemBase, unsigned * alignSize)
*alignSize = heapDesc.Alignment;

// 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;

if (this->allowRGB24)
Expand Down Expand Up @@ -396,8 +423,12 @@ static CaptureResult d12_waitFrame(unsigned frameBufferIndex,
CaptureResult result = CAPTURE_RESULT_ERROR;
comRef_scopePush(1);

const RECT * dirtyRects;
unsigned nbDirtyRects;

comRef_defineLocal(ID3D12Resource, src);
*src = d12_backendFetch(this->backend, frameBufferIndex);
*src = d12_backendFetch(this->backend, frameBufferIndex,
&dirtyRects, &nbDirtyRects);
if (!*src)
{
DEBUG_ERROR("D12 backend failed to produce an expected frame: %u",
Expand Down Expand Up @@ -457,7 +488,23 @@ static CaptureResult d12_waitFrame(unsigned frameBufferIndex,
frame->hdr = false;
frame->hdrPQ = false;
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;

Expand All @@ -472,8 +519,12 @@ static CaptureResult d12_getFrame(unsigned frameBufferIndex,
CaptureResult result = CAPTURE_RESULT_ERROR;
comRef_scopePush(3);

const RECT * dirtyRects;
unsigned nbDirtyRects;

comRef_defineLocal(ID3D12Resource, src);
*src = d12_backendFetch(this->backend, frameBufferIndex);
*src = d12_backendFetch(this->backend, frameBufferIndex,
&dirtyRects, &nbDirtyRects);
if (!*src)
{
DEBUG_ERROR("D12 backend failed to produce an expected frame: %u",
Expand Down Expand Up @@ -526,8 +577,62 @@ static CaptureResult d12_getFrame(unsigned frameBufferIndex,
}
};

ID3D12GraphicsCommandList_CopyTextureRegion(
*this->copyCommand.gfxList, &dstLoc, 0, 0, 0, &srcLoc, NULL);
// if full frame damage
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
if (this->allowRGB24)
Expand Down

0 comments on commit 9de047d

Please sign in to comment.