[idd] capture: recover final desktop updates

Request one static-desktop re-encode and inspect repeated presentation
numbers for its empty dirty-rectangle marker.

Prepare every marker in a free candidate without superseding retained
damage, so the last desktop update survives a late copy failure.
This commit is contained in:
Geoffrey McRae
2026-08-06 14:52:37 +10:00
parent 909271bfe1
commit f65ecdaf5d
3 changed files with 82 additions and 43 deletions

View File

@@ -413,6 +413,7 @@ void CIndirectDeviceContext::InitAdapter()
#endif
caps.MaxMonitorsSupported = 1;
caps.StaticDesktopReencodeFrameCount = 1;
caps.EndPointDiagnostics.Size = sizeof(caps.EndPointDiagnostics);
caps.EndPointDiagnostics.GammaSupport = IDDCX_FEATURE_IMPLEMENTATION_NONE;

View File

@@ -90,6 +90,8 @@ CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContex
// once set or only one thread would ever observe termination.
m_terminateEvent.Attach(CreateEvent(nullptr, TRUE, FALSE, nullptr));
m_candidateEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr));
m_candidateAvailableEvent.Attach(
CreateEvent(nullptr, FALSE, FALSE, nullptr));
m_publishTimer.Attach(CreateWaitableTimerExW(nullptr, nullptr,
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS));
if (!m_publishTimer.Get())
@@ -102,7 +104,8 @@ CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContex
bool CSwapChainProcessor::Start()
{
if (!m_terminateEvent.Get() || !m_candidateEvent.Get() ||
!m_publishTimer.Get() || !m_cursorDataEvent.Get() || !m_shapeBuffer)
!m_candidateAvailableEvent.Get() || !m_publishTimer.Get() ||
!m_cursorDataEvent.Get() || !m_shapeBuffer)
{
DEBUG_ERROR("Failed to initialize swap chain worker resources");
return false;
@@ -517,6 +520,7 @@ void CSwapChainProcessor::SwapChainThreadCore()
g_pipe.SetGPUStatus(m_dx11Device->IsSoftware());
UINT lastFrameNumber = 0;
bool hasLastFrameNumber = false;
for (;;)
{
if (WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0)
@@ -591,13 +595,16 @@ void CSwapChainProcessor::SwapChainThreadCore()
}
else if (SUCCEEDED(hr))
{
if (frameNumber != lastFrameNumber)
const bool duplicateFrame =
hasLastFrameNumber && frameNumber == lastFrameNumber;
if (!duplicateFrame)
{
lastFrameNumber = frameNumber;
if (!SwapChainNewFrame(surface, dirtyRectCount, moveRegionCount,
colorSpace, sdrWhiteLevel, captureStart))
DEBUG_WARN("Failed to submit frame");
hasLastFrameNumber = true;
}
if (!SwapChainNewFrame(surface, dirtyRectCount, moveRegionCount,
colorSpace, sdrWhiteLevel, captureStart, duplicateFrame))
DEBUG_WARN("Failed to submit frame");
// Every acquired frame must be finished before the next acquire, even if
// its presentation number was a duplicate and no work was submitted.
@@ -944,7 +951,8 @@ void CSwapChainProcessor::AccumulateFrameDamage(
ReleaseSRWLockExclusive(&m_damageLock);
}
int CSwapChainProcessor::AcquireCandidate(bool exclusiveSample)
int CSwapChainProcessor::AcquireCandidate(
bool exclusiveSample, bool allowSupersede)
{
int selected = -1;
uint64_t oldest = UINT64_MAX;
@@ -978,7 +986,7 @@ int CSwapChainProcessor::AcquireCandidate(bool exclusiveSample)
// Preserve one completed fallback unless another candidate is already
// publishing. In that case its peer must remain available for new source
// frames instead of being frozen for the duration of the transport copy.
if (!exclusiveSample && selected < 0 &&
if (allowSupersede && !exclusiveSample && selected < 0 &&
readyCount > (publishing ? 0U : 1U))
for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i)
if (m_candidates[i].state == CANDIDATE_READY &&
@@ -1099,6 +1107,7 @@ void CSwapChainProcessor::ResetCandidates()
void CSwapChainProcessor::SignalCandidateState()
{
SetEvent(m_candidateEvent.Get());
SetEvent(m_candidateAvailableEvent.Get());
}
bool CSwapChainProcessor::PublishNewestCandidate(
@@ -1389,35 +1398,21 @@ bool CSwapChainProcessor::GetContentHDRMetadata(D12FrameFormat& format) const
bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer,
unsigned dirtyRectCount, unsigned moveRegionCount,
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel,
uint64_t captureStart)
uint64_t captureStart, bool duplicateFrame)
{
const uint64_t postProcessStart = CFrameScheduler::Nanotime();
const uint64_t captureTime = postProcessStart - captureStart;
ComPtr<ID3D11Texture2D> texture;
HRESULT hr = acquiredBuffer.As(&texture);
if (FAILED(hr))
{
DEBUG_ERROR_HR(hr, "Failed to obtain the ID3D11Texture2D from the acquiredBuffer");
SetFullPendingDamage();
return false;
}
CInteropResource * srcRes = m_resPool.Get(texture);
if (!srcRes)
{
DEBUG_ERROR("Failed to get a CInteropResource from the pool");
SetFullPendingDamage();
return false;
}
RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {0};
unsigned resolvedDirtyRectCount = 0;
bool fullDamage = false;
bool noImageUpdate = false;
HRESULT hr;
if (moveRegionCount || dirtyRectCount > ARRAYSIZE(dirtyRects))
{
// Move regions are not represented by the dirty rectangle list. Copy the
// full surface so the alternating destinations remain coherent.
srcRes->SetFullDamage();
fullDamage = true;
}
else
{
@@ -1430,7 +1425,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
if (FAILED(hr))
{
DEBUG_ERROR_HR(hr, "IddCxSwapChainGetDirtyRects Failed");
srcRes->SetFullDamage();
fullDamage = true;
}
else if (dirtyOut.DirtyRectOutCount == 1 &&
dirtyRects[0].left == 0 && dirtyRects[0].top == 0 &&
@@ -1439,12 +1434,39 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
// One empty rectangle is IddCx's static-desktop re-encode marker. It
// does not describe an image update and must not become full damage.
noImageUpdate = true;
srcRes->SetDirtyRects(nullptr, 0);
}
else
srcRes->SetDirtyRects(dirtyRects, dirtyOut.DirtyRectOutCount);
resolvedDirtyRectCount = dirtyOut.DirtyRectOutCount;
}
// Reencode frames reuse the preceding presentation number. Inspect their
// empty dirty rectangle above, but suppress every ordinary duplicate.
if (duplicateFrame && !noImageUpdate)
return true;
ComPtr<ID3D11Texture2D> texture;
hr = acquiredBuffer.As(&texture);
if (FAILED(hr))
{
DEBUG_ERROR_HR(hr,
"Failed to obtain the ID3D11Texture2D from the acquiredBuffer");
SetFullPendingDamage();
return false;
}
CInteropResource * srcRes = m_resPool.Get(texture);
if (!srcRes)
{
DEBUG_ERROR("Failed to get a CInteropResource from the pool");
SetFullPendingDamage();
return false;
}
if (fullDamage)
srcRes->SetFullDamage();
else
srcRes->SetDirtyRects(dirtyRects, resolvedDirtyRectCount);
D3D12_RESOURCE_DESC srcDesc = srcRes->GetRes()->GetDesc();
if (!noImageUpdate)
{
@@ -1619,16 +1641,30 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged)
m_devContext->ForceFrame();
if (noImageUpdate)
// Always prepare the requested static-desktop re-encode. An older
// publication can still fail after this frame is acquired, so deciding
// solely from the current pending-damage state can lose the final update.
int selectedCandidate = AcquireCandidate(timingToken != 0, !noImageUpdate);
while (selectedCandidate < 0 && noImageUpdate)
{
AcquireSRWLockShared(&m_damageLock);
const bool hasPendingDamage = m_hasPendingDamage;
ReleaseSRWLockShared(&m_damageLock);
if (!hasPendingDamage)
HANDLE waitHandles[] =
{
m_terminateEvent.Get(),
m_candidateAvailableEvent.Get(),
};
const DWORD waitResult = WaitForMultipleObjects(
ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE);
if (waitResult == WAIT_OBJECT_0)
return true;
if (waitResult != WAIT_OBJECT_0 + 1)
{
DEBUG_ERROR_HR(HRESULT_FROM_WIN32(GetLastError()),
"Failed while waiting for a frame candidate");
return false;
}
const int selectedCandidate = AcquireCandidate(timingToken != 0);
selectedCandidate = AcquireCandidate(timingToken != 0, false);
}
if (selectedCandidate < 0)
{
m_devContext->FrameSuperseded();
@@ -1636,6 +1672,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
}
const unsigned candidateIndex =
static_cast<unsigned>(selectedCandidate);
FrameCandidate& candidate = m_candidates[candidateIndex];
CSRWExclusiveLock pipelineLock(&m_pipelineLock);

View File

@@ -107,6 +107,7 @@ private:
Wrappers::HandleT<Wrappers::HandleTraits::HANDLENullTraits> m_thread[3];
Wrappers::Event m_terminateEvent;
Wrappers::Event m_candidateEvent;
Wrappers::Event m_candidateAvailableEvent;
Wrappers::HandleT<Wrappers::HandleTraits::HANDLENullTraits> m_publishTimer;
Wrappers::Event m_cursorDataEvent;
@@ -147,7 +148,7 @@ private:
const CFrameScheduler::Schedule& schedule, bool periodic,
uint64_t publishStart);
bool HasReadyCandidate();
int AcquireCandidate(bool exclusiveSample);
int AcquireCandidate(bool exclusiveSample, bool allowSupersede);
void ReleaseCandidate(unsigned candidateIndex);
bool EnsureCandidateResource(
unsigned candidateIndex, size_t frameSize);
@@ -171,7 +172,7 @@ private:
bool SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer,
unsigned dirtyRectCount, unsigned moveRegionCount,
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel,
uint64_t captureStart);
uint64_t captureStart, bool duplicateFrame);
public:
CSwapChainProcessor(CIndirectMonitorContext * monitorContext, UINT64 assignmentGeneration,