From 9de047d9cbc25fb657e84af8af4b74c01784b387 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 23 Feb 2024 10:54:08 +1100 Subject: [PATCH] [host] d12: implement damage aware copy --- host/platform/Windows/capture/D12/backend.h | 23 +++- .../platform/Windows/capture/D12/backend/dd.c | 78 ++++++++++- host/platform/Windows/capture/D12/d12.c | 123 ++++++++++++++++-- 3 files changed, 209 insertions(+), 15 deletions(-) diff --git a/host/platform/Windows/capture/D12/backend.h b/host/platform/Windows/capture/D12/backend.h index 59010db9..cf32f4ff 100644 --- a/host/platform/Windows/capture/D12/backend.h +++ b/host/platform/Windows/capture/D12/backend.h @@ -25,6 +25,8 @@ #include #include "interface/capture.h" +#define D12_MAX_DIRTY_RECTS 256 + typedef struct D12Backend D12Backend; struct D12Backend @@ -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)( @@ -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, @@ -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); } @@ -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 diff --git a/host/platform/Windows/capture/D12/backend/dd.c b/host/platform/Windows/capture/D12/backend/dd.c index 1eb3a4f1..d7d58f20 100644 --- a/host/platform/Windows/capture/D12/backend/dd.c +++ b/host/platform/Windows/capture/D12/backend/dd.c @@ -51,6 +51,9 @@ typedef struct DDCacheInfo ID3D12Fence ** d12Fence; UINT64 fenceValue; bool ready; + + RECT dirtyRects[D12_MAX_DIRTY_RECTS]; + unsigned nbDirtyRects; } DDCacheInfo; @@ -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; } @@ -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: diff --git a/host/platform/Windows/capture/D12/d12.c b/host/platform/Windows/capture/D12/d12.c index c1f9af18..12d3b50d 100644 --- a/host/platform/Windows/capture/D12/d12.c +++ b/host/platform/Windows/capture/D12/d12.c @@ -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; @@ -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", @@ -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} }; @@ -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) { @@ -190,8 +218,6 @@ static bool d12_create( this->frameBufferCount = frameBuffers; - this->allowRGB24 = option_get_bool("d12", "allowRGB24"); - return true; } @@ -318,7 +344,8 @@ retryCreateCommandQueue: *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) @@ -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", @@ -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; @@ -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", @@ -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)