mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 08:41:31 +00:00
[idd] capture: split software and hardware frame processors
Move cadence-aware hardware capture and immediate software capture behind a common frame processor interface. Keep shared damage tracking and frame-buffer ownership in the base processor, and move stateless format, resource, and rectangle helpers into CFrameProcessorUtil. Decouple frame-buffer resources from CSwapChainProcessor by passing the device dependencies they use directly.
This commit is contained in:
364
idd/LGIdd/CSoftwareFrameProcessor.cpp
Normal file
364
idd/LGIdd/CSoftwareFrameProcessor.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "CSoftwareFrameProcessor.h"
|
||||
#include "CFrameProcessorUtil.h"
|
||||
#include "CDebug.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
CSoftwareFrameProcessor::CSoftwareFrameProcessor(
|
||||
CIndirectDeviceContext * device, std::shared_ptr<CD3D12Device> dx12,
|
||||
CPostProcessor postProcessors[LGMP_Q_FRAME_LEN],
|
||||
SRWLOCK * pipelineLock, HANDLE terminateEvent) :
|
||||
CFrameProcessor(device, std::move(dx12), postProcessors,
|
||||
pipelineLock, terminateEvent),
|
||||
m_directTexture(
|
||||
!m_dx12->IsIndirectCopy() && m_dx12->CanUseIVSHMEMTexture())
|
||||
{
|
||||
}
|
||||
|
||||
void CSoftwareFrameProcessor::CompletionFunction(
|
||||
CD3D12CommandSlot * slot, bool result, void * param1, void * param2)
|
||||
{
|
||||
auto processor = static_cast<CSoftwareFrameProcessor *>(param1);
|
||||
auto fbRes = static_cast<CFrameBufferResource *>(param2);
|
||||
fbRes->MarkCompletion();
|
||||
|
||||
if (!result)
|
||||
{
|
||||
processor->m_device->FailFrameBuffer(fbRes->GetFrameIndex());
|
||||
processor->SetFullDamage();
|
||||
processor->m_device->ForceFrame();
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t indirectCopyTime = 0;
|
||||
if (processor->m_dx12->IsIndirectCopy())
|
||||
{
|
||||
const uint64_t indirectCopyStart = CFrameScheduler::Nanotime();
|
||||
if (fbRes->IsFullCopy())
|
||||
processor->m_device->WriteFrameBuffer(fbRes->GetFrameIndex(),
|
||||
fbRes->GetMap(), 0, fbRes->GetFrameSize(), false);
|
||||
else
|
||||
{
|
||||
const unsigned pitch = fbRes->GetCopyPitch();
|
||||
const unsigned bytesPerPixel = fbRes->GetCopyBytesPerPixel();
|
||||
const RECT * dirtyRects = fbRes->GetCopyDirtyRects();
|
||||
const unsigned count = fbRes->GetCopyDirtyRectCount();
|
||||
for (const RECT * rect = dirtyRects; rect < dirtyRects + count; ++rect)
|
||||
{
|
||||
const size_t rowOffset =
|
||||
(size_t)rect->top * pitch +
|
||||
(size_t)rect->left * bytesPerPixel;
|
||||
const size_t rowBytes =
|
||||
(size_t)(rect->right - rect->left) * bytesPerPixel;
|
||||
processor->m_device->WriteFrameBufferRows(fbRes->GetFrameIndex(),
|
||||
fbRes->GetMap(), rowOffset, rowBytes, pitch,
|
||||
(unsigned)(rect->bottom - rect->top));
|
||||
}
|
||||
}
|
||||
indirectCopyTime = CFrameScheduler::Nanotime() - indirectCopyStart;
|
||||
}
|
||||
|
||||
uint64_t gpuStart = 0;
|
||||
uint64_t gpuEnd = 0;
|
||||
const uint64_t copyReady = CFrameScheduler::Nanotime();
|
||||
const bool gpuTimingValid = slot->GetGPUTimes(gpuStart, gpuEnd);
|
||||
|
||||
processor->m_device->FinalizeFrameBuffer(fbRes->GetFrameIndex());
|
||||
const uint64_t publishedAt = CFrameScheduler::Nanotime();
|
||||
const uint64_t postProcessStart = fbRes->GetPostProcessStart();
|
||||
const uint64_t copyStart = fbRes->GetCopyStart();
|
||||
uint64_t postProcessTime = copyStart >= postProcessStart ?
|
||||
copyStart - postProcessStart : 0;
|
||||
uint64_t copyTime = copyReady >= copyStart ?
|
||||
copyReady - copyStart : 0;
|
||||
if (gpuTimingValid && gpuStart >= postProcessStart &&
|
||||
gpuEnd >= gpuStart && gpuEnd <= copyReady)
|
||||
{
|
||||
postProcessTime = gpuStart - postProcessStart;
|
||||
copyTime = gpuEnd - gpuStart + indirectCopyTime;
|
||||
}
|
||||
|
||||
const uint64_t elapsed = publishedAt >= postProcessStart ?
|
||||
publishedAt - postProcessStart : 0;
|
||||
const uint64_t measured = postProcessTime + copyTime;
|
||||
const uint64_t readyTime = elapsed > measured ? elapsed - measured : 0;
|
||||
|
||||
processor->m_device->SetFrameTiming(fbRes->GetFrameIndex(),
|
||||
fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime, 0,
|
||||
fbRes->GetSchedule(), publishedAt);
|
||||
processor->m_device->CompleteFrameBuffer(fbRes->GetFrameIndex(), true);
|
||||
}
|
||||
|
||||
bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission)
|
||||
{
|
||||
CFrameProcessorSharedLock pipelineLock(m_pipelineLock);
|
||||
CPostProcessor& postProcessor = m_postProcessors[0];
|
||||
const D12FrameFormat& dstFormat = postProcessor.GetOutputFormat();
|
||||
|
||||
D3D12_RESOURCE_DESC textureDesc = {};
|
||||
const D3D12_RESOURCE_DESC * textureDescPtr = nullptr;
|
||||
unsigned pitch = postProcessor.GetOutputPitch();
|
||||
size_t frameSize = postProcessor.GetOutputSize();
|
||||
if (m_directTexture &&
|
||||
dstFormat.desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D &&
|
||||
dstFormat.desc.Width && dstFormat.desc.Height &&
|
||||
dstFormat.desc.Format != DXGI_FORMAT_UNKNOWN)
|
||||
{
|
||||
textureDesc = dstFormat.desc;
|
||||
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
textureDesc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
|
||||
textureDesc.DepthOrArraySize = 1;
|
||||
textureDesc.MipLevels = 1;
|
||||
textureDesc.SampleDesc.Count = 1;
|
||||
textureDesc.SampleDesc.Quality = 0;
|
||||
textureDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||
textureDesc.Flags =
|
||||
D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER;
|
||||
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout = {};
|
||||
m_dx12->GetDevice()->GetCopyableFootprints(
|
||||
&textureDesc, 0, 1, 0, &layout, nullptr, nullptr, nullptr);
|
||||
const unsigned texturePitch = layout.Footprint.RowPitch;
|
||||
if (texturePitch && textureDesc.Height <=
|
||||
m_device->GetMaxFrameSize() / texturePitch)
|
||||
{
|
||||
pitch = texturePitch;
|
||||
frameSize = (size_t)pitch * textureDesc.Height;
|
||||
textureDescPtr = &textureDesc;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_directTexture = false;
|
||||
DEBUG_WARN("IVSHMEM texture layout does not fit the framebuffer");
|
||||
}
|
||||
}
|
||||
else if (m_directTexture)
|
||||
{
|
||||
m_directTexture = false;
|
||||
DEBUG_WARN("Post-processor output cannot use an IVSHMEM texture");
|
||||
}
|
||||
|
||||
if (!pitch || !frameSize || frameSize > m_device->GetMaxFrameSize())
|
||||
{
|
||||
DEBUG_ERROR("Software frame does not fit in shared memory");
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (submission.noImageUpdate && !HasPendingDamage())
|
||||
return true;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
CFrameScheduler::Schedule commitSchedule = {};
|
||||
CFrameScheduler::Schedule deliverySchedule = {};
|
||||
CIndirectDeviceContext::PreparedFrameBuffer buffer = {};
|
||||
CD3D12CommandSlot * copySlot = nullptr;
|
||||
RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {};
|
||||
unsigned nbDirtyRects = 0;
|
||||
bool hasDamage = false;
|
||||
|
||||
uint64_t ignoredTarget = 0;
|
||||
bool ignoredPeriodic = false;
|
||||
bool ignoredRepublish = false;
|
||||
m_device->GetPublishTarget(CFrameScheduler::Nanotime(),
|
||||
ignoredTarget, commitSchedule, ignoredPeriodic, ignoredRepublish);
|
||||
deliverySchedule = commitSchedule;
|
||||
deliverySchedule.deliveryDeadlineSerial = 0;
|
||||
deliverySchedule.phaseEligible = false;
|
||||
|
||||
m_device->ProcessFrameQueue();
|
||||
if (!m_device->FrameBufferAvailable(
|
||||
deliverySchedule, submission.noImageUpdate))
|
||||
{
|
||||
if (!submission.noImageUpdate)
|
||||
{
|
||||
m_device->FrameSuperseded();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (WaitForSingleObject(m_terminateEvent, 1) == WAIT_OBJECT_0)
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
copySlot = m_dx12->GetCopySlot();
|
||||
if (!copySlot)
|
||||
{
|
||||
if (!submission.noImageUpdate)
|
||||
{
|
||||
m_device->FrameSuperseded();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (WaitForSingleObject(m_terminateEvent, 1) == WAIT_OBJECT_0)
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
hasDamage = TakePendingDamage(currentDirtyRects, &nbDirtyRects);
|
||||
CFrameProcessorUtil::ClipDirtyRects(
|
||||
currentDirtyRects, &nbDirtyRects,
|
||||
dstFormat.width, dstFormat.height);
|
||||
buffer = m_device->PrepareFrameBuffer(
|
||||
pitch, submission.sourceFormat, dstFormat,
|
||||
currentDirtyRects, nbDirtyRects, deliverySchedule,
|
||||
submission.noImageUpdate);
|
||||
if (!buffer.mem)
|
||||
{
|
||||
copySlot->Cancel();
|
||||
RestorePendingDamage(
|
||||
currentDirtyRects, nbDirtyRects, hasDamage);
|
||||
if (!submission.noImageUpdate)
|
||||
{
|
||||
m_device->FrameSuperseded();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (WaitForSingleObject(m_terminateEvent, 1) == WAIT_OBJECT_0)
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
CFrameBufferResource * fbRes = nullptr;
|
||||
if (textureDescPtr)
|
||||
{
|
||||
fbRes = m_frameBuffers.Get(buffer, frameSize, textureDescPtr);
|
||||
if (!fbRes)
|
||||
{
|
||||
const HRESULT deviceStatus =
|
||||
m_dx12->GetDevice()->GetDeviceRemovedReason();
|
||||
if (FAILED(deviceStatus))
|
||||
{
|
||||
copySlot->Cancel();
|
||||
m_device->AbortFrameBuffer(buffer.frameIndex);
|
||||
RestorePendingDamage(
|
||||
currentDirtyRects, nbDirtyRects, hasDamage);
|
||||
DEBUG_ERROR_HR(deviceStatus,
|
||||
"D3D12 device removed while creating an IVSHMEM texture");
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_directTexture = false;
|
||||
textureDescPtr = nullptr;
|
||||
DEBUG_WARN(
|
||||
"IVSHMEM textures unavailable; using a direct buffer copy");
|
||||
}
|
||||
}
|
||||
|
||||
if (!fbRes)
|
||||
fbRes = m_frameBuffers.Get(buffer, frameSize);
|
||||
if (!fbRes)
|
||||
{
|
||||
copySlot->Cancel();
|
||||
m_device->AbortFrameBuffer(buffer.frameIndex);
|
||||
RestorePendingDamage(
|
||||
currentDirtyRects, nbDirtyRects, hasDamage);
|
||||
DEBUG_ERROR("Failed to get a framebuffer for software capture");
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!submission.source->Signal() ||
|
||||
!submission.source->Sync(*copySlot))
|
||||
{
|
||||
copySlot->Cancel();
|
||||
m_device->AbortFrameBuffer(buffer.frameIndex);
|
||||
RestorePendingDamage(
|
||||
currentDirtyRects, nbDirtyRects, hasDamage);
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT previousDirtyRects[LG_MAX_DIRTY_RECTS] = {};
|
||||
unsigned nbPreviousDirtyRects = 0;
|
||||
GetPreviousDamage(previousDirtyRects, &nbPreviousDirtyRects);
|
||||
|
||||
RECT copyDirtyRects[LG_MAX_DIRTY_RECTS * 2] = {};
|
||||
unsigned nbCopyDirtyRects = 0;
|
||||
const bool fullCopy = CFrameProcessorUtil::BuildCopyDamage(
|
||||
postProcessor, buffer.fullCopy,
|
||||
previousDirtyRects, nbPreviousDirtyRects,
|
||||
currentDirtyRects, nbDirtyRects,
|
||||
dstFormat.width, dstFormat.height,
|
||||
copyDirtyRects, &nbCopyDirtyRects);
|
||||
|
||||
const unsigned bytesPerPixel =
|
||||
dstFormat.format == FRAME_TYPE_RGBA16F ? 8 : 4;
|
||||
const uint64_t copyStart = CFrameScheduler::Nanotime();
|
||||
fbRes->SetTiming(submission.captureTime,
|
||||
submission.postProcessStart, copyStart);
|
||||
fbRes->SetSchedule(deliverySchedule);
|
||||
fbRes->SetCopyDamage(copyDirtyRects, nbCopyDirtyRects,
|
||||
fullCopy, pitch, bytesPerPixel);
|
||||
fbRes->ResetCompletion();
|
||||
copySlot->SetCompletionCallback(
|
||||
&CompletionFunction, this, fbRes);
|
||||
copySlot->BeginTiming();
|
||||
postProcessor.CopyToFrameBuffer(copySlot->GetGfxList(),
|
||||
fbRes->Get().Get(), submission.source->GetRes().Get(),
|
||||
copyDirtyRects, nbCopyDirtyRects, fullCopy);
|
||||
copySlot->EndTiming();
|
||||
|
||||
bool deliveredToOwner;
|
||||
if (!m_device->PublishFrameBuffer(
|
||||
buffer.frameIndex, deliverySchedule, deliveredToOwner))
|
||||
{
|
||||
copySlot->Cancel();
|
||||
m_device->AbortFrameBuffer(buffer.frameIndex);
|
||||
RestorePendingDamage(
|
||||
currentDirtyRects, nbDirtyRects, hasDamage);
|
||||
if (!submission.noImageUpdate)
|
||||
{
|
||||
m_device->FrameSuperseded();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (WaitForSingleObject(m_terminateEvent, 1) == WAIT_OBJECT_0)
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
CommitDamage(currentDirtyRects, nbDirtyRects);
|
||||
if (!copySlot->Execute())
|
||||
{
|
||||
const bool submittedWork = copySlot->HasSubmittedWork();
|
||||
const bool completionHandled = fbRes->CompletionHandled();
|
||||
if (!submittedWork && !completionHandled)
|
||||
m_device->FailFrameBuffer(buffer.frameIndex);
|
||||
RestorePendingDamage(
|
||||
currentDirtyRects, nbDirtyRects, hasDamage);
|
||||
if (!submittedWork && !completionHandled)
|
||||
{
|
||||
SetFullDamage();
|
||||
m_device->ForceFrame();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
m_device->CommitFrameBuffer(
|
||||
buffer.frameIndex, commitSchedule, false, deliveredToOwner);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user