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

View File

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