mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 08:41:31 +00:00
[idd] project: organize driver sources by responsibility
Group the IDD sources and Visual Studio filters by subsystem. Split the device and swap-chain implementations into focused units, rename the context classes, and reduce header coupling.
This commit is contained in:
51
idd/LGIdd/capture/CFrameBufferPool.cpp
Normal file
51
idd/LGIdd/capture/CFrameBufferPool.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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 "capture/CFrameBufferPool.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void CFrameBufferPool::Init(
|
||||
CFrameTransport * transport, CD3D12Device * dx12)
|
||||
{
|
||||
m_transport = transport;
|
||||
m_dx12 = dx12;
|
||||
}
|
||||
|
||||
void CFrameBufferPool::Reset()
|
||||
{
|
||||
for (int i = 0; i < ARRAYSIZE(m_buffers); ++i)
|
||||
m_buffers[i].Reset();
|
||||
}
|
||||
|
||||
CFrameBufferResource * CFrameBufferPool::Get(
|
||||
const PreparedFrameBuffer& buffer,
|
||||
size_t minSize, const D3D12_RESOURCE_DESC * textureDesc)
|
||||
{
|
||||
if (buffer.frameIndex > ARRAYSIZE(m_buffers) - 1)
|
||||
return nullptr;
|
||||
|
||||
CFrameBufferResource * fbr = &m_buffers[buffer.frameIndex];
|
||||
if (!fbr->Init(m_transport, m_dx12, buffer.frameIndex, buffer.mem,
|
||||
minSize, textureDesc))
|
||||
return nullptr;
|
||||
|
||||
return fbr;
|
||||
}
|
||||
45
idd/LGIdd/capture/CFrameBufferPool.h
Normal file
45
idd/LGIdd/capture/CFrameBufferPool.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "capture/CFrameBufferResource.h"
|
||||
#include "capture/FrameBufferTypes.h"
|
||||
#include "common/KVMFR.h"
|
||||
|
||||
struct CD3D12Device;
|
||||
class CFrameTransport;
|
||||
|
||||
class CFrameBufferPool
|
||||
{
|
||||
private:
|
||||
CFrameTransport * m_transport = nullptr;
|
||||
CD3D12Device * m_dx12 = nullptr;
|
||||
|
||||
CFrameBufferResource m_buffers[LGMP_Q_FRAME_BUFFER_LEN];
|
||||
|
||||
public:
|
||||
void Init(CFrameTransport * transport, CD3D12Device * dx12);
|
||||
void Reset();
|
||||
|
||||
CFrameBufferResource * Get(const PreparedFrameBuffer& buffer,
|
||||
size_t minSize,
|
||||
const D3D12_RESOURCE_DESC * textureDesc = nullptr);
|
||||
};
|
||||
229
idd/LGIdd/capture/CFrameBufferResource.cpp
Normal file
229
idd/LGIdd/capture/CFrameBufferResource.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* 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 "capture/CFrameBufferResource.h"
|
||||
#include "capture/CFrameProcessorUtil.h"
|
||||
#include "d3d/CD3D12Device.h"
|
||||
#include "transport/CFrameTransport.h"
|
||||
#include "transport/CIVSHMEM.h"
|
||||
#include "CDebug.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
bool CFrameBufferResource::Init(CFrameTransport * transport,
|
||||
CD3D12Device * dx12, unsigned frameIndex, uint8_t * base, size_t size,
|
||||
const D3D12_RESOURCE_DESC * textureDesc)
|
||||
{
|
||||
m_frameIndex = frameIndex;
|
||||
|
||||
if (size > transport->GetMaxFrameSize())
|
||||
{
|
||||
DEBUG_ERROR("Frame size of %llu is too large to fit in shared ram",
|
||||
(unsigned long long)size);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool indirect = dx12->IsIndirectCopy();
|
||||
const ResourceType type = textureDesc ?
|
||||
RESOURCE_TEXTURE : RESOURCE_BUFFER;
|
||||
|
||||
D3D12_RESOURCE_DESC desc = {};
|
||||
if (textureDesc)
|
||||
{
|
||||
if (indirect || !dx12->CanUseIVSHMEMTexture())
|
||||
return false;
|
||||
desc = *textureDesc;
|
||||
if (desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
|
||||
!desc.Width ||
|
||||
!desc.Height ||
|
||||
desc.DepthOrArraySize != 1 ||
|
||||
desc.MipLevels != 1 ||
|
||||
desc.SampleDesc.Count != 1 ||
|
||||
desc.SampleDesc.Quality ||
|
||||
desc.Format == DXGI_FORMAT_UNKNOWN ||
|
||||
desc.Layout != D3D12_TEXTURE_LAYOUT_ROW_MAJOR ||
|
||||
desc.Flags != D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
||||
desc.Width = size;
|
||||
desc.Height = 1;
|
||||
desc.DepthOrArraySize = 1;
|
||||
desc.MipLevels = 1;
|
||||
desc.Format = DXGI_FORMAT_UNKNOWN;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.SampleDesc.Quality = 0;
|
||||
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
|
||||
if (!indirect)
|
||||
{
|
||||
desc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
|
||||
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing to do if the resource already represents this allocation.
|
||||
if (m_base == base && m_type == type &&
|
||||
((type == RESOURCE_BUFFER && m_size >= size) ||
|
||||
(type == RESOURCE_TEXTURE &&
|
||||
CFrameProcessorUtil::ResourceDescMatches(m_desc, desc))))
|
||||
{
|
||||
m_frameSize = size;
|
||||
return true;
|
||||
}
|
||||
|
||||
Reset();
|
||||
|
||||
HRESULT hr;
|
||||
const WCHAR * resName;
|
||||
UINT64 allocationSize = size;
|
||||
|
||||
if (indirect)
|
||||
{
|
||||
DEBUG_TRACE("Creating standard resource for %p", base);
|
||||
D3D12_HEAP_PROPERTIES heapProps = {};
|
||||
heapProps.Type = D3D12_HEAP_TYPE_READBACK;
|
||||
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
|
||||
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
|
||||
heapProps.CreationNodeMask = 1;
|
||||
heapProps.VisibleNodeMask = 1;
|
||||
|
||||
hr = dx12->GetDevice()->CreateCommittedResource(
|
||||
&heapProps,
|
||||
D3D12_HEAP_FLAG_NONE,
|
||||
&desc,
|
||||
D3D12_RESOURCE_STATE_COPY_DEST,
|
||||
NULL,
|
||||
IID_PPV_ARGS(&m_res)
|
||||
);
|
||||
resName = L"STAGING";
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
D3D12_RANGE range = {0, 0};
|
||||
hr = m_res->Map(0, &range, &m_map);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_ERROR_HR(hr, "Failed to map the resource");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const UINT64 heapOffset =
|
||||
(uintptr_t)base -
|
||||
(uintptr_t)transport->GetIVSHMEM().GetMem();
|
||||
const D3D12_RESOURCE_ALLOCATION_INFO allocation =
|
||||
dx12->GetDevice()->GetResourceAllocationInfo(0, 1, &desc);
|
||||
allocationSize = allocation.SizeInBytes;
|
||||
const D3D12_HEAP_DESC heapDesc = dx12->GetHeap()->GetDesc();
|
||||
if (!allocation.Alignment ||
|
||||
heapOffset % allocation.Alignment ||
|
||||
allocation.SizeInBytes > transport->GetMaxFrameSize() ||
|
||||
heapOffset > heapDesc.SizeInBytes ||
|
||||
allocation.SizeInBytes > heapDesc.SizeInBytes - heapOffset)
|
||||
{
|
||||
DEBUG_ERROR("IVSHMEM resource does not fit its framebuffer allocation");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == RESOURCE_TEXTURE)
|
||||
{
|
||||
D3D12_FEATURE_DATA_FORMAT_SUPPORT support = {};
|
||||
support.Format = desc.Format;
|
||||
hr = dx12->GetDevice()->CheckFeatureSupport(
|
||||
D3D12_FEATURE_FORMAT_SUPPORT, &support, sizeof(support));
|
||||
if (FAILED(hr) ||
|
||||
!(support.Support1 & D3D12_FORMAT_SUPPORT1_TEXTURE2D))
|
||||
{
|
||||
DEBUG_ERROR("IVSHMEM texture format is unsupported");
|
||||
return false;
|
||||
}
|
||||
DEBUG_TRACE("Creating IVSHMEM texture for %p", base);
|
||||
resName = L"IVSHMEM Texture";
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_TRACE("Creating IVSHMEM buffer for %p", base);
|
||||
resName = L"IVSHMEM";
|
||||
}
|
||||
|
||||
hr = dx12->GetDevice()->CreatePlacedResource(
|
||||
dx12->GetHeap().Get(),
|
||||
heapOffset,
|
||||
&desc,
|
||||
D3D12_RESOURCE_STATE_COMMON,
|
||||
NULL,
|
||||
IID_PPV_ARGS(&m_res)
|
||||
);
|
||||
}
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_ERROR_HR(hr, "Failed to create the FrameBuffer ID3D12Resource");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_res->SetName(resName);
|
||||
|
||||
m_base = base;
|
||||
m_size = type == RESOURCE_TEXTURE ?
|
||||
(size_t)allocationSize : size;
|
||||
m_frameSize = size;
|
||||
m_type = type;
|
||||
m_desc = desc;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CFrameBufferResource::Reset()
|
||||
{
|
||||
if (m_map)
|
||||
{
|
||||
m_res->Unmap(0, NULL);
|
||||
m_map = NULL;
|
||||
}
|
||||
|
||||
m_base = nullptr;
|
||||
m_size = 0;
|
||||
m_frameSize = 0;
|
||||
m_type = RESOURCE_NONE;
|
||||
m_desc = {};
|
||||
m_fullCopy = false;
|
||||
m_nbCopyDirtyRects = 0;
|
||||
m_copyPitch = 0;
|
||||
m_copyBytesPerPixel = 0;
|
||||
m_res.Reset();
|
||||
}
|
||||
|
||||
void CFrameBufferResource::SetCopyDamage(const RECT dirtyRects[],
|
||||
unsigned nbDirtyRects, bool fullCopy, unsigned pitch,
|
||||
unsigned bytesPerPixel)
|
||||
{
|
||||
m_fullCopy = fullCopy;
|
||||
m_nbCopyDirtyRects = fullCopy ? 0 : nbDirtyRects;
|
||||
m_copyPitch = pitch;
|
||||
m_copyBytesPerPixel = bytesPerPixel;
|
||||
if (m_nbCopyDirtyRects)
|
||||
memcpy(m_copyDirtyRects, dirtyRects,
|
||||
m_nbCopyDirtyRects * sizeof(*m_copyDirtyRects));
|
||||
}
|
||||
136
idd/LGIdd/capture/CFrameBufferResource.h
Normal file
136
idd/LGIdd/capture/CFrameBufferResource.h
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <wdf.h>
|
||||
#include <wrl.h>
|
||||
#include <d3d12.h>
|
||||
#include <atomic>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "capture/CFrameScheduler.h"
|
||||
#include "d3d/CInteropResource.h"
|
||||
|
||||
struct CD3D12Device;
|
||||
class CFrameTransport;
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
class CFrameBufferResource
|
||||
{
|
||||
private:
|
||||
enum ResourceType
|
||||
{
|
||||
RESOURCE_NONE,
|
||||
RESOURCE_BUFFER,
|
||||
RESOURCE_TEXTURE,
|
||||
};
|
||||
|
||||
unsigned m_frameIndex = 0;
|
||||
uint8_t * m_base = nullptr;
|
||||
size_t m_size = 0;
|
||||
size_t m_frameSize = 0;
|
||||
uint64_t m_captureTime = 0;
|
||||
uint64_t m_postProcessStart = 0;
|
||||
uint64_t m_copyStart = 0;
|
||||
|
||||
CFrameScheduler::Schedule m_schedule = {};
|
||||
|
||||
unsigned m_timingEffectIndex = 0;
|
||||
uint64_t m_timingToken = 0;
|
||||
bool m_fullCopy = false;
|
||||
RECT m_copyDirtyRects[LG_MAX_DIRTY_RECTS * 2] = {};
|
||||
unsigned m_nbCopyDirtyRects = 0;
|
||||
unsigned m_copyPitch = 0;
|
||||
unsigned m_copyBytesPerPixel = 0;
|
||||
unsigned m_candidateIndex = 0;
|
||||
ResourceType m_type = RESOURCE_NONE;
|
||||
D3D12_RESOURCE_DESC m_desc = {};
|
||||
std::atomic<bool> m_completionHandled = false;
|
||||
ComPtr<ID3D12Resource> m_res;
|
||||
void * m_map = nullptr;
|
||||
|
||||
public:
|
||||
bool Init(CFrameTransport * transport, CD3D12Device * dx12,
|
||||
unsigned frameIndex, uint8_t * base, size_t size,
|
||||
const D3D12_RESOURCE_DESC * textureDesc = nullptr);
|
||||
void Reset();
|
||||
|
||||
unsigned GetFrameIndex() { return m_frameIndex; }
|
||||
size_t GetFrameSize() { return m_frameSize; }
|
||||
void * GetMap() { return m_map; }
|
||||
|
||||
void SetTiming(uint64_t captureTime, uint64_t postProcessStart,
|
||||
uint64_t copyStart)
|
||||
{
|
||||
m_captureTime = captureTime;
|
||||
m_postProcessStart = postProcessStart;
|
||||
m_copyStart = copyStart;
|
||||
}
|
||||
uint64_t GetCaptureTime () const { return m_captureTime; }
|
||||
uint64_t GetPostProcessStart() const { return m_postProcessStart; }
|
||||
uint64_t GetCopyStart () const { return m_copyStart; }
|
||||
void SetSchedule(const CFrameScheduler::Schedule& schedule)
|
||||
{
|
||||
m_schedule = schedule;
|
||||
}
|
||||
const CFrameScheduler::Schedule& GetSchedule() const
|
||||
{
|
||||
return m_schedule;
|
||||
}
|
||||
|
||||
void SetPostProcessSample(
|
||||
unsigned effectIndex, uint64_t token, bool fullCopy)
|
||||
{
|
||||
m_timingEffectIndex = effectIndex;
|
||||
m_timingToken = token;
|
||||
m_fullCopy = fullCopy;
|
||||
}
|
||||
unsigned GetTimingEffectIndex() const { return m_timingEffectIndex; }
|
||||
uint64_t GetTimingToken () const { return m_timingToken; }
|
||||
bool IsFullCopy () const { return m_fullCopy; }
|
||||
void SetCopyDamage(const RECT dirtyRects[], unsigned nbDirtyRects,
|
||||
bool fullCopy, unsigned pitch, unsigned bytesPerPixel);
|
||||
const RECT * GetCopyDirtyRects() const { return m_copyDirtyRects; }
|
||||
unsigned GetCopyDirtyRectCount() const { return m_nbCopyDirtyRects; }
|
||||
unsigned GetCopyPitch () const { return m_copyPitch; }
|
||||
unsigned GetCopyBytesPerPixel () const
|
||||
{
|
||||
return m_copyBytesPerPixel;
|
||||
}
|
||||
void ResetCompletion()
|
||||
{
|
||||
m_completionHandled.store(false, std::memory_order_release);
|
||||
}
|
||||
void MarkCompletion()
|
||||
{
|
||||
m_completionHandled.store(true, std::memory_order_release);
|
||||
}
|
||||
bool CompletionHandled() const
|
||||
{
|
||||
return m_completionHandled.load(std::memory_order_acquire);
|
||||
}
|
||||
void SetCandidateIndex(unsigned index) { m_candidateIndex = index; }
|
||||
unsigned GetCandidateIndex() const { return m_candidateIndex; }
|
||||
|
||||
ComPtr<ID3D12Resource> Get() { return m_res; }
|
||||
};
|
||||
175
idd/LGIdd/capture/CFrameProcessor.cpp
Normal file
175
idd/LGIdd/capture/CFrameProcessor.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 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 "capture/CFrameProcessor.h"
|
||||
#include "capture/CFrameProcessorUtil.h"
|
||||
#include "capture/CHardwareFrameProcessor.h"
|
||||
#include "capture/CSoftwareFrameProcessor.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
CFrameProcessor::CFrameProcessor(CFrameTransport * transport,
|
||||
std::shared_ptr<CD3D12Device> dx12,
|
||||
CPostProcessor postProcessors[LGMP_Q_FRAME_LEN],
|
||||
SRWLOCK * pipelineLock, HANDLE terminateEvent) :
|
||||
m_transport(transport),
|
||||
m_dx12(std::move(dx12)),
|
||||
m_postProcessors(postProcessors),
|
||||
m_pipelineLock(pipelineLock),
|
||||
m_terminateEvent(terminateEvent)
|
||||
{
|
||||
m_readyEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr));
|
||||
m_frameBuffers.Init(m_transport, m_dx12.get());
|
||||
}
|
||||
|
||||
bool CFrameProcessor::IsValid() const
|
||||
{
|
||||
return m_readyEvent.Get() != nullptr;
|
||||
}
|
||||
|
||||
void CFrameProcessor::Reset()
|
||||
{
|
||||
m_frameBuffers.Reset();
|
||||
}
|
||||
|
||||
void CFrameProcessor::Invalidate()
|
||||
{
|
||||
AcquireSRWLockExclusive(&m_damageLock);
|
||||
m_previousDamageCount = 0;
|
||||
m_hasPendingDamage = true;
|
||||
m_pendingDamageCount = 0;
|
||||
SetFullDamageLocked();
|
||||
ReleaseSRWLockExclusive(&m_damageLock);
|
||||
}
|
||||
|
||||
void CFrameProcessor::ResetPipeline()
|
||||
{
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void CFrameProcessor::AccumulateDamage(
|
||||
const RECT dirtyRects[], unsigned count)
|
||||
{
|
||||
AcquireSRWLockExclusive(&m_damageLock);
|
||||
CFrameProcessorUtil::AccumulateDamage(
|
||||
m_pendingDamage, &m_pendingDamageCount, &m_hasPendingDamage,
|
||||
dirtyRects, count);
|
||||
AccumulateDamageLocked(dirtyRects, count);
|
||||
ReleaseSRWLockExclusive(&m_damageLock);
|
||||
}
|
||||
|
||||
void CFrameProcessor::SetFullDamage()
|
||||
{
|
||||
AcquireSRWLockExclusive(&m_damageLock);
|
||||
m_hasPendingDamage = true;
|
||||
m_pendingDamageCount = 0;
|
||||
SetFullDamageLocked();
|
||||
ReleaseSRWLockExclusive(&m_damageLock);
|
||||
}
|
||||
|
||||
void CFrameProcessor::AccumulateDamageLocked(
|
||||
const RECT[], unsigned)
|
||||
{
|
||||
}
|
||||
|
||||
void CFrameProcessor::SetFullDamageLocked()
|
||||
{
|
||||
}
|
||||
|
||||
bool CFrameProcessor::HasPendingDamage() const
|
||||
{
|
||||
AcquireSRWLockShared(&m_damageLock);
|
||||
const bool result = m_hasPendingDamage;
|
||||
ReleaseSRWLockShared(&m_damageLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CFrameProcessor::TakePendingDamage(
|
||||
RECT dirtyRects[], unsigned * count)
|
||||
{
|
||||
AcquireSRWLockExclusive(&m_damageLock);
|
||||
const bool hasDamage = m_hasPendingDamage;
|
||||
*count = hasDamage ? m_pendingDamageCount : 0;
|
||||
if (*count)
|
||||
memcpy(dirtyRects, m_pendingDamage,
|
||||
*count * sizeof(*dirtyRects));
|
||||
m_hasPendingDamage = false;
|
||||
m_pendingDamageCount = 0;
|
||||
ReleaseSRWLockExclusive(&m_damageLock);
|
||||
return hasDamage;
|
||||
}
|
||||
|
||||
void CFrameProcessor::RestorePendingDamage(
|
||||
const RECT dirtyRects[], unsigned count, bool hasDamage)
|
||||
{
|
||||
if (!hasDamage)
|
||||
return;
|
||||
|
||||
AcquireSRWLockExclusive(&m_damageLock);
|
||||
CFrameProcessorUtil::AccumulateDamage(
|
||||
m_pendingDamage, &m_pendingDamageCount, &m_hasPendingDamage,
|
||||
dirtyRects, count);
|
||||
ReleaseSRWLockExclusive(&m_damageLock);
|
||||
}
|
||||
|
||||
void CFrameProcessor::CommitDamage(
|
||||
const RECT dirtyRects[], unsigned count)
|
||||
{
|
||||
AcquireSRWLockExclusive(&m_damageLock);
|
||||
m_previousDamageCount = count;
|
||||
if (count)
|
||||
memcpy(m_previousDamage, dirtyRects,
|
||||
count * sizeof(*m_previousDamage));
|
||||
ReleaseSRWLockExclusive(&m_damageLock);
|
||||
}
|
||||
|
||||
void CFrameProcessor::GetPreviousDamage(
|
||||
RECT dirtyRects[], unsigned * count) const
|
||||
{
|
||||
AcquireSRWLockShared(&m_damageLock);
|
||||
*count = m_previousDamageCount;
|
||||
if (*count)
|
||||
memcpy(dirtyRects, m_previousDamage,
|
||||
*count * sizeof(*dirtyRects));
|
||||
ReleaseSRWLockShared(&m_damageLock);
|
||||
}
|
||||
|
||||
std::unique_ptr<CFrameProcessor> CreateFrameProcessor(
|
||||
bool software, CFrameTransport * transport,
|
||||
std::shared_ptr<CD3D12Device> dx12,
|
||||
CPostProcessor postProcessors[LGMP_Q_FRAME_LEN],
|
||||
SRWLOCK * pipelineLock, HANDLE terminateEvent)
|
||||
{
|
||||
std::unique_ptr<CFrameProcessor> processor;
|
||||
if (software)
|
||||
processor.reset(new (std::nothrow) CSoftwareFrameProcessor(
|
||||
transport, std::move(dx12), postProcessors,
|
||||
pipelineLock, terminateEvent));
|
||||
else
|
||||
processor.reset(new (std::nothrow) CHardwareFrameProcessor(
|
||||
transport, std::move(dx12), postProcessors,
|
||||
pipelineLock, terminateEvent));
|
||||
|
||||
if (!processor || !processor->IsValid())
|
||||
return nullptr;
|
||||
return processor;
|
||||
}
|
||||
100
idd/LGIdd/capture/CFrameProcessor.h
Normal file
100
idd/LGIdd/capture/CFrameProcessor.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "d3d/CD3D12Device.h"
|
||||
#include "capture/CFrameBufferPool.h"
|
||||
#include "d3d/CInteropResource.h"
|
||||
#include "postprocess/CPostProcessor.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <memory>
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
class CFrameTransport;
|
||||
|
||||
struct FrameSubmission
|
||||
{
|
||||
CInteropResource * source;
|
||||
D12FrameFormat sourceFormat;
|
||||
uint64_t captureTime;
|
||||
uint64_t postProcessStart;
|
||||
unsigned timingEffectIndex;
|
||||
uint64_t timingToken;
|
||||
bool noImageUpdate;
|
||||
};
|
||||
|
||||
class CFrameProcessor
|
||||
{
|
||||
protected:
|
||||
CFrameTransport * m_transport;
|
||||
std::shared_ptr<CD3D12Device> m_dx12;
|
||||
CPostProcessor * m_postProcessors;
|
||||
SRWLOCK * m_pipelineLock;
|
||||
HANDLE m_terminateEvent;
|
||||
CFrameBufferPool m_frameBuffers;
|
||||
Wrappers::Event m_readyEvent;
|
||||
|
||||
mutable SRWLOCK m_damageLock = SRWLOCK_INIT;
|
||||
RECT m_previousDamage[LG_MAX_DIRTY_RECTS] = {};
|
||||
unsigned m_previousDamageCount = 0;
|
||||
RECT m_pendingDamage[LG_MAX_DIRTY_RECTS] = {};
|
||||
unsigned m_pendingDamageCount = 0;
|
||||
bool m_hasPendingDamage = true;
|
||||
|
||||
bool HasPendingDamage() const;
|
||||
bool TakePendingDamage(RECT dirtyRects[], unsigned * count);
|
||||
void RestorePendingDamage(
|
||||
const RECT dirtyRects[], unsigned count, bool hasDamage);
|
||||
void CommitDamage(const RECT dirtyRects[], unsigned count);
|
||||
void GetPreviousDamage(RECT dirtyRects[], unsigned * count) const;
|
||||
virtual void AccumulateDamageLocked(
|
||||
const RECT dirtyRects[], unsigned count);
|
||||
virtual void SetFullDamageLocked();
|
||||
|
||||
public:
|
||||
CFrameProcessor(CFrameTransport * transport,
|
||||
std::shared_ptr<CD3D12Device> dx12,
|
||||
CPostProcessor postProcessors[LGMP_Q_FRAME_LEN],
|
||||
SRWLOCK * pipelineLock, HANDLE terminateEvent);
|
||||
virtual ~CFrameProcessor() = default;
|
||||
|
||||
virtual bool IsValid() const;
|
||||
virtual bool Submit(const FrameSubmission& submission) = 0;
|
||||
virtual bool HasReadyFrame() const = 0;
|
||||
virtual bool Publish(const CFrameScheduler::Schedule& schedule,
|
||||
bool periodic, uint64_t publishStart) = 0;
|
||||
virtual bool UsesCadence() const = 0;
|
||||
virtual void Reset();
|
||||
virtual void Invalidate();
|
||||
virtual void ResetPipeline();
|
||||
void AccumulateDamage(const RECT dirtyRects[], unsigned count);
|
||||
void SetFullDamage();
|
||||
|
||||
HANDLE GetReadyEvent() const { return m_readyEvent.Get(); }
|
||||
};
|
||||
|
||||
std::unique_ptr<CFrameProcessor> CreateFrameProcessor(
|
||||
bool software, CFrameTransport * transport,
|
||||
std::shared_ptr<CD3D12Device> dx12,
|
||||
CPostProcessor postProcessors[LGMP_Q_FRAME_LEN],
|
||||
SRWLOCK * pipelineLock, HANDLE terminateEvent);
|
||||
272
idd/LGIdd/capture/CFrameProcessorUtil.cpp
Normal file
272
idd/LGIdd/capture/CFrameProcessorUtil.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* 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 "capture/CFrameProcessorUtil.h"
|
||||
#include "d3d/CInteropResource.h"
|
||||
#include "postprocess/CPostProcessor.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
bool CFrameProcessorUtil::FrameMetadataChanged(
|
||||
const D12FrameFormat& previous, const D12FrameFormat& current)
|
||||
{
|
||||
return
|
||||
previous.hdrMetadata != current.hdrMetadata ||
|
||||
previous.sdrWhiteLevel != current.sdrWhiteLevel ||
|
||||
(current.hdrMetadata &&
|
||||
(memcmp(previous.displayPrimary, current.displayPrimary,
|
||||
sizeof(current.displayPrimary)) != 0 ||
|
||||
memcmp(previous.whitePoint, current.whitePoint,
|
||||
sizeof(current.whitePoint)) != 0 ||
|
||||
previous.maxDisplayLuminance != current.maxDisplayLuminance ||
|
||||
previous.minDisplayLuminance != current.minDisplayLuminance ||
|
||||
previous.maxContentLightLevel != current.maxContentLightLevel ||
|
||||
previous.maxFrameAverageLightLevel != current.maxFrameAverageLightLevel));
|
||||
}
|
||||
|
||||
FrameType CFrameProcessorUtil::GetFrameType(DXGI_FORMAT format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case DXGI_FORMAT_B8G8R8A8_UNORM : return FRAME_TYPE_BGRA;
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM : return FRAME_TYPE_RGBA;
|
||||
case DXGI_FORMAT_R10G10B10A2_UNORM : return FRAME_TYPE_RGBA10;
|
||||
case DXGI_FORMAT_R16G16B16A16_FLOAT: return FRAME_TYPE_RGBA16F;
|
||||
default : return FRAME_TYPE_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
bool CFrameProcessorUtil::ResourceDescMatches(
|
||||
const D3D12_RESOURCE_DESC& left, const D3D12_RESOURCE_DESC& right,
|
||||
bool compareAlignment)
|
||||
{
|
||||
// GetDesc may report a resolved alignment when resource creation requested
|
||||
// automatic alignment, so callers comparing creation descriptors can omit
|
||||
// this allocation metadata.
|
||||
return
|
||||
left.Dimension == right.Dimension &&
|
||||
(!compareAlignment || left.Alignment == right.Alignment) &&
|
||||
left.Width == right.Width &&
|
||||
left.Height == right.Height &&
|
||||
left.DepthOrArraySize == right.DepthOrArraySize &&
|
||||
left.MipLevels == right.MipLevels &&
|
||||
left.Format == right.Format &&
|
||||
left.SampleDesc.Count == right.SampleDesc.Count &&
|
||||
left.SampleDesc.Quality == right.SampleDesc.Quality &&
|
||||
left.Layout == right.Layout &&
|
||||
left.Flags == right.Flags;
|
||||
}
|
||||
|
||||
static bool IsFullDamage(const RECT * dirtyRects, unsigned nbDirtyRects,
|
||||
unsigned width, unsigned height)
|
||||
{
|
||||
for (const RECT * rect = dirtyRects;
|
||||
rect < dirtyRects + nbDirtyRects; ++rect)
|
||||
if (rect->left == 0 &&
|
||||
rect->top == 0 &&
|
||||
rect->right == (LONG)width &&
|
||||
rect->bottom == (LONG)height)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool DirtyRectContains(const RECT& outer, const RECT& inner)
|
||||
{
|
||||
return outer.left <= inner.left &&
|
||||
outer.top <= inner.top &&
|
||||
outer.right >= inner.right &&
|
||||
outer.bottom >= inner.bottom;
|
||||
}
|
||||
|
||||
static bool DirtyRectsTouchOrIntersect(const RECT& a, const RECT& b)
|
||||
{
|
||||
return a.left <= b.right && a.right >= b.left &&
|
||||
a.top <= b.bottom && a.bottom >= b.top;
|
||||
}
|
||||
|
||||
static RECT MergeDirtyRects(const RECT& a, const RECT& b)
|
||||
{
|
||||
RECT result;
|
||||
result.left = min(a.left , b.left );
|
||||
result.top = min(a.top , b.top );
|
||||
result.right = max(a.right , b.right );
|
||||
result.bottom = max(a.bottom, b.bottom);
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint64_t DirtyRectArea(const RECT& rect)
|
||||
{
|
||||
const uint64_t width = (uint64_t)((int64_t)rect.right - rect.left);
|
||||
const uint64_t height = (uint64_t)((int64_t)rect.bottom - rect.top );
|
||||
return width * height;
|
||||
}
|
||||
|
||||
static bool AddCopyDirtyRect(RECT dirtyRects[], unsigned capacity,
|
||||
unsigned * nbDirtyRects, const RECT& dirtyRect)
|
||||
{
|
||||
RECT candidate = dirtyRect;
|
||||
for (unsigned i = 0; i < *nbDirtyRects;)
|
||||
{
|
||||
if (DirtyRectContains(dirtyRects[i], candidate))
|
||||
return true;
|
||||
|
||||
const RECT merged = MergeDirtyRects(dirtyRects[i], candidate);
|
||||
if (DirtyRectContains(candidate, dirtyRects[i]) ||
|
||||
(DirtyRectsTouchOrIntersect(dirtyRects[i], candidate) &&
|
||||
DirtyRectArea(merged) <=
|
||||
DirtyRectArea(dirtyRects[i]) + DirtyRectArea(candidate)))
|
||||
{
|
||||
candidate = merged;
|
||||
--(*nbDirtyRects);
|
||||
dirtyRects[i] = dirtyRects[*nbDirtyRects];
|
||||
i = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
if (*nbDirtyRects >= capacity)
|
||||
return false;
|
||||
|
||||
dirtyRects[(*nbDirtyRects)++] = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CopyAreaCoversFrame(const RECT * dirtyRects,
|
||||
unsigned nbDirtyRects, unsigned width, unsigned height)
|
||||
{
|
||||
const uint64_t frameArea = (uint64_t)width * height;
|
||||
uint64_t copyArea = 0;
|
||||
|
||||
for (const RECT * rect = dirtyRects;
|
||||
rect < dirtyRects + nbDirtyRects; ++rect)
|
||||
{
|
||||
const uint64_t area = DirtyRectArea(*rect);
|
||||
if (area >= frameArea - copyArea)
|
||||
return true;
|
||||
copyArea += area;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ClipDirtyRect(RECT& rect, unsigned width, unsigned height)
|
||||
{
|
||||
const LONG maxRight = (LONG)width;
|
||||
const LONG maxBottom = (LONG)height;
|
||||
|
||||
if (rect.left < 0 ) rect.left = 0;
|
||||
if (rect.top < 0 ) rect.top = 0;
|
||||
if (rect.right > maxRight ) rect.right = maxRight;
|
||||
if (rect.bottom > maxBottom) rect.bottom = maxBottom;
|
||||
|
||||
return rect.left < rect.right && rect.top < rect.bottom;
|
||||
}
|
||||
|
||||
void CFrameProcessorUtil::ClipDirtyRects(
|
||||
RECT dirtyRects[], unsigned * nbDirtyRects,
|
||||
unsigned width, unsigned height)
|
||||
{
|
||||
unsigned out = 0;
|
||||
for (unsigned i = 0; i < *nbDirtyRects; ++i)
|
||||
{
|
||||
RECT rect = dirtyRects[i];
|
||||
if (ClipDirtyRect(rect, width, height))
|
||||
dirtyRects[out++] = rect;
|
||||
}
|
||||
*nbDirtyRects = out;
|
||||
}
|
||||
|
||||
bool CFrameProcessorUtil::BuildCopyDamage(
|
||||
const CPostProcessor& postProcessor, bool destinationNeedsFullCopy,
|
||||
const RECT previousDirtyRects[], unsigned nbPreviousDirtyRects,
|
||||
const RECT currentDirtyRects[], unsigned nbCurrentDirtyRects,
|
||||
unsigned width, unsigned height,
|
||||
RECT copyDirtyRects[], unsigned * nbCopyDirtyRects)
|
||||
{
|
||||
*nbCopyDirtyRects = 0;
|
||||
bool fullCopy = destinationNeedsFullCopy ||
|
||||
nbCurrentDirtyRects == 0 || nbPreviousDirtyRects == 0;
|
||||
|
||||
if (fullCopy)
|
||||
return true;
|
||||
|
||||
for (const RECT * rect = previousDirtyRects;
|
||||
rect < previousDirtyRects + nbPreviousDirtyRects && !fullCopy;
|
||||
++rect)
|
||||
{
|
||||
RECT clipped = *rect;
|
||||
if (ClipDirtyRect(clipped, width, height) &&
|
||||
!AddCopyDirtyRect(copyDirtyRects, LG_MAX_DIRTY_RECTS * 2,
|
||||
nbCopyDirtyRects, clipped))
|
||||
fullCopy = true;
|
||||
}
|
||||
|
||||
for (const RECT * rect = currentDirtyRects;
|
||||
rect < currentDirtyRects + nbCurrentDirtyRects && !fullCopy;
|
||||
++rect)
|
||||
if (!AddCopyDirtyRect(copyDirtyRects, LG_MAX_DIRTY_RECTS * 2,
|
||||
nbCopyDirtyRects, *rect))
|
||||
fullCopy = true;
|
||||
|
||||
if (!fullCopy)
|
||||
fullCopy = IsFullDamage(copyDirtyRects, *nbCopyDirtyRects,
|
||||
width, height) ||
|
||||
CopyAreaCoversFrame(copyDirtyRects, *nbCopyDirtyRects,
|
||||
width, height);
|
||||
|
||||
if (!fullCopy)
|
||||
fullCopy = postProcessor.ShouldCopyFully(
|
||||
copyDirtyRects, *nbCopyDirtyRects);
|
||||
|
||||
return fullCopy;
|
||||
}
|
||||
|
||||
void CFrameProcessorUtil::AccumulateDamage(
|
||||
RECT pendingDirtyRects[], unsigned * nbPendingDirtyRects,
|
||||
bool * hasPendingDamage, const RECT dirtyRects[],
|
||||
unsigned nbDirtyRects)
|
||||
{
|
||||
if (nbDirtyRects > LG_MAX_DIRTY_RECTS)
|
||||
nbDirtyRects = 0;
|
||||
|
||||
if (!*hasPendingDamage)
|
||||
{
|
||||
*hasPendingDamage = true;
|
||||
*nbPendingDirtyRects = nbDirtyRects;
|
||||
if (nbDirtyRects)
|
||||
memcpy(pendingDirtyRects, dirtyRects,
|
||||
nbDirtyRects * sizeof(*pendingDirtyRects));
|
||||
return;
|
||||
}
|
||||
|
||||
if (*nbPendingDirtyRects == 0 || nbDirtyRects == 0 ||
|
||||
*nbPendingDirtyRects + nbDirtyRects > LG_MAX_DIRTY_RECTS)
|
||||
{
|
||||
*nbPendingDirtyRects = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(pendingDirtyRects + *nbPendingDirtyRects, dirtyRects,
|
||||
nbDirtyRects * sizeof(*pendingDirtyRects));
|
||||
*nbPendingDirtyRects += nbDirtyRects;
|
||||
}
|
||||
47
idd/LGIdd/capture/CFrameProcessorUtil.h
Normal file
47
idd/LGIdd/capture/CFrameProcessorUtil.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "postprocess/D12FrameFormat.h"
|
||||
|
||||
class CPostProcessor;
|
||||
|
||||
class CFrameProcessorUtil
|
||||
{
|
||||
public:
|
||||
static bool FrameMetadataChanged(const D12FrameFormat& previous,
|
||||
const D12FrameFormat& current);
|
||||
static FrameType GetFrameType(DXGI_FORMAT format);
|
||||
static bool ResourceDescMatches(const D3D12_RESOURCE_DESC& left,
|
||||
const D3D12_RESOURCE_DESC& right, bool compareAlignment = true);
|
||||
static void ClipDirtyRects(RECT dirtyRects[], unsigned * nbDirtyRects,
|
||||
unsigned width, unsigned height);
|
||||
static bool BuildCopyDamage(const CPostProcessor& postProcessor,
|
||||
bool destinationNeedsFullCopy,
|
||||
const RECT previousDirtyRects[], unsigned nbPreviousDirtyRects,
|
||||
const RECT currentDirtyRects[], unsigned nbCurrentDirtyRects,
|
||||
unsigned width, unsigned height,
|
||||
RECT copyDirtyRects[], unsigned * nbCopyDirtyRects);
|
||||
static void AccumulateDamage(
|
||||
RECT pendingDirtyRects[], unsigned * nbPendingDirtyRects,
|
||||
bool * hasPendingDamage, const RECT dirtyRects[],
|
||||
unsigned nbDirtyRects);
|
||||
};
|
||||
1056
idd/LGIdd/capture/CFrameScheduler.cpp
Normal file
1056
idd/LGIdd/capture/CFrameScheduler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
171
idd/LGIdd/capture/CFrameScheduler.h
Normal file
171
idd/LGIdd/capture/CFrameScheduler.h
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern "C" {
|
||||
#include <lgmp/lgmp.h>
|
||||
}
|
||||
|
||||
#include "common/KVMFR.h"
|
||||
|
||||
class CFrameScheduler
|
||||
{
|
||||
public:
|
||||
struct Schedule
|
||||
{
|
||||
uint32_t clientID;
|
||||
uint32_t generation;
|
||||
uint32_t epoch;
|
||||
uint64_t period;
|
||||
uint64_t targetSlack;
|
||||
uint64_t deadline;
|
||||
uint64_t forceTicket;
|
||||
uint64_t republishTicket;
|
||||
uint32_t deadlineSerial;
|
||||
uint32_t deliveryDeadlineSerial;
|
||||
bool phaseEligible;
|
||||
};
|
||||
|
||||
private:
|
||||
struct Client
|
||||
{
|
||||
uint32_t clientID;
|
||||
uint32_t generation;
|
||||
uint64_t period;
|
||||
uint64_t targetSlack;
|
||||
uint64_t expiry;
|
||||
uint64_t nextDelivery;
|
||||
uint32_t lastFeedbackDeadlineSerial;
|
||||
uint32_t lastDeliveredFrameSerial;
|
||||
bool subscribed;
|
||||
bool ownerCapable;
|
||||
bool subscriptionSeen;
|
||||
bool active;
|
||||
bool immediate;
|
||||
bool deliveredFrameValid;
|
||||
};
|
||||
|
||||
struct Publication
|
||||
{
|
||||
uint32_t generation;
|
||||
uint32_t epoch;
|
||||
uint32_t deadlineSerial;
|
||||
uint32_t frameSerial;
|
||||
uint64_t deadline;
|
||||
bool committed;
|
||||
bool completed;
|
||||
bool phaseValid;
|
||||
bool accepted;
|
||||
};
|
||||
|
||||
static const unsigned PUBLICATION_HISTORY_SIZE = 128;
|
||||
static const unsigned WORK_TIMING_HISTORY_SIZE = 32;
|
||||
|
||||
mutable SRWLOCK m_lock = SRWLOCK_INIT;
|
||||
HANDLE m_wakeEvent = nullptr;
|
||||
Client m_clients[LGMP_MAX_CLIENTS] = {};
|
||||
Schedule m_schedule = {};
|
||||
bool m_scheduling = false;
|
||||
uint32_t m_epoch = 0;
|
||||
|
||||
// A result acknowledges only the request tickets captured by its attempt.
|
||||
uint64_t m_forceRequestTicket = 0;
|
||||
uint64_t m_forceAckTicket = 0;
|
||||
uint64_t m_republishRequestTicket = 0;
|
||||
uint64_t m_republishAckTicket = 0;
|
||||
|
||||
uint64_t m_lastArrival = 0;
|
||||
uint64_t m_guestPeriod = 0;
|
||||
uint64_t m_workEstimate = 0;
|
||||
uint64_t m_workTiming[WORK_TIMING_HISTORY_SIZE] = {};
|
||||
unsigned m_workTimingCount = 0;
|
||||
unsigned m_workTimingIndex = 0;
|
||||
uint64_t m_nextDeadline = 0;
|
||||
uint32_t m_deadlineSerial = 0;
|
||||
int64_t m_pendingCorrection = 0;
|
||||
|
||||
Publication m_publications[PUBLICATION_HISTORY_SIZE] = {};
|
||||
unsigned m_publicationIndex = 0;
|
||||
|
||||
int64_t m_lastPhaseError = 0;
|
||||
uint64_t m_acquiredFrames = 0;
|
||||
uint64_t m_skippedFrames = 0;
|
||||
uint64_t m_publishedFrames = 0;
|
||||
uint64_t m_lastLog = 0;
|
||||
uint64_t m_lastLogAcquired = 0;
|
||||
uint64_t m_lastLogSkipped = 0;
|
||||
uint64_t m_lastLogPublished = 0;
|
||||
|
||||
Client * FindClient(uint32_t clientID);
|
||||
Client * FindOrAllocateClient(uint32_t clientID);
|
||||
Publication * FindPublication(const Schedule& schedule,
|
||||
uint32_t frameSerial);
|
||||
bool ElectOwner(uint64_t now, uint32_t resetClientID = 0);
|
||||
bool ApplyFeedback(Client& client, const KVMFRFrameSchedule& schedule);
|
||||
void AdvanceCurrentDeadline();
|
||||
void AdvanceDeadlineSerial(uint64_t count);
|
||||
void AdvanceDeadline(uint64_t now);
|
||||
static void AdvanceDelivery(Client& client, uint64_t now);
|
||||
void WakePublisher() const;
|
||||
|
||||
public:
|
||||
CFrameScheduler();
|
||||
~CFrameScheduler();
|
||||
|
||||
static uint64_t Nanotime();
|
||||
|
||||
void Reset();
|
||||
void UpdateSubscribers(const uint32_t * clientIDs, unsigned count,
|
||||
const uint32_t * ownerClientIDs, unsigned ownerCount, uint64_t now);
|
||||
bool UpdateSchedule(uint32_t sourceClientID,
|
||||
const KVMFRFrameSchedule& schedule, uint64_t now);
|
||||
bool GetSchedule(Schedule& schedule) const;
|
||||
HANDLE GetWakeEvent() const { return m_wakeEvent; }
|
||||
void ObserveFrame(uint64_t now);
|
||||
void ForceFrame();
|
||||
bool GetPublishTarget(uint64_t now, uint64_t& target,
|
||||
Schedule& schedule, bool& periodic, bool& republish);
|
||||
void FrameMissed(const Schedule& schedule, uint64_t now, bool periodic);
|
||||
void FrameSuperseded();
|
||||
bool TryFrameSubmitted(const Schedule& schedule, uint32_t frameSerial);
|
||||
void FramePublished(const Schedule& schedule, uint32_t frameSerial,
|
||||
uint64_t now, bool periodic);
|
||||
void FrameRetained(const Schedule& schedule, uint64_t now,
|
||||
bool periodic);
|
||||
void FrameRepublished(const Schedule& schedule, uint32_t frameSerial);
|
||||
bool TryFrameCompleted(const Schedule& schedule, uint32_t frameSerial,
|
||||
uint64_t completedAt);
|
||||
unsigned GetSecondaryRecipients(const uint32_t * clientIDs,
|
||||
unsigned count, uint32_t frameSerial, uint64_t now,
|
||||
uint32_t * recipients) const;
|
||||
bool GetSecondaryTarget(uint32_t frameSerial, uint64_t now,
|
||||
const uint32_t * blockedClientIDs, unsigned blockedCount,
|
||||
uint64_t& target) const;
|
||||
void FrameDelivered(const uint32_t * clientIDs, unsigned count,
|
||||
uint32_t frameSerial, uint64_t now);
|
||||
void RequestRepublish();
|
||||
void NotifyPublisher() const { WakePublisher(); }
|
||||
void TryRecordFrameTiming(uint64_t duration);
|
||||
void LogStatistics(uint64_t now);
|
||||
};
|
||||
823
idd/LGIdd/capture/CHardwareFrameProcessor.cpp
Normal file
823
idd/LGIdd/capture/CHardwareFrameProcessor.cpp
Normal file
@@ -0,0 +1,823 @@
|
||||
/**
|
||||
* 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 "capture/CHardwareFrameProcessor.h"
|
||||
#include "capture/CFrameProcessorUtil.h"
|
||||
#include "transport/CFrameTransport.h"
|
||||
#include "util/CSRWLock.h"
|
||||
#include "CDebug.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
static_assert(LGMP_Q_FRAME_LEN == 2,
|
||||
"IDD candidate pipeline assumes two slots");
|
||||
|
||||
class CPublishPending
|
||||
{
|
||||
private:
|
||||
SRWLOCK * m_lock;
|
||||
bool * m_pending;
|
||||
HANDLE m_event;
|
||||
bool m_active = true;
|
||||
|
||||
public:
|
||||
CPublishPending(SRWLOCK * lock, bool * pending, HANDLE event) :
|
||||
m_lock(lock),
|
||||
m_pending(pending),
|
||||
m_event(event)
|
||||
{
|
||||
AcquireSRWLockExclusive(m_lock);
|
||||
*m_pending = true;
|
||||
ResetEvent(m_event);
|
||||
ReleaseSRWLockExclusive(m_lock);
|
||||
}
|
||||
|
||||
~CPublishPending()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
if (!m_active)
|
||||
return;
|
||||
|
||||
AcquireSRWLockExclusive(m_lock);
|
||||
*m_pending = false;
|
||||
SetEvent(m_event);
|
||||
ReleaseSRWLockExclusive(m_lock);
|
||||
m_active = false;
|
||||
}
|
||||
};
|
||||
|
||||
CHardwareFrameProcessor::CHardwareFrameProcessor(
|
||||
CFrameTransport * transport, std::shared_ptr<CD3D12Device> dx12,
|
||||
CPostProcessor postProcessors[LGMP_Q_FRAME_LEN],
|
||||
SRWLOCK * pipelineLock, HANDLE terminateEvent) :
|
||||
CFrameProcessor(transport, std::move(dx12), postProcessors,
|
||||
pipelineLock, terminateEvent)
|
||||
{
|
||||
m_candidateAvailableEvent.Attach(
|
||||
CreateEvent(nullptr, FALSE, FALSE, nullptr));
|
||||
m_copySubmitEvent.Attach(CreateEvent(nullptr, TRUE, TRUE, nullptr));
|
||||
}
|
||||
|
||||
bool CHardwareFrameProcessor::IsValid() const
|
||||
{
|
||||
return CFrameProcessor::IsValid() &&
|
||||
m_candidateAvailableEvent.Get() && m_copySubmitEvent.Get();
|
||||
}
|
||||
|
||||
void CHardwareFrameProcessor::SignalCandidateState()
|
||||
{
|
||||
SetEvent(m_readyEvent.Get());
|
||||
SetEvent(m_candidateAvailableEvent.Get());
|
||||
}
|
||||
|
||||
void CHardwareFrameProcessor::SetFullDamageLocked()
|
||||
{
|
||||
for (CandidateDamageTail& tail : m_candidateDamageTail)
|
||||
if (tail.active)
|
||||
{
|
||||
tail.hasDamage = true;
|
||||
tail.nbDirtyRects = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CHardwareFrameProcessor::AccumulateDamageLocked(
|
||||
const RECT dirtyRects[], unsigned count)
|
||||
{
|
||||
for (CandidateDamageTail& tail : m_candidateDamageTail)
|
||||
if (tail.active)
|
||||
CFrameProcessorUtil::AccumulateDamage(
|
||||
tail.dirtyRects, &tail.nbDirtyRects, &tail.hasDamage,
|
||||
dirtyRects, count);
|
||||
}
|
||||
|
||||
void CHardwareFrameProcessor::ResetCandidates()
|
||||
{
|
||||
AcquireSRWLockExclusive(&m_candidateLock);
|
||||
for (FrameCandidate& candidate : m_candidates)
|
||||
candidate = {};
|
||||
ReleaseSRWLockExclusive(&m_candidateLock);
|
||||
|
||||
AcquireSRWLockExclusive(&m_damageLock);
|
||||
for (CandidateDamageTail& tail : m_candidateDamageTail)
|
||||
tail = {};
|
||||
ReleaseSRWLockExclusive(&m_damageLock);
|
||||
SignalCandidateState();
|
||||
}
|
||||
|
||||
void CHardwareFrameProcessor::Reset()
|
||||
{
|
||||
ResetCandidates();
|
||||
CFrameProcessor::Reset();
|
||||
}
|
||||
|
||||
void CHardwareFrameProcessor::ResetPipeline()
|
||||
{
|
||||
ResetCandidates();
|
||||
CFrameProcessor::Invalidate();
|
||||
}
|
||||
|
||||
bool CHardwareFrameProcessor::HasReadyFrame() const
|
||||
{
|
||||
bool ready = false;
|
||||
AcquireSRWLockShared(&m_candidateLock);
|
||||
for (const FrameCandidate& candidate : m_candidates)
|
||||
if (candidate.state == CANDIDATE_READY)
|
||||
{
|
||||
ready = true;
|
||||
break;
|
||||
}
|
||||
ReleaseSRWLockShared(&m_candidateLock);
|
||||
return ready;
|
||||
}
|
||||
|
||||
int CHardwareFrameProcessor::AcquireCandidate(
|
||||
bool exclusiveSample, bool allowSupersede)
|
||||
{
|
||||
int selected = -1;
|
||||
uint64_t oldest = UINT64_MAX;
|
||||
bool superseded = false;
|
||||
bool idle = true;
|
||||
bool publishing = false;
|
||||
|
||||
AcquireSRWLockExclusive(&m_candidateLock);
|
||||
for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i)
|
||||
{
|
||||
if (m_candidates[i].state != CANDIDATE_FREE)
|
||||
{
|
||||
idle = false;
|
||||
if (m_candidates[i].state == CANDIDATE_PUBLISHING)
|
||||
publishing = true;
|
||||
}
|
||||
else if (selected < 0)
|
||||
selected = static_cast<int>(i);
|
||||
}
|
||||
|
||||
if (exclusiveSample && !idle)
|
||||
selected = -1;
|
||||
|
||||
unsigned readyCount = 0;
|
||||
for (const FrameCandidate& candidate : m_candidates)
|
||||
if (candidate.state == CANDIDATE_READY)
|
||||
++readyCount;
|
||||
|
||||
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 &&
|
||||
m_candidates[i].sequence < oldest)
|
||||
{
|
||||
selected = static_cast<int>(i);
|
||||
oldest = m_candidates[i].sequence;
|
||||
}
|
||||
|
||||
if (selected >= 0)
|
||||
{
|
||||
FrameCandidate& candidate =
|
||||
m_candidates[static_cast<unsigned>(selected)];
|
||||
superseded = candidate.state == CANDIDATE_READY;
|
||||
candidate.state = CANDIDATE_PREPARING;
|
||||
candidate.sequence = ++m_candidateSequence;
|
||||
}
|
||||
ReleaseSRWLockExclusive(&m_candidateLock);
|
||||
|
||||
if (superseded)
|
||||
m_transport->FrameSuperseded();
|
||||
return selected;
|
||||
}
|
||||
|
||||
void CHardwareFrameProcessor::ReleaseCandidate(unsigned candidateIndex)
|
||||
{
|
||||
if (candidateIndex >= ARRAYSIZE(m_candidates))
|
||||
return;
|
||||
|
||||
AcquireSRWLockExclusive(&m_candidateLock);
|
||||
m_candidates[candidateIndex].state = CANDIDATE_FREE;
|
||||
ReleaseSRWLockExclusive(&m_candidateLock);
|
||||
SignalCandidateState();
|
||||
}
|
||||
|
||||
bool CHardwareFrameProcessor::EnsureCandidateResource(
|
||||
unsigned candidateIndex, size_t frameSize)
|
||||
{
|
||||
FrameCandidate& candidate = m_candidates[candidateIndex];
|
||||
|
||||
D3D12_RESOURCE_DESC desc = {};
|
||||
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
||||
desc.Width = frameSize;
|
||||
desc.Height = 1;
|
||||
desc.DepthOrArraySize = 1;
|
||||
desc.MipLevels = 1;
|
||||
desc.Format = DXGI_FORMAT_UNKNOWN;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.SampleDesc.Quality = 0;
|
||||
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
|
||||
|
||||
if (candidate.resource &&
|
||||
CFrameProcessorUtil::ResourceDescMatches(
|
||||
candidate.resource->GetDesc(), desc, false))
|
||||
return true;
|
||||
|
||||
candidate.resource.Reset();
|
||||
|
||||
D3D12_HEAP_PROPERTIES heapProps = {};
|
||||
heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
|
||||
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
|
||||
heapProps.CreationNodeMask = 1;
|
||||
heapProps.VisibleNodeMask = 1;
|
||||
|
||||
const HRESULT hr = m_dx12->GetDevice()->CreateCommittedResource(
|
||||
&heapProps, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON,
|
||||
nullptr, IID_PPV_ARGS(&candidate.resource));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_ERROR_HR(hr, "Failed to create retained frame candidate");
|
||||
return false;
|
||||
}
|
||||
|
||||
static const WCHAR * names[] =
|
||||
{
|
||||
L"Frame Candidate 0",
|
||||
L"Frame Candidate 1",
|
||||
};
|
||||
candidate.resource->SetName(names[candidateIndex]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CHardwareFrameProcessor::ExecuteCandidateCopy(
|
||||
CD3D12CommandSlot * copySlot)
|
||||
{
|
||||
HANDLE waitHandles[] =
|
||||
{
|
||||
m_terminateEvent,
|
||||
m_copySubmitEvent.Get(),
|
||||
};
|
||||
|
||||
for (;;)
|
||||
{
|
||||
AcquireSRWLockExclusive(&m_copySubmitLock);
|
||||
if (!m_publishPending)
|
||||
{
|
||||
const bool result = copySlot->Execute();
|
||||
ReleaseSRWLockExclusive(&m_copySubmitLock);
|
||||
return result;
|
||||
}
|
||||
ReleaseSRWLockExclusive(&m_copySubmitLock);
|
||||
|
||||
const DWORD result = WaitForMultipleObjects(
|
||||
ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE);
|
||||
if (result == WAIT_OBJECT_0 + 1)
|
||||
continue;
|
||||
|
||||
copySlot->Cancel();
|
||||
if (result != WAIT_OBJECT_0)
|
||||
DEBUG_ERROR_HR(HRESULT_FROM_WIN32(GetLastError()),
|
||||
"Failed while waiting to submit a frame candidate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CHardwareFrameProcessor::CandidateCompletionFunction(
|
||||
CD3D12CommandSlot * slot, bool result, void * param1, void * param2)
|
||||
{
|
||||
auto processor = static_cast<CHardwareFrameProcessor *>(param1);
|
||||
auto candidate = static_cast<FrameCandidate *>(param2);
|
||||
|
||||
uint64_t gpuStart = 0;
|
||||
uint64_t gpuEnd = 0;
|
||||
const bool timingValid = result && slot->GetGPUTimes(gpuStart, gpuEnd);
|
||||
|
||||
bool forceFrame = false;
|
||||
AcquireSRWLockExclusive(&processor->m_candidateLock);
|
||||
if (candidate->state == CANDIDATE_PREPARING)
|
||||
{
|
||||
candidate->prepareReady = CFrameScheduler::Nanotime();
|
||||
candidate->prepareGPUStart = gpuStart;
|
||||
candidate->prepareGPUEnd = gpuEnd;
|
||||
candidate->prepareTimingValid = timingValid;
|
||||
candidate->state =
|
||||
result ? CANDIDATE_READY : CANDIDATE_FREE;
|
||||
forceFrame = result && candidate->timingToken != 0;
|
||||
}
|
||||
ReleaseSRWLockExclusive(&processor->m_candidateLock);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
processor->SetFullDamage();
|
||||
processor->m_transport->ForceFrame();
|
||||
}
|
||||
else if (forceFrame)
|
||||
processor->m_transport->ForceFrame();
|
||||
processor->SignalCandidateState();
|
||||
}
|
||||
|
||||
void CHardwareFrameProcessor::CompletionFunction(
|
||||
CD3D12CommandSlot * slot, bool result, void * param1, void * param2)
|
||||
{
|
||||
auto processor = static_cast<CHardwareFrameProcessor *>(param1);
|
||||
auto fbRes = static_cast<CFrameBufferResource *>(param2);
|
||||
const unsigned candidateIndex = fbRes->GetCandidateIndex();
|
||||
|
||||
if (!result)
|
||||
{
|
||||
processor->m_transport->FailFrameBuffer(fbRes->GetFrameIndex());
|
||||
processor->SetFullDamage();
|
||||
processor->m_transport->ForceFrame();
|
||||
processor->ReleaseCandidate(candidateIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t prepareCopyStart;
|
||||
uint64_t prepareReady;
|
||||
uint64_t prepareGPUStart;
|
||||
uint64_t prepareGPUEnd;
|
||||
uint64_t timingStart;
|
||||
bool prepareTimingValid;
|
||||
AcquireSRWLockShared(&processor->m_candidateLock);
|
||||
const FrameCandidate& candidate =
|
||||
processor->m_candidates[candidateIndex];
|
||||
prepareCopyStart = candidate.prepareCopyStart;
|
||||
prepareReady = candidate.prepareReady;
|
||||
prepareGPUStart = candidate.prepareGPUStart;
|
||||
prepareGPUEnd = candidate.prepareGPUEnd;
|
||||
timingStart = candidate.timingStart;
|
||||
prepareTimingValid = candidate.prepareTimingValid;
|
||||
ReleaseSRWLockShared(&processor->m_candidateLock);
|
||||
|
||||
const uint64_t publishStart = fbRes->GetCopyStart();
|
||||
uint64_t gpuCopyStart = 0;
|
||||
uint64_t gpuCopyEnd = 0;
|
||||
uint64_t indirectCopyTime = 0;
|
||||
if (processor->m_dx12->IsIndirectCopy())
|
||||
{
|
||||
const uint64_t indirectCopyStart = CFrameScheduler::Nanotime();
|
||||
processor->m_transport->WriteFrameBuffer(
|
||||
fbRes->GetFrameIndex(), fbRes->GetMap(), 0,
|
||||
fbRes->GetFrameSize(), false);
|
||||
indirectCopyTime = CFrameScheduler::Nanotime() - indirectCopyStart;
|
||||
}
|
||||
|
||||
const bool gpuTimingValid =
|
||||
slot->GetGPUTimes(gpuCopyStart, gpuCopyEnd);
|
||||
const uint64_t copyReady = CFrameScheduler::Nanotime();
|
||||
|
||||
const uint64_t postProcessStart = fbRes->GetPostProcessStart();
|
||||
uint64_t postProcessTime = prepareCopyStart - postProcessStart;
|
||||
uint64_t prepareCopyTime = prepareReady - prepareCopyStart;
|
||||
if (prepareTimingValid && prepareGPUStart >= postProcessStart &&
|
||||
prepareGPUEnd >= prepareGPUStart && prepareGPUEnd <= prepareReady)
|
||||
{
|
||||
postProcessTime = prepareGPUStart - postProcessStart;
|
||||
prepareCopyTime = prepareGPUEnd - prepareGPUStart;
|
||||
}
|
||||
|
||||
uint64_t publishCopyTime = copyReady - publishStart;
|
||||
if (gpuTimingValid && gpuCopyStart >= publishStart &&
|
||||
gpuCopyEnd >= gpuCopyStart && gpuCopyEnd <= copyReady)
|
||||
publishCopyTime = gpuCopyEnd - gpuCopyStart + indirectCopyTime;
|
||||
|
||||
const uint64_t copyTime = prepareCopyTime + publishCopyTime;
|
||||
|
||||
processor->m_transport->FinalizeFrameBuffer(fbRes->GetFrameIndex());
|
||||
const uint64_t publishedAt = CFrameScheduler::Nanotime();
|
||||
const uint64_t prepareElapsed = prepareReady >= postProcessStart ?
|
||||
prepareReady - postProcessStart : 0;
|
||||
const uint64_t prepareMeasured = postProcessTime + prepareCopyTime;
|
||||
const uint64_t prepareReadyTime = prepareElapsed > prepareMeasured ?
|
||||
prepareElapsed - prepareMeasured : 0;
|
||||
const uint64_t publishElapsed = publishedAt >= publishStart ?
|
||||
publishedAt - publishStart : 0;
|
||||
const uint64_t publishReadyTime = publishElapsed > publishCopyTime ?
|
||||
publishElapsed - publishCopyTime : 0;
|
||||
const uint64_t readyTime = prepareReadyTime + publishReadyTime;
|
||||
const uint64_t holdTime = publishStart >= prepareReady ?
|
||||
publishStart - prepareReady : 0;
|
||||
|
||||
processor->m_transport->SetFrameTiming(fbRes->GetFrameIndex(),
|
||||
fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime, holdTime,
|
||||
fbRes->GetSchedule(), publishedAt);
|
||||
processor->m_transport->TryRecordFrameTiming(publishedAt - publishStart);
|
||||
|
||||
const uint64_t timingToken = fbRes->GetTimingToken();
|
||||
if (timingToken && timingStart && prepareReady >= timingStart &&
|
||||
copyReady >= publishStart)
|
||||
{
|
||||
const uint64_t totalTime =
|
||||
(prepareReady - timingStart) + (copyReady - publishStart);
|
||||
processor->m_postProcessors[candidateIndex].RecordTiming(
|
||||
fbRes->GetTimingEffectIndex(), timingToken,
|
||||
fbRes->IsFullCopy(), totalTime);
|
||||
}
|
||||
|
||||
processor->m_transport->CompleteFrameBuffer(fbRes->GetFrameIndex(), true);
|
||||
processor->ReleaseCandidate(candidateIndex);
|
||||
}
|
||||
|
||||
bool CHardwareFrameProcessor::Publish(
|
||||
const CFrameScheduler::Schedule& schedule, bool periodic,
|
||||
uint64_t publishStart)
|
||||
{
|
||||
CPublishPending publishPending(
|
||||
&m_copySubmitLock, &m_publishPending, m_copySubmitEvent.Get());
|
||||
CSRWSharedLock pipelineLock(m_pipelineLock);
|
||||
|
||||
int selectedCandidate = -1;
|
||||
uint64_t newestSequence = 0;
|
||||
|
||||
AcquireSRWLockExclusive(&m_candidateLock);
|
||||
for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i)
|
||||
if (m_candidates[i].state == CANDIDATE_READY &&
|
||||
(selectedCandidate < 0 ||
|
||||
m_candidates[i].sequence > newestSequence))
|
||||
{
|
||||
selectedCandidate = static_cast<int>(i);
|
||||
newestSequence = m_candidates[i].sequence;
|
||||
}
|
||||
|
||||
if (selectedCandidate >= 0)
|
||||
m_candidates[static_cast<unsigned>(selectedCandidate)].state =
|
||||
CANDIDATE_PUBLISHING;
|
||||
ReleaseSRWLockExclusive(&m_candidateLock);
|
||||
|
||||
if (selectedCandidate < 0)
|
||||
return false;
|
||||
const unsigned candidateIndex =
|
||||
static_cast<unsigned>(selectedCandidate);
|
||||
|
||||
const auto restoreCandidate = [this, candidateIndex]()
|
||||
{
|
||||
AcquireSRWLockExclusive(&m_candidateLock);
|
||||
if (m_candidates[candidateIndex].state == CANDIDATE_PUBLISHING)
|
||||
m_candidates[candidateIndex].state = CANDIDATE_READY;
|
||||
ReleaseSRWLockExclusive(&m_candidateLock);
|
||||
SignalCandidateState();
|
||||
};
|
||||
|
||||
AcquireSRWLockShared(&m_candidateLock);
|
||||
const bool candidateValid =
|
||||
m_candidates[candidateIndex].state == CANDIDATE_PUBLISHING &&
|
||||
m_candidates[candidateIndex].resource.Get();
|
||||
ReleaseSRWLockShared(&m_candidateLock);
|
||||
if (!candidateValid)
|
||||
{
|
||||
restoreCandidate();
|
||||
return false;
|
||||
}
|
||||
|
||||
FrameCandidate& candidate = m_candidates[candidateIndex];
|
||||
CPostProcessor& postProcessor = m_postProcessors[candidateIndex];
|
||||
const uint64_t candidateSequence = candidate.sequence;
|
||||
|
||||
auto buffer = m_transport->PrepareFrameBuffer(
|
||||
candidate.pitch, candidate.srcFormat, candidate.dstFormat,
|
||||
candidate.dirtyRects, candidate.nbDirtyRects, schedule);
|
||||
if (!buffer.mem)
|
||||
{
|
||||
restoreCandidate();
|
||||
return false;
|
||||
}
|
||||
|
||||
CFrameBufferResource * fbRes =
|
||||
m_frameBuffers.Get(buffer, candidate.frameSize);
|
||||
if (!fbRes)
|
||||
{
|
||||
m_transport->AbortFrameBuffer(buffer.frameIndex);
|
||||
restoreCandidate();
|
||||
DEBUG_ERROR("Failed to get a CFrameBufferResource from the pool");
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
CD3D12CommandSlot * copySlot = m_dx12->GetCopySlot(candidateIndex);
|
||||
if (!copySlot)
|
||||
{
|
||||
m_transport->AbortFrameBuffer(buffer.frameIndex);
|
||||
restoreCandidate();
|
||||
DEBUG_ERROR("Failed to get a copy CommandSlot for publication");
|
||||
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,
|
||||
candidate.dirtyRects, candidate.nbDirtyRects,
|
||||
candidate.dstFormat.width, candidate.dstFormat.height,
|
||||
copyDirtyRects, &nbCopyDirtyRects);
|
||||
|
||||
fbRes->SetTiming(
|
||||
candidate.captureTime, candidate.postProcessStart, publishStart);
|
||||
fbRes->SetCandidateIndex(candidateIndex);
|
||||
fbRes->SetPostProcessSample(
|
||||
candidate.timingEffectIndex, candidate.timingToken, fullCopy);
|
||||
copySlot->SetCompletionCallback(&CompletionFunction, this, fbRes);
|
||||
|
||||
copySlot->BeginTiming();
|
||||
postProcessor.CopyFromCandidate(
|
||||
copySlot->GetGfxList(), fbRes->Get().Get(), candidate.resource.Get(),
|
||||
copyDirtyRects, nbCopyDirtyRects, fullCopy);
|
||||
copySlot->EndTiming();
|
||||
|
||||
bool deliveredToOwner;
|
||||
if (!m_transport->PublishFrameBuffer(
|
||||
buffer.frameIndex, schedule, deliveredToOwner))
|
||||
{
|
||||
copySlot->Cancel();
|
||||
m_transport->AbortFrameBuffer(buffer.frameIndex);
|
||||
restoreCandidate();
|
||||
return false;
|
||||
}
|
||||
CFrameScheduler::Schedule frameSchedule = schedule;
|
||||
if (!deliveredToOwner ||
|
||||
!m_transport->TryFrameSubmitted(buffer.frameIndex, schedule))
|
||||
frameSchedule.phaseEligible = false;
|
||||
fbRes->SetSchedule(frameSchedule);
|
||||
|
||||
AcquireSRWLockExclusive(&m_damageLock);
|
||||
if (candidate.nbDirtyRects)
|
||||
memcpy(m_previousDamage, candidate.dirtyRects,
|
||||
candidate.nbDirtyRects * sizeof(*m_previousDamage));
|
||||
m_previousDamageCount = candidate.nbDirtyRects;
|
||||
CandidateDamageTail& tail = m_candidateDamageTail[candidateIndex];
|
||||
if (tail.active && tail.ownerSequence == candidateSequence)
|
||||
{
|
||||
m_hasPendingDamage = tail.hasDamage;
|
||||
m_pendingDamageCount = tail.nbDirtyRects;
|
||||
if (tail.hasDamage && tail.nbDirtyRects)
|
||||
memcpy(m_pendingDamage, tail.dirtyRects,
|
||||
tail.nbDirtyRects * sizeof(*m_pendingDamage));
|
||||
tail.ownerSequence = 0;
|
||||
tail.active = false;
|
||||
}
|
||||
ReleaseSRWLockExclusive(&m_damageLock);
|
||||
|
||||
const bool submitted = copySlot->Execute();
|
||||
publishPending.Clear();
|
||||
if (!submitted)
|
||||
{
|
||||
SetFullDamage();
|
||||
AcquireSRWLockShared(&m_candidateLock);
|
||||
const bool callbackPending =
|
||||
candidate.state == CANDIDATE_PUBLISHING;
|
||||
ReleaseSRWLockShared(&m_candidateLock);
|
||||
if (callbackPending && !copySlot->HasSubmittedWork())
|
||||
{
|
||||
m_transport->FailFrameBuffer(buffer.frameIndex);
|
||||
ReleaseCandidate(candidateIndex);
|
||||
}
|
||||
m_transport->ForceFrame();
|
||||
SignalCandidateState();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_transport->CommitFrameBuffer(
|
||||
buffer.frameIndex, schedule, periodic, deliveredToOwner);
|
||||
|
||||
unsigned superseded = 0;
|
||||
AcquireSRWLockExclusive(&m_candidateLock);
|
||||
for (FrameCandidate& ready : m_candidates)
|
||||
if (ready.state == CANDIDATE_READY &&
|
||||
ready.sequence < candidateSequence)
|
||||
{
|
||||
ready.state = CANDIDATE_FREE;
|
||||
++superseded;
|
||||
}
|
||||
ReleaseSRWLockExclusive(&m_candidateLock);
|
||||
for (unsigned i = 0; i < superseded; ++i)
|
||||
m_transport->FrameSuperseded();
|
||||
SignalCandidateState();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CHardwareFrameProcessor::Submit(const FrameSubmission& submission)
|
||||
{
|
||||
int selectedCandidate = AcquireCandidate(
|
||||
submission.timingToken != 0, !submission.noImageUpdate);
|
||||
while (selectedCandidate < 0 && submission.noImageUpdate)
|
||||
{
|
||||
HANDLE waitHandles[] =
|
||||
{
|
||||
m_terminateEvent,
|
||||
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;
|
||||
}
|
||||
|
||||
selectedCandidate = AcquireCandidate(
|
||||
submission.timingToken != 0, false);
|
||||
}
|
||||
if (selectedCandidate < 0)
|
||||
{
|
||||
m_transport->FrameSuperseded();
|
||||
return true;
|
||||
}
|
||||
const unsigned candidateIndex =
|
||||
static_cast<unsigned>(selectedCandidate);
|
||||
FrameCandidate& candidate = m_candidates[candidateIndex];
|
||||
|
||||
CSRWSharedLock pipelineLock(m_pipelineLock);
|
||||
CPostProcessor& postProcessor = m_postProcessors[candidateIndex];
|
||||
const D12FrameFormat& dstFormat = postProcessor.GetOutputFormat();
|
||||
|
||||
RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {};
|
||||
unsigned nbDirtyRects = 0;
|
||||
AcquireSRWLockExclusive(&m_damageLock);
|
||||
if (m_hasPendingDamage)
|
||||
{
|
||||
nbDirtyRects = m_pendingDamageCount;
|
||||
if (nbDirtyRects)
|
||||
memcpy(currentDirtyRects, m_pendingDamage,
|
||||
nbDirtyRects * sizeof(*currentDirtyRects));
|
||||
}
|
||||
CandidateDamageTail& tail = m_candidateDamageTail[candidateIndex];
|
||||
tail.ownerSequence = candidate.sequence;
|
||||
tail.nbDirtyRects = 0;
|
||||
tail.hasDamage = false;
|
||||
tail.active = true;
|
||||
ReleaseSRWLockExclusive(&m_damageLock);
|
||||
|
||||
CD3D12CommandSlot * copySlot = m_dx12->GetCopySlot(candidateIndex);
|
||||
if (!copySlot)
|
||||
{
|
||||
ReleaseCandidate(candidateIndex);
|
||||
DEBUG_ERROR("Failed to get a copy CommandSlot");
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
const uint64_t timingStart = submission.timingToken ?
|
||||
CFrameScheduler::Nanotime() : 0;
|
||||
|
||||
ComPtr<ID3D12Resource> copySrcResource =
|
||||
submission.source->GetRes();
|
||||
CD3D12CommandSlot * computeSlot = nullptr;
|
||||
if (postProcessor.HasActiveEffects())
|
||||
{
|
||||
computeSlot = m_dx12->GetComputeSlot(candidateIndex);
|
||||
if (!computeSlot)
|
||||
{
|
||||
copySlot->Cancel();
|
||||
ReleaseCandidate(candidateIndex);
|
||||
DEBUG_ERROR("Failed to get a compute CommandSlot");
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!submission.source->Signal())
|
||||
{
|
||||
if (computeSlot)
|
||||
computeSlot->Cancel();
|
||||
copySlot->Cancel();
|
||||
ReleaseCandidate(candidateIndex);
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (computeSlot)
|
||||
{
|
||||
if (!submission.source->Sync(*computeSlot))
|
||||
{
|
||||
computeSlot->Cancel();
|
||||
copySlot->Cancel();
|
||||
ReleaseCandidate(candidateIndex);
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
copySrcResource = postProcessor.Run(
|
||||
computeSlot->GetGfxList(), copySrcResource,
|
||||
currentDirtyRects, &nbDirtyRects);
|
||||
if (!copySrcResource)
|
||||
{
|
||||
computeSlot->Cancel();
|
||||
copySlot->Cancel();
|
||||
ReleaseCandidate(candidateIndex);
|
||||
DEBUG_ERROR("Post processor returned no output resource");
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!computeSlot->Execute())
|
||||
{
|
||||
copySlot->Cancel();
|
||||
m_dx12->WaitForIdle();
|
||||
ReleaseCandidate(candidateIndex);
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!copySlot->WaitFor(*computeSlot))
|
||||
{
|
||||
copySlot->Cancel();
|
||||
m_dx12->WaitForIdle();
|
||||
ReleaseCandidate(candidateIndex);
|
||||
DEBUG_ERROR("Failed to queue compute synchronization");
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!submission.source->Sync(*copySlot))
|
||||
{
|
||||
copySlot->Cancel();
|
||||
ReleaseCandidate(candidateIndex);
|
||||
DEBUG_ERROR("Failed to queue source synchronization");
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
CFrameProcessorUtil::ClipDirtyRects(
|
||||
currentDirtyRects, &nbDirtyRects,
|
||||
dstFormat.width, dstFormat.height);
|
||||
|
||||
const size_t frameSize = postProcessor.GetOutputSize();
|
||||
if (!EnsureCandidateResource(candidateIndex, frameSize))
|
||||
{
|
||||
copySlot->Cancel();
|
||||
if (computeSlot)
|
||||
m_dx12->WaitForIdle();
|
||||
ReleaseCandidate(candidateIndex);
|
||||
SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
candidate.srcFormat = submission.sourceFormat;
|
||||
candidate.dstFormat = dstFormat;
|
||||
candidate.nbDirtyRects = nbDirtyRects;
|
||||
candidate.pitch = postProcessor.GetOutputPitch();
|
||||
candidate.frameSize = frameSize;
|
||||
candidate.captureTime = submission.captureTime;
|
||||
candidate.postProcessStart = submission.postProcessStart;
|
||||
candidate.prepareCopyStart = CFrameScheduler::Nanotime();
|
||||
candidate.prepareReady = 0;
|
||||
candidate.prepareGPUStart = 0;
|
||||
candidate.prepareGPUEnd = 0;
|
||||
candidate.timingStart = timingStart;
|
||||
candidate.prepareTimingValid = false;
|
||||
if (nbDirtyRects)
|
||||
memcpy(candidate.dirtyRects, currentDirtyRects,
|
||||
nbDirtyRects * sizeof(*candidate.dirtyRects));
|
||||
candidate.timingEffectIndex = submission.timingEffectIndex;
|
||||
candidate.timingToken = submission.timingToken;
|
||||
|
||||
copySlot->SetCompletionCallback(
|
||||
&CandidateCompletionFunction, this, &candidate);
|
||||
copySlot->BeginTiming();
|
||||
postProcessor.CopyToCandidate(
|
||||
copySlot->GetGfxList(), candidate.resource.Get(),
|
||||
copySrcResource.Get());
|
||||
copySlot->EndTiming();
|
||||
|
||||
if (!ExecuteCandidateCopy(copySlot))
|
||||
{
|
||||
if (!copySlot->HasSubmittedWork())
|
||||
{
|
||||
if (computeSlot)
|
||||
m_dx12->WaitForIdle();
|
||||
ReleaseCandidate(candidateIndex);
|
||||
}
|
||||
SetFullDamage();
|
||||
m_transport->ForceFrame();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
105
idd/LGIdd/capture/CHardwareFrameProcessor.h
Normal file
105
idd/LGIdd/capture/CHardwareFrameProcessor.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "capture/CFrameProcessor.h"
|
||||
|
||||
class CHardwareFrameProcessor final : public CFrameProcessor
|
||||
{
|
||||
private:
|
||||
enum CandidateState
|
||||
{
|
||||
CANDIDATE_FREE,
|
||||
CANDIDATE_PREPARING,
|
||||
CANDIDATE_READY,
|
||||
CANDIDATE_PUBLISHING,
|
||||
};
|
||||
|
||||
struct FrameCandidate
|
||||
{
|
||||
CandidateState state = CANDIDATE_FREE;
|
||||
ComPtr<ID3D12Resource> resource;
|
||||
D12FrameFormat srcFormat = {};
|
||||
D12FrameFormat dstFormat = {};
|
||||
RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {};
|
||||
unsigned nbDirtyRects = 0;
|
||||
unsigned pitch = 0;
|
||||
size_t frameSize = 0;
|
||||
uint64_t sequence = 0;
|
||||
uint64_t captureTime = 0;
|
||||
uint64_t postProcessStart = 0;
|
||||
uint64_t prepareCopyStart = 0;
|
||||
uint64_t prepareReady = 0;
|
||||
uint64_t prepareGPUStart = 0;
|
||||
uint64_t prepareGPUEnd = 0;
|
||||
uint64_t timingStart = 0;
|
||||
unsigned timingEffectIndex = 0;
|
||||
uint64_t timingToken = 0;
|
||||
bool prepareTimingValid = false;
|
||||
};
|
||||
|
||||
struct CandidateDamageTail
|
||||
{
|
||||
uint64_t ownerSequence = 0;
|
||||
RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {};
|
||||
unsigned nbDirtyRects = 0;
|
||||
bool hasDamage = false;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
FrameCandidate m_candidates[LGMP_Q_FRAME_LEN];
|
||||
CandidateDamageTail m_candidateDamageTail[LGMP_Q_FRAME_LEN];
|
||||
mutable SRWLOCK m_candidateLock = SRWLOCK_INIT;
|
||||
SRWLOCK m_copySubmitLock = SRWLOCK_INIT;
|
||||
uint64_t m_candidateSequence = 0;
|
||||
bool m_publishPending = false;
|
||||
Wrappers::Event m_candidateAvailableEvent;
|
||||
Wrappers::Event m_copySubmitEvent;
|
||||
|
||||
static void CandidateCompletionFunction(
|
||||
CD3D12CommandSlot * slot, bool result, void * param1, void * param2);
|
||||
static void CompletionFunction(
|
||||
CD3D12CommandSlot * slot, bool result, void * param1, void * param2);
|
||||
int AcquireCandidate(bool exclusiveSample, bool allowSupersede);
|
||||
void ReleaseCandidate(unsigned candidateIndex);
|
||||
bool EnsureCandidateResource(unsigned candidateIndex, size_t frameSize);
|
||||
void ResetCandidates();
|
||||
void SignalCandidateState();
|
||||
bool ExecuteCandidateCopy(CD3D12CommandSlot * copySlot);
|
||||
void AccumulateDamageLocked(
|
||||
const RECT dirtyRects[], unsigned count) override;
|
||||
void SetFullDamageLocked() override;
|
||||
|
||||
public:
|
||||
CHardwareFrameProcessor(CFrameTransport * transport,
|
||||
std::shared_ptr<CD3D12Device> dx12,
|
||||
CPostProcessor postProcessors[LGMP_Q_FRAME_LEN],
|
||||
SRWLOCK * pipelineLock, HANDLE terminateEvent);
|
||||
|
||||
bool IsValid() const override;
|
||||
bool Submit(const FrameSubmission& submission) override;
|
||||
bool HasReadyFrame() const override;
|
||||
bool Publish(const CFrameScheduler::Schedule& schedule,
|
||||
bool periodic, uint64_t publishStart) override;
|
||||
bool UsesCadence() const override { return true; }
|
||||
void Reset() override;
|
||||
void ResetPipeline() override;
|
||||
};
|
||||
367
idd/LGIdd/capture/CSoftwareFrameProcessor.cpp
Normal file
367
idd/LGIdd/capture/CSoftwareFrameProcessor.cpp
Normal file
@@ -0,0 +1,367 @@
|
||||
/**
|
||||
* 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 "capture/CSoftwareFrameProcessor.h"
|
||||
#include "capture/CFrameProcessorUtil.h"
|
||||
#include "capture/FrameBufferTypes.h"
|
||||
#include "transport/CFrameTransport.h"
|
||||
#include "util/CSRWLock.h"
|
||||
#include "CDebug.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
CSoftwareFrameProcessor::CSoftwareFrameProcessor(
|
||||
CFrameTransport * transport, std::shared_ptr<CD3D12Device> dx12,
|
||||
CPostProcessor postProcessors[LGMP_Q_FRAME_LEN],
|
||||
SRWLOCK * pipelineLock, HANDLE terminateEvent) :
|
||||
CFrameProcessor(transport, 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_transport->FailFrameBuffer(fbRes->GetFrameIndex());
|
||||
processor->SetFullDamage();
|
||||
processor->m_transport->ForceFrame();
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t indirectCopyTime = 0;
|
||||
if (processor->m_dx12->IsIndirectCopy())
|
||||
{
|
||||
const uint64_t indirectCopyStart = CFrameScheduler::Nanotime();
|
||||
if (fbRes->IsFullCopy())
|
||||
processor->m_transport->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_transport->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_transport->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_transport->SetFrameTiming(fbRes->GetFrameIndex(),
|
||||
fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime, 0,
|
||||
fbRes->GetSchedule(), publishedAt);
|
||||
processor->m_transport->CompleteFrameBuffer(fbRes->GetFrameIndex(), true);
|
||||
}
|
||||
|
||||
bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission)
|
||||
{
|
||||
CSRWSharedLock 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_transport->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_transport->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 = {};
|
||||
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_transport->GetPublishTarget(CFrameScheduler::Nanotime(),
|
||||
ignoredTarget, commitSchedule, ignoredPeriodic, ignoredRepublish);
|
||||
deliverySchedule = commitSchedule;
|
||||
deliverySchedule.deliveryDeadlineSerial = 0;
|
||||
deliverySchedule.phaseEligible = false;
|
||||
|
||||
m_transport->ProcessFrameQueue();
|
||||
if (!m_transport->FrameBufferAvailable(
|
||||
deliverySchedule, submission.noImageUpdate))
|
||||
{
|
||||
if (!submission.noImageUpdate)
|
||||
{
|
||||
m_transport->FrameSuperseded();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (WaitForSingleObject(m_terminateEvent, 1) == WAIT_OBJECT_0)
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
copySlot = m_dx12->GetCopySlot();
|
||||
if (!copySlot)
|
||||
{
|
||||
if (!submission.noImageUpdate)
|
||||
{
|
||||
m_transport->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_transport->PrepareFrameBuffer(
|
||||
pitch, submission.sourceFormat, dstFormat,
|
||||
currentDirtyRects, nbDirtyRects, deliverySchedule,
|
||||
submission.noImageUpdate);
|
||||
if (!buffer.mem)
|
||||
{
|
||||
copySlot->Cancel();
|
||||
RestorePendingDamage(
|
||||
currentDirtyRects, nbDirtyRects, hasDamage);
|
||||
if (!submission.noImageUpdate)
|
||||
{
|
||||
m_transport->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_transport->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_transport->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_transport->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_transport->PublishFrameBuffer(
|
||||
buffer.frameIndex, deliverySchedule, deliveredToOwner))
|
||||
{
|
||||
copySlot->Cancel();
|
||||
m_transport->AbortFrameBuffer(buffer.frameIndex);
|
||||
RestorePendingDamage(
|
||||
currentDirtyRects, nbDirtyRects, hasDamage);
|
||||
if (!submission.noImageUpdate)
|
||||
{
|
||||
m_transport->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_transport->FailFrameBuffer(buffer.frameIndex);
|
||||
RestorePendingDamage(
|
||||
currentDirtyRects, nbDirtyRects, hasDamage);
|
||||
if (!submittedWork && !completionHandled)
|
||||
{
|
||||
SetFullDamage();
|
||||
m_transport->ForceFrame();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
m_transport->CommitFrameBuffer(
|
||||
buffer.frameIndex, commitSchedule, false, deliveredToOwner);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
46
idd/LGIdd/capture/CSoftwareFrameProcessor.h
Normal file
46
idd/LGIdd/capture/CSoftwareFrameProcessor.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "capture/CFrameProcessor.h"
|
||||
|
||||
class CSoftwareFrameProcessor final : public CFrameProcessor
|
||||
{
|
||||
private:
|
||||
bool m_directTexture;
|
||||
|
||||
static void CompletionFunction(
|
||||
CD3D12CommandSlot * slot, bool result, void * param1, void * param2);
|
||||
|
||||
public:
|
||||
CSoftwareFrameProcessor(CFrameTransport * transport,
|
||||
std::shared_ptr<CD3D12Device> dx12,
|
||||
CPostProcessor postProcessors[LGMP_Q_FRAME_LEN],
|
||||
SRWLOCK * pipelineLock, HANDLE terminateEvent);
|
||||
|
||||
bool Submit(const FrameSubmission& submission) override;
|
||||
bool HasReadyFrame() const override { return false; }
|
||||
bool Publish(const CFrameScheduler::Schedule&, bool, uint64_t) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool UsesCadence() const override { return false; }
|
||||
};
|
||||
122
idd/LGIdd/capture/CSwapChainCursor.cpp
Normal file
122
idd/LGIdd/capture/CSwapChainCursor.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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 "capture/CSwapChainProcessor.h"
|
||||
|
||||
#include "display/IddCxCompat.h"
|
||||
#include "display/device/CDeviceContext.h"
|
||||
#include "transport/CLGMPControl.h"
|
||||
|
||||
#include "CDebug.h"
|
||||
|
||||
DWORD CALLBACK CSwapChainProcessor::_CursorThread(LPVOID arg)
|
||||
{
|
||||
reinterpret_cast<CSwapChainProcessor*>(arg)->CursorThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CSwapChainProcessor::QueryHWCursor()
|
||||
{
|
||||
IDARG_IN_QUERY_HWCURSOR in = {};
|
||||
in.LastShapeId = m_lastShapeId;
|
||||
in.pShapeBuffer = m_shapeBuffer;
|
||||
in.ShapeBufferSizeInBytes = 512 * 512 * 4;
|
||||
|
||||
IDARG_OUT_QUERY_HWCURSOR out = {};
|
||||
UINT cursorWhiteLevel = m_sdrWhiteLevel.load(std::memory_order_relaxed);
|
||||
NTSTATUS status;
|
||||
#ifdef HAS_IDDCX_110
|
||||
if (m_devContext->HasIddCx110DDIs())
|
||||
{
|
||||
IDARG_OUT_QUERY_HWCURSOR3 out3 = {};
|
||||
status = IddCxMonitorQueryHardwareCursor3(m_monitor, &in, &out3);
|
||||
out.IsCursorVisible = out3.IsCursorVisible;
|
||||
out.X = out3.X;
|
||||
out.Y = out3.Y;
|
||||
out.IsCursorShapeUpdated = out3.IsCursorShapeUpdated;
|
||||
out.CursorShapeInfo = out3.CursorShapeInfo;
|
||||
if (out3.SdrWhiteLevel)
|
||||
cursorWhiteLevel = out3.SdrWhiteLevel;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
status = IddCxMonitorQueryHardwareCursor(m_monitor, &in, &out);
|
||||
}
|
||||
|
||||
if (FAILED(status))
|
||||
{
|
||||
// this occurs if the display went away (ie, screen blanking or disabled)
|
||||
if (status == STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY)
|
||||
{
|
||||
SetEvent(m_terminateEvent.Get());
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_ERROR("IddCxMonitorQueryHardwareCursor failed (0x%08x)", status);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out.IsCursorShapeUpdated)
|
||||
m_lastShapeId = out.CursorShapeInfo.ShapeId;
|
||||
|
||||
m_control.SendCursor(out, m_shapeBuffer, cursorWhiteLevel);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSwapChainProcessor::CursorThread()
|
||||
{
|
||||
HRESULT hr = 0;
|
||||
bool running = true;
|
||||
|
||||
while (running)
|
||||
{
|
||||
HANDLE waitHandles[] =
|
||||
{
|
||||
m_cursorDataEvent.Get(),
|
||||
m_terminateEvent.Get()
|
||||
};
|
||||
|
||||
DWORD waitResult = WaitForMultipleObjects(
|
||||
ARRAYSIZE(waitHandles), waitHandles, FALSE, 100);
|
||||
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_TIMEOUT:
|
||||
continue;
|
||||
|
||||
// cursorDataEvent
|
||||
case WAIT_OBJECT_0:
|
||||
if (!QueryHWCursor())
|
||||
return;
|
||||
continue;
|
||||
|
||||
// terminateEvent
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
running = false;
|
||||
continue;
|
||||
|
||||
default:
|
||||
hr = HRESULT_FROM_WIN32(waitResult);
|
||||
DEBUG_ERROR_HR(hr, "WaitForMultipleObjects");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
738
idd/LGIdd/capture/CSwapChainProcessor.cpp
Normal file
738
idd/LGIdd/capture/CSwapChainProcessor.cpp
Normal file
@@ -0,0 +1,738 @@
|
||||
/**
|
||||
* 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 "capture/CSwapChainProcessor.h"
|
||||
#include "capture/CFrameProcessorUtil.h"
|
||||
#include "display/IddCxCompat.h"
|
||||
#include "display/device/CDeviceContext.h"
|
||||
#include "display/monitor/Context.h"
|
||||
#include "platform/CPlatformInfo.h"
|
||||
#include "transport/CFrameTransport.h"
|
||||
#include "transport/CLGMPControl.h"
|
||||
#include "util/CSRWLock.h"
|
||||
|
||||
#include <avrt.h>
|
||||
#include <new>
|
||||
#include "CDebug.h"
|
||||
#include "transport/CPipeServer.h"
|
||||
|
||||
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
|
||||
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
|
||||
#endif
|
||||
|
||||
static const uint32_t HDR_PQ_MIN_LUMINANCE = 50;
|
||||
static const uint32_t HDR_PQ_MAX_LUMINANCE = 10000;
|
||||
|
||||
CSwapChainProcessor::CSwapChainProcessor(CMonitorContext * monitorContext,
|
||||
UINT64 assignmentGeneration, IDDCX_MONITOR monitor,
|
||||
CDeviceContext * devContext, IDDCX_SWAPCHAIN hSwapChain,
|
||||
LUID renderAdapter, std::shared_ptr<CD3D11Device> dx11Device,
|
||||
HANDLE newFrameEvent) :
|
||||
m_monitorContext(monitorContext),
|
||||
m_assignmentGeneration(assignmentGeneration),
|
||||
m_monitor(monitor),
|
||||
m_devContext(devContext),
|
||||
m_transport(devContext->GetFrameTransport()),
|
||||
m_control(devContext->GetLGMPControl()),
|
||||
m_hSwapChain(hSwapChain),
|
||||
m_renderAdapter(renderAdapter),
|
||||
m_dx11Device(dx11Device),
|
||||
m_newFrameEvent(newFrameEvent)
|
||||
{
|
||||
// Manual-reset: all worker threads wait on this, so it must stay signalled
|
||||
// once set or only one thread would ever observe termination.
|
||||
m_terminateEvent.Attach(CreateEvent(nullptr, TRUE, FALSE, nullptr));
|
||||
m_publishTimer.Attach(CreateWaitableTimerExW(nullptr, nullptr,
|
||||
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS));
|
||||
if (!m_publishTimer.Get())
|
||||
m_publishTimer.Attach(CreateWaitableTimerExW(
|
||||
nullptr, nullptr, 0, TIMER_ALL_ACCESS));
|
||||
m_cursorDataEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr));
|
||||
m_shapeBuffer = new (std::nothrow) BYTE[512 * 512 * 4];
|
||||
}
|
||||
|
||||
bool CSwapChainProcessor::Start()
|
||||
{
|
||||
if (!m_terminateEvent.Get() || !m_publishTimer.Get() ||
|
||||
!m_cursorDataEvent.Get() || !m_shapeBuffer)
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize swap chain worker resources");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bind the swap chain before initializing the expensive transport pipeline.
|
||||
m_thread[0].Attach(CreateThread(
|
||||
nullptr, 0, _SwapChainThread, this, 0, nullptr));
|
||||
if (!m_thread[0].Get())
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(), "Failed to create swap chain worker");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CSwapChainProcessor::InitializePipeline()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (!m_monitorContext->IsAssignmentCurrent(m_assignmentGeneration) ||
|
||||
WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0)
|
||||
return false;
|
||||
|
||||
UINT64 alignSize = CPlatformInfo::GetPageSize();
|
||||
auto dx12Device = std::make_shared<CD3D12Device>(m_renderAdapter);
|
||||
const CD3D12Device::InitResult result = dx12Device->Init(
|
||||
m_transport.GetIVSHMEM(), alignSize, !m_dx11Device->IsSoftware());
|
||||
if (result == CD3D12Device::RETRY)
|
||||
{
|
||||
const HRESULT deviceStatus =
|
||||
m_dx11Device->GetDevice()->GetDeviceRemovedReason();
|
||||
if (FAILED(deviceStatus))
|
||||
{
|
||||
DEBUG_ERROR_HR(deviceStatus,
|
||||
"D3D11 device removed during D3D12 initialization");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (result == CD3D12Device::FAILURE)
|
||||
return false;
|
||||
|
||||
if (!m_devContext->SetupLGMP(alignSize))
|
||||
{
|
||||
DEBUG_ERROR("SetupLGMP failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_dx12Device = std::move(dx12Device);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!m_monitorContext->IsAssignmentCurrent(m_assignmentGeneration) ||
|
||||
WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0)
|
||||
return false;
|
||||
|
||||
m_resPool.Init(m_dx11Device, m_dx12Device);
|
||||
const bool enableEffects = !m_dx11Device->IsSoftware();
|
||||
if (!enableEffects)
|
||||
DEBUG_INFO("Software render adapter: post-processing disabled");
|
||||
|
||||
bool initialized = true;
|
||||
for (CPostProcessor& postProcessor : m_postProcessors)
|
||||
if (!postProcessor.Init(m_dx12Device, enableEffects))
|
||||
{
|
||||
initialized = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (initialized)
|
||||
for (unsigned i = 1; i < ARRAYSIZE(m_postProcessors); ++i)
|
||||
if (!m_postProcessors[i].ShareEffectState(m_postProcessors[0]))
|
||||
{
|
||||
DEBUG_ERROR("Post processor effect chains do not match");
|
||||
initialized = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
for (CPostProcessor& postProcessor : m_postProcessors)
|
||||
{
|
||||
postProcessor.Reset();
|
||||
if (!postProcessor.Init(m_dx12Device, false))
|
||||
DEBUG_ERROR("Failed to initialize post processor copy support");
|
||||
}
|
||||
DEBUG_WARN(
|
||||
"Failed to initialize post-processing effects; effects disabled");
|
||||
}
|
||||
|
||||
m_frameProcessor = CreateFrameProcessor(m_dx11Device->IsSoftware(),
|
||||
&m_transport, m_dx12Device, m_postProcessors,
|
||||
&m_pipelineLock, m_terminateEvent.Get());
|
||||
if (!m_frameProcessor)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the frame processor");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_monitorContext->IsAssignmentCurrent(m_assignmentGeneration) ||
|
||||
WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0)
|
||||
return false;
|
||||
|
||||
m_thread[2].Attach(CreateThread(
|
||||
nullptr, 0, _PublisherThread, this, 0, nullptr));
|
||||
if (!m_thread[2].Get())
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(), "Failed to create publisher thread");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CSwapChainProcessor::~CSwapChainProcessor()
|
||||
{
|
||||
SetEvent(m_terminateEvent.Get());
|
||||
if (m_thread[0].Get())
|
||||
WaitForSingleObject(m_thread[0].Get(), INFINITE);
|
||||
if (m_thread[1].Get())
|
||||
WaitForSingleObject(m_thread[1].Get(), INFINITE);
|
||||
if (m_thread[2].Get())
|
||||
WaitForSingleObject(m_thread[2].Get(), INFINITE);
|
||||
|
||||
// Drain in-flight GPU work / completion callbacks before releasing the
|
||||
// resources they reference. The swap chain was already released in the
|
||||
// worker epilogue, so this does not hold an IddCx frame.
|
||||
if (m_dx12Device)
|
||||
{
|
||||
m_dx12Device->WaitForIdle();
|
||||
if (m_frameProcessor)
|
||||
m_frameProcessor->Reset();
|
||||
}
|
||||
|
||||
for (CPostProcessor& postProcessor : m_postProcessors)
|
||||
postProcessor.Reset();
|
||||
m_frameProcessor.reset();
|
||||
m_resPool.Reset();
|
||||
delete[] m_shapeBuffer;
|
||||
}
|
||||
|
||||
DWORD CALLBACK CSwapChainProcessor::_SwapChainThread(LPVOID arg)
|
||||
{
|
||||
reinterpret_cast<CSwapChainProcessor*>(arg)->SwapChainThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CSwapChainProcessor::SwapChainThread()
|
||||
{
|
||||
DWORD avTask = 0;
|
||||
HANDLE avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask);
|
||||
|
||||
SwapChainThreadCore();
|
||||
|
||||
// Returning success from EvtIddCxMonitorAssignSwapChain transfers ownership
|
||||
// to the driver, regardless of whether SetDevice or later initialization
|
||||
// succeeds. Release it on every worker exit.
|
||||
WdfObjectDelete((WDFOBJECT)m_hSwapChain);
|
||||
m_hSwapChain = nullptr;
|
||||
|
||||
AvRevertMmThreadCharacteristics(avTaskHandle);
|
||||
}
|
||||
|
||||
void CSwapChainProcessor::SwapChainThreadCore()
|
||||
{
|
||||
ComPtr<IDXGIDevice> dxgiDevice;
|
||||
HRESULT hr = m_dx11Device->GetDevice().As(&dxgiDevice);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_ERROR_HR(hr, "Failed to get the dxgiDevice");
|
||||
return;
|
||||
}
|
||||
|
||||
IDARG_IN_SWAPCHAINSETDEVICE setDevice = {};
|
||||
setDevice.pDevice = dxgiDevice.Get();
|
||||
|
||||
// IddCx can unassign a swap chain before its worker binds the device. Avoid
|
||||
// using an invalidated handle; the worker epilogue still releases the
|
||||
// driver-owned swap chain.
|
||||
if (!m_monitorContext->IsAssignmentCurrent(m_assignmentGeneration) ||
|
||||
WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0)
|
||||
return;
|
||||
|
||||
// A failure here (commonly DXGI_ERROR_ACCESS_LOST on the first assignment)
|
||||
// is not recoverable on this handle - IddCx reassigns a fresh swap chain,
|
||||
// which is what actually succeeds. Bail cleanly and let that happen.
|
||||
hr = IddCxSwapChainSetDevice(m_hSwapChain, &setDevice);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
if (!m_monitorContext->IsAssignmentCurrent(m_assignmentGeneration) ||
|
||||
WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0)
|
||||
DEBUG_INFO("Swap chain was unassigned during device setup");
|
||||
else
|
||||
DEBUG_ERROR_HR(hr, "IddCxSwapChainSetDevice Failed");
|
||||
return;
|
||||
}
|
||||
DEBUG_INFO("Swap chain device set");
|
||||
|
||||
if (IDD_IS_FUNCTION_AVAILABLE(IddCxSetRealtimeGPUPriority))
|
||||
{
|
||||
DEBUG_INFO("Using IddCxSetRealtimeGPUPriority");
|
||||
IDARG_IN_SETREALTIMEGPUPRIORITY arg = {0};
|
||||
arg.pDevice = dxgiDevice.Get();
|
||||
hr = IddCxSetRealtimeGPUPriority(m_hSwapChain, &arg);
|
||||
if (FAILED(hr))
|
||||
DEBUG_ERROR_HR(hr, "Failed to set realtime GPU thread priority");
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_INFO("Using SetGPUThreadPriority");
|
||||
dxgiDevice->SetGPUThreadPriority(7);
|
||||
}
|
||||
|
||||
if (!InitializePipeline())
|
||||
return;
|
||||
|
||||
if (!m_monitorContext->IsAssignmentCurrent(m_assignmentGeneration) ||
|
||||
WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0)
|
||||
return;
|
||||
|
||||
IDARG_IN_SETUP_HWCURSOR c = {};
|
||||
c.CursorInfo.Size = sizeof(c.CursorInfo);
|
||||
c.CursorInfo.AlphaCursorSupport = TRUE;
|
||||
c.CursorInfo.ColorXorCursorSupport = IDDCX_XOR_CURSOR_SUPPORT_FULL;
|
||||
c.CursorInfo.MaxX = 512;
|
||||
c.CursorInfo.MaxY = 512;
|
||||
c.hNewCursorDataAvailable = m_cursorDataEvent.Get();
|
||||
NTSTATUS status = IddCxMonitorSetupHardwareCursor(m_monitor, &c);
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DEBUG_ERROR("IddCxMonitorSetupHardwareCursor Failed (0x%08x)", status);
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastShapeId = 0;
|
||||
m_thread[1].Attach(CreateThread(nullptr, 0, _CursorThread, this, 0, nullptr));
|
||||
|
||||
// The replacement swap chain is fully initialized and no frame has been
|
||||
// acquired yet, so a coalesced follow-up replug may now proceed safely.
|
||||
m_devContext->OnSwapChainReady();
|
||||
|
||||
// postpone sending this to ensure we dont spam messages if we end up in a
|
||||
// restart loop while waiting for a valid configuration
|
||||
g_pipe.SetGPUStatus(m_dx11Device->IsSoftware());
|
||||
|
||||
UINT lastFrameNumber = 0;
|
||||
bool hasLastFrameNumber = false;
|
||||
for (;;)
|
||||
{
|
||||
if (WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0)
|
||||
break;
|
||||
|
||||
UINT frameNumber = 0;
|
||||
UINT dirtyRectCount = 0;
|
||||
UINT moveRegionCount = 0;
|
||||
ComPtr<IDXGIResource> surface;
|
||||
|
||||
// The surface colour space is the source of truth for the content format.
|
||||
// Only the buffer2 acquisition path (IddCx 1.10+) reports it; on the legacy
|
||||
// path HDR is not available, so default to SDR.
|
||||
DXGI_COLOR_SPACE_TYPE colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
|
||||
UINT sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT;
|
||||
const uint64_t captureStart = CFrameScheduler::Nanotime();
|
||||
|
||||
#ifdef HAS_IDDCX_110
|
||||
if (m_devContext->HasIddCx110DDIs())
|
||||
{
|
||||
IDARG_IN_RELEASEANDACQUIREBUFFER2 acquireIn = {};
|
||||
acquireIn.Size = sizeof(acquireIn);
|
||||
acquireIn.AcquireSystemMemoryBuffer = FALSE;
|
||||
|
||||
IDARG_OUT_RELEASEANDACQUIREBUFFER2 buffer = {};
|
||||
buffer.MetaData.Size = sizeof(buffer.MetaData);
|
||||
|
||||
hr = IddCxSwapChainReleaseAndAcquireBuffer2(m_hSwapChain, &acquireIn, &buffer);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
frameNumber = buffer.MetaData.PresentationFrameNumber;
|
||||
dirtyRectCount = buffer.MetaData.DirtyRectCount;
|
||||
surface = buffer.MetaData.pSurface;
|
||||
colorSpace = buffer.MetaData.SurfaceColorSpace;
|
||||
sdrWhiteLevel = buffer.MetaData.SdrWhiteLevel;
|
||||
m_sdrWhiteLevel.store(sdrWhiteLevel, std::memory_order_relaxed);
|
||||
UpdateHDRMetadata(buffer.MetaData);
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
IDARG_OUT_RELEASEANDACQUIREBUFFER buffer = {};
|
||||
|
||||
hr = IddCxSwapChainReleaseAndAcquireBuffer(m_hSwapChain, &buffer);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
frameNumber = buffer.MetaData.PresentationFrameNumber;
|
||||
dirtyRectCount = buffer.MetaData.DirtyRectCount;
|
||||
moveRegionCount = buffer.MetaData.MoveRegionCount;
|
||||
surface = buffer.MetaData.pSurface;
|
||||
}
|
||||
}
|
||||
|
||||
if (hr == E_PENDING)
|
||||
{
|
||||
HANDLE waitHandles[] =
|
||||
{
|
||||
m_newFrameEvent,
|
||||
m_terminateEvent.Get()
|
||||
};
|
||||
DWORD waitResult = WaitForMultipleObjects(ARRAYSIZE(waitHandles), waitHandles, FALSE, 17);
|
||||
if (waitResult == WAIT_OBJECT_0 || waitResult == WAIT_TIMEOUT)
|
||||
continue;
|
||||
else if (waitResult == WAIT_OBJECT_0 + 1)
|
||||
break;
|
||||
else
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(waitResult);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (SUCCEEDED(hr))
|
||||
{
|
||||
const bool duplicateFrame =
|
||||
hasLastFrameNumber && frameNumber == lastFrameNumber;
|
||||
if (!duplicateFrame)
|
||||
{
|
||||
lastFrameNumber = frameNumber;
|
||||
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.
|
||||
hr = IddCxSwapChainFinishedProcessingFrame(m_hSwapChain);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
// A lost path is normal (mode change/topology rebuild); Windows
|
||||
// reassigns a fresh swap chain. Just exit and let it.
|
||||
if (hr != STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY)
|
||||
DEBUG_ERROR_HR(hr, "IddCxSwapChainFinishedProcessingFrame Failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAS_IDDCX_110
|
||||
void CSwapChainProcessor::UpdateHDRMetadata(const IDDCX_METADATA2& metadata)
|
||||
{
|
||||
if (!(metadata.ValidFlags & IDDCX_METADATA2_VALID_FLAGS_HDR10METADATA))
|
||||
return;
|
||||
|
||||
const IDDCX_HDR10_FRAME_METADATA& frame = metadata.Hdr10FrameMetaData;
|
||||
switch (frame.Type)
|
||||
{
|
||||
case IDDCX_HDR10_FRAME_METADATA_TYPE_DEFAULT:
|
||||
if (!m_useDefaultHDRMetadata)
|
||||
DEBUG_TRACE("HDR10 frame metadata switched to the monitor default");
|
||||
m_useDefaultHDRMetadata = true;
|
||||
m_hasNewHDRMetadata = false;
|
||||
break;
|
||||
|
||||
case IDDCX_HDR10_FRAME_METADATA_TYPE_UNCHANGED:
|
||||
break;
|
||||
|
||||
case IDDCX_HDR10_FRAME_METADATA_TYPE_NEW:
|
||||
if (!m_hasNewHDRMetadata ||
|
||||
memcmp(&m_newHDRMetadata, &frame.NewMetaData,
|
||||
sizeof(m_newHDRMetadata)) != 0)
|
||||
DEBUG_TRACE("Received new HDR10 frame metadata");
|
||||
m_newHDRMetadata = frame.NewMetaData;
|
||||
m_useDefaultHDRMetadata = false;
|
||||
m_hasNewHDRMetadata = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_WARN("Invalid HDR10 frame metadata type %u",
|
||||
static_cast<unsigned>(frame.Type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool CSwapChainProcessor::GetContentHDRMetadata(D12FrameFormat& format) const
|
||||
{
|
||||
#ifdef HAS_IDDCX_110
|
||||
// The monitor default describes the virtual display, not the content. Only
|
||||
// publish an explicit per-frame metadata block to downstream consumers.
|
||||
if (m_useDefaultHDRMetadata || !m_hasNewHDRMetadata)
|
||||
return false;
|
||||
|
||||
const IDDCX_HDR10_METADATA& metadata = m_newHDRMetadata;
|
||||
format.displayPrimary[0][0] = metadata.RedPrimary [0];
|
||||
format.displayPrimary[0][1] = metadata.RedPrimary [1];
|
||||
format.displayPrimary[1][0] = metadata.GreenPrimary[0];
|
||||
format.displayPrimary[1][1] = metadata.GreenPrimary[1];
|
||||
format.displayPrimary[2][0] = metadata.BluePrimary [0];
|
||||
format.displayPrimary[2][1] = metadata.BluePrimary [1];
|
||||
format.whitePoint [0] = metadata.WhitePoint [0];
|
||||
format.whitePoint [1] = metadata.WhitePoint [1];
|
||||
format.maxDisplayLuminance = metadata.MaxMasteringLuminance;
|
||||
format.minDisplayLuminance = metadata.MinMasteringLuminance;
|
||||
format.maxContentLightLevel = metadata.MaxContentLightLevel;
|
||||
format.maxFrameAverageLightLevel = metadata.MaxFrameAverageLightLevel;
|
||||
return true;
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(format);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer,
|
||||
unsigned dirtyRectCount, unsigned moveRegionCount,
|
||||
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel,
|
||||
uint64_t captureStart, bool duplicateFrame)
|
||||
{
|
||||
const uint64_t postProcessStart = CFrameScheduler::Nanotime();
|
||||
const uint64_t captureTime = postProcessStart - captureStart;
|
||||
|
||||
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.
|
||||
fullDamage = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
IDARG_IN_GETDIRTYRECTS dirtyIn = {};
|
||||
dirtyIn.DirtyRectInCount = dirtyRectCount;
|
||||
dirtyIn.pDirtyRects = dirtyRects;
|
||||
|
||||
IDARG_OUT_GETDIRTYRECTS dirtyOut = {};
|
||||
hr = IddCxSwapChainGetDirtyRects(m_hSwapChain, &dirtyIn, &dirtyOut);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_ERROR_HR(hr, "IddCxSwapChainGetDirtyRects Failed");
|
||||
fullDamage = true;
|
||||
}
|
||||
else if (dirtyOut.DirtyRectOutCount == 1 &&
|
||||
dirtyRects[0].left == 0 && dirtyRects[0].top == 0 &&
|
||||
dirtyRects[0].right == 0 && dirtyRects[0].bottom == 0)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
else
|
||||
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");
|
||||
m_frameProcessor->SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
CInteropResource * srcRes = m_resPool.Get(texture);
|
||||
if (!srcRes)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get a CInteropResource from the pool");
|
||||
m_frameProcessor->SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fullDamage)
|
||||
srcRes->SetFullDamage();
|
||||
else
|
||||
srcRes->SetDirtyRects(dirtyRects, resolvedDirtyRectCount);
|
||||
|
||||
D3D12_RESOURCE_DESC srcDesc = srcRes->GetRes()->GetDesc();
|
||||
if (!noImageUpdate)
|
||||
{
|
||||
m_transport.ObserveFrame(postProcessStart);
|
||||
m_frameProcessor->AccumulateDamage(
|
||||
srcRes->GetDirtyRects(), srcRes->GetDirtyRectCount());
|
||||
}
|
||||
|
||||
D12FrameFormat srcFormat = {};
|
||||
srcFormat.desc = srcDesc;
|
||||
srcFormat.width = (unsigned)srcDesc.Width;
|
||||
srcFormat.height = srcDesc.Height;
|
||||
srcFormat.format = CFrameProcessorUtil::GetFrameType(srcDesc.Format);
|
||||
srcFormat.sdrWhiteLevel = sdrWhiteLevel;
|
||||
srcFormat.colorTransform = m_control.GetColorTransform();
|
||||
|
||||
switch (colorSpace)
|
||||
{
|
||||
case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020:
|
||||
case DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020:
|
||||
// HDR10: BT.2020 primaries with the PQ (ST.2084) transfer function
|
||||
// already applied to the pixel data.
|
||||
srcFormat.hdr = true;
|
||||
srcFormat.hdrPQ = true;
|
||||
if (!GetContentHDRMetadata(srcFormat))
|
||||
{
|
||||
// No per-content metadata is active. The pixels are still PQ-encoded,
|
||||
// so keep the PQ flag and use BT.2020/PQ defaults internally rather
|
||||
// than publishing the virtual monitor metadata as content metadata.
|
||||
// BT.2020 primaries (in 0.00002 units):
|
||||
srcFormat.displayPrimary[0][0] = 35400; // Rx
|
||||
srcFormat.displayPrimary[0][1] = 14600; // Ry
|
||||
srcFormat.displayPrimary[1][0] = 8500; // Gx
|
||||
srcFormat.displayPrimary[1][1] = 39850; // Gy
|
||||
srcFormat.displayPrimary[2][0] = 6550; // Bx
|
||||
srcFormat.displayPrimary[2][1] = 2300; // By
|
||||
// D65 white point (in 0.00002 units):
|
||||
srcFormat.whitePoint[0] = 15635;
|
||||
srcFormat.whitePoint[1] = 16450;
|
||||
// Cover the complete PQ signal range.
|
||||
srcFormat.maxDisplayLuminance = HDR_PQ_MAX_LUMINANCE;
|
||||
srcFormat.minDisplayLuminance = HDR_PQ_MIN_LUMINANCE;
|
||||
// Content light levels unknown:
|
||||
srcFormat.maxContentLightLevel = 0;
|
||||
srcFormat.maxFrameAverageLightLevel = 0;
|
||||
}
|
||||
else
|
||||
srcFormat.hdrMetadata = true;
|
||||
break;
|
||||
|
||||
case DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709:
|
||||
// scRGB: linear (FP16) content with BT.709 primaries. HDR, but the PQ
|
||||
// curve has not been applied.
|
||||
srcFormat.hdr = true;
|
||||
srcFormat.hdrPQ = false;
|
||||
if (!GetContentHDRMetadata(srcFormat))
|
||||
{
|
||||
// No per-content metadata is active. Use reasonable internal defaults
|
||||
// without publishing the virtual monitor metadata downstream.
|
||||
// BT.709/sRGB primaries (in 0.00002 units):
|
||||
srcFormat.displayPrimary[0][0] = 32000; // Rx
|
||||
srcFormat.displayPrimary[0][1] = 16500; // Ry
|
||||
srcFormat.displayPrimary[1][0] = 15000; // Gx
|
||||
srcFormat.displayPrimary[1][1] = 30000; // Gy
|
||||
srcFormat.displayPrimary[2][0] = 7500; // Bx
|
||||
srcFormat.displayPrimary[2][1] = 3000; // By
|
||||
// D65 white point (in 0.00002 units):
|
||||
srcFormat.whitePoint[0] = 15635;
|
||||
srcFormat.whitePoint[1] = 16450;
|
||||
// Mastering luminances follow SMPTE ST 2086 units: max in whole cd/m²,
|
||||
// min in 0.0001 cd/m². 80 cd/m² display, 0.005 cd/m² black:
|
||||
srcFormat.maxDisplayLuminance = 80;
|
||||
srcFormat.minDisplayLuminance = 50;
|
||||
// Content light levels unknown:
|
||||
srcFormat.maxContentLightLevel = 0;
|
||||
srcFormat.maxFrameAverageLightLevel = 0;
|
||||
}
|
||||
else
|
||||
srcFormat.hdrMetadata = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Everything else (e.g. RGB_FULL_G22_NONE_P709) is SDR.
|
||||
srcFormat.hdr = false;
|
||||
srcFormat.hdrPQ = false;
|
||||
break;
|
||||
}
|
||||
|
||||
bool frameMetadataChanged = false;
|
||||
bool needsReconfigure = false;
|
||||
bool postProcessFormatChanged = false;
|
||||
bool requiresFullDamage = false;
|
||||
unsigned timingEffectIndex = 0;
|
||||
uint64_t timingToken = 0;
|
||||
{
|
||||
CSRWExclusiveLock pipelineLock(&m_pipelineLock);
|
||||
m_postProcessors[0].Update(srcFormat);
|
||||
|
||||
frameMetadataChanged = noImageUpdate &&
|
||||
CFrameProcessorUtil::FrameMetadataChanged(
|
||||
m_postProcessors[0].GetOutputFormat(), srcFormat);
|
||||
|
||||
for (const CPostProcessor& postProcessor : m_postProcessors)
|
||||
if (postProcessor.NeedsReconfigure(srcFormat))
|
||||
{
|
||||
needsReconfigure = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// A format change can replace resources referenced by in-flight work.
|
||||
// Drain both queues before invalidating the selected frame processor.
|
||||
if (needsReconfigure)
|
||||
{
|
||||
m_dx12Device->WaitForIdle();
|
||||
m_frameProcessor->ResetPipeline();
|
||||
}
|
||||
|
||||
bool configurationStable = false;
|
||||
for (unsigned pass = 0; pass < 2 && !configurationStable; ++pass)
|
||||
{
|
||||
for (unsigned i = 0; i < ARRAYSIZE(m_postProcessors); ++i)
|
||||
{
|
||||
bool formatChanged = false;
|
||||
if (!m_postProcessors[i].Configure(srcFormat, &formatChanged))
|
||||
{
|
||||
m_frameProcessor->SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
postProcessFormatChanged |= formatChanged;
|
||||
}
|
||||
|
||||
configurationStable = true;
|
||||
for (const CPostProcessor& postProcessor : m_postProcessors)
|
||||
if (postProcessor.NeedsReconfigure(srcFormat))
|
||||
{
|
||||
configurationStable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!configurationStable)
|
||||
{
|
||||
DEBUG_ERROR("Post processor configuration did not stabilize");
|
||||
m_frameProcessor->SetFullDamage();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (postProcessFormatChanged)
|
||||
m_frameProcessor->Invalidate();
|
||||
else if (frameMetadataChanged)
|
||||
m_frameProcessor->SetFullDamage();
|
||||
|
||||
requiresFullDamage = m_postProcessors[0].RequiresFullDamage();
|
||||
if (requiresFullDamage)
|
||||
m_frameProcessor->SetFullDamage();
|
||||
|
||||
m_postProcessors[0].GetTimingToken(
|
||||
&timingEffectIndex, &timingToken);
|
||||
}
|
||||
|
||||
if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged)
|
||||
m_transport.ForceFrame();
|
||||
|
||||
const FrameSubmission submission =
|
||||
{
|
||||
srcRes,
|
||||
srcFormat,
|
||||
captureTime,
|
||||
postProcessStart,
|
||||
timingEffectIndex,
|
||||
timingToken,
|
||||
noImageUpdate,
|
||||
};
|
||||
return m_frameProcessor->Submit(submission);
|
||||
}
|
||||
109
idd/LGIdd/capture/CSwapChainProcessor.h
Normal file
109
idd/LGIdd/capture/CSwapChainProcessor.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "d3d/CD3D11Device.h"
|
||||
#include "d3d/CD3D12Device.h"
|
||||
#include "display/IddCxCompat.h"
|
||||
#include "d3d/CInteropResourcePool.h"
|
||||
#include "capture/CFrameProcessor.h"
|
||||
#include "common/KVMFR.h"
|
||||
#include "postprocess/CPostProcessor.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <wrl.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
class CMonitorContext;
|
||||
class CDeviceContext;
|
||||
class CFrameTransport;
|
||||
class CLGMPControl;
|
||||
|
||||
class CSwapChainProcessor
|
||||
{
|
||||
private:
|
||||
CMonitorContext * m_monitorContext;
|
||||
UINT64 m_assignmentGeneration;
|
||||
IDDCX_MONITOR m_monitor;
|
||||
CDeviceContext * m_devContext;
|
||||
CFrameTransport & m_transport;
|
||||
CLGMPControl & m_control;
|
||||
IDDCX_SWAPCHAIN m_hSwapChain;
|
||||
LUID m_renderAdapter;
|
||||
std::shared_ptr<CD3D11Device> m_dx11Device;
|
||||
std::shared_ptr<CD3D12Device> m_dx12Device;
|
||||
HANDLE m_newFrameEvent;
|
||||
|
||||
CInteropResourcePool m_resPool;
|
||||
CPostProcessor m_postProcessors[LGMP_Q_FRAME_LEN];
|
||||
std::unique_ptr<CFrameProcessor> m_frameProcessor;
|
||||
// Reconfiguration is exclusive while per-candidate recording is shared.
|
||||
SRWLOCK m_pipelineLock = SRWLOCK_INIT;
|
||||
|
||||
Wrappers::HandleT<Wrappers::HandleTraits::HANDLENullTraits> m_thread[3];
|
||||
Wrappers::Event m_terminateEvent;
|
||||
Wrappers::HandleT<Wrappers::HandleTraits::HANDLENullTraits> m_publishTimer;
|
||||
|
||||
Wrappers::Event m_cursorDataEvent;
|
||||
BYTE* m_shapeBuffer;
|
||||
DWORD m_lastShapeId = 0;
|
||||
std::atomic<UINT> m_sdrWhiteLevel { KVMFR_SDR_WHITE_LEVEL_DEFAULT };
|
||||
|
||||
#ifdef HAS_IDDCX_110
|
||||
// The per-frame metadata stream can select the monitor default, provide a
|
||||
// replacement block, or retain the selection from the previous frame.
|
||||
bool m_useDefaultHDRMetadata = true;
|
||||
bool m_hasNewHDRMetadata = false;
|
||||
IDDCX_HDR10_METADATA m_newHDRMetadata = {};
|
||||
#endif
|
||||
|
||||
static DWORD CALLBACK _SwapChainThread(LPVOID arg);
|
||||
void SwapChainThread();
|
||||
void SwapChainThreadCore();
|
||||
bool InitializePipeline();
|
||||
|
||||
static DWORD CALLBACK _PublisherThread(LPVOID arg);
|
||||
void PublisherThread();
|
||||
static DWORD CALLBACK _CursorThread(LPVOID arg);
|
||||
bool QueryHWCursor();
|
||||
void CursorThread();
|
||||
|
||||
#ifdef HAS_IDDCX_110
|
||||
void UpdateHDRMetadata(const IDDCX_METADATA2& metadata);
|
||||
#endif
|
||||
bool GetContentHDRMetadata(D12FrameFormat& format) const;
|
||||
bool SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer,
|
||||
unsigned dirtyRectCount, unsigned moveRegionCount,
|
||||
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel,
|
||||
uint64_t captureStart, bool duplicateFrame);
|
||||
|
||||
public:
|
||||
CSwapChainProcessor(CMonitorContext * monitorContext,
|
||||
UINT64 assignmentGeneration, IDDCX_MONITOR monitor,
|
||||
CDeviceContext * devContext, IDDCX_SWAPCHAIN hSwapChain,
|
||||
LUID renderAdapter, std::shared_ptr<CD3D11Device> dx11Device,
|
||||
HANDLE newFrameEvent);
|
||||
~CSwapChainProcessor();
|
||||
bool Start();
|
||||
};
|
||||
268
idd/LGIdd/capture/CSwapChainPublisher.cpp
Normal file
268
idd/LGIdd/capture/CSwapChainPublisher.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* 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 "capture/CSwapChainProcessor.h"
|
||||
|
||||
#include "transport/CFrameTransport.h"
|
||||
|
||||
#include <avrt.h>
|
||||
#include "CDebug.h"
|
||||
|
||||
static const uint64_t PUBLISH_RETRY_NS = 1000000ULL;
|
||||
|
||||
static bool ArmPublishTimer(HANDLE timer, uint64_t delay)
|
||||
{
|
||||
if (!timer)
|
||||
return false;
|
||||
|
||||
LARGE_INTEGER due = {};
|
||||
due.QuadPart = -static_cast<LONGLONG>((delay + 99) / 100);
|
||||
if (!due.QuadPart)
|
||||
due.QuadPart = -1;
|
||||
return SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE) != FALSE;
|
||||
}
|
||||
|
||||
DWORD CALLBACK CSwapChainProcessor::_PublisherThread(LPVOID arg)
|
||||
{
|
||||
reinterpret_cast<CSwapChainProcessor *>(arg)->PublisherThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CSwapChainProcessor::PublisherThread()
|
||||
{
|
||||
DWORD avTask = 0;
|
||||
HANDLE avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask);
|
||||
if (avTaskHandle &&
|
||||
!AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH))
|
||||
DEBUG_WARN("Failed to raise publisher MMCSS priority: %lu",
|
||||
GetLastError());
|
||||
|
||||
const HANDLE scheduleEvent = m_transport.GetFrameScheduleEvent();
|
||||
HANDLE idleHandles[] =
|
||||
{
|
||||
m_terminateEvent.Get(),
|
||||
m_frameProcessor->GetReadyEvent(),
|
||||
scheduleEvent,
|
||||
};
|
||||
HANDLE timerHandles[] =
|
||||
{
|
||||
m_terminateEvent.Get(),
|
||||
m_frameProcessor->GetReadyEvent(),
|
||||
scheduleEvent,
|
||||
m_publishTimer.Get(),
|
||||
};
|
||||
const bool cadenceEnabled = m_frameProcessor->UsesCadence();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const uint64_t now = CFrameScheduler::Nanotime();
|
||||
uint64_t target;
|
||||
CFrameScheduler::Schedule schedule;
|
||||
bool periodic;
|
||||
bool republish;
|
||||
m_transport.GetPublishTarget(
|
||||
now, target, schedule, periodic, republish);
|
||||
|
||||
const bool ready = m_frameProcessor->HasReadyFrame();
|
||||
if (!ready)
|
||||
{
|
||||
m_transport.ProcessFrameQueue();
|
||||
if (m_frameProcessor->HasReadyFrame())
|
||||
continue;
|
||||
|
||||
uint64_t current = CFrameScheduler::Nanotime();
|
||||
uint64_t cadenceTarget = 0;
|
||||
if (cadenceEnabled && schedule.deliveryDeadlineSerial && periodic)
|
||||
{
|
||||
if (schedule.deadline <= current)
|
||||
{
|
||||
m_transport.FrameMissed(schedule, current, periodic);
|
||||
continue;
|
||||
}
|
||||
cadenceTarget = schedule.deadline;
|
||||
}
|
||||
|
||||
if (republish && m_transport.HasPublishedFrame())
|
||||
{
|
||||
if (m_transport.RepublishFrameBuffer(schedule))
|
||||
continue;
|
||||
|
||||
current = CFrameScheduler::Nanotime();
|
||||
if (cadenceTarget && cadenceTarget <= current)
|
||||
{
|
||||
m_transport.FrameMissed(schedule, current, periodic);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t retryTarget = current + PUBLISH_RETRY_NS;
|
||||
if (cadenceTarget)
|
||||
retryTarget = min(retryTarget, cadenceTarget);
|
||||
ArmPublishTimer(m_publishTimer.Get(), retryTarget - current);
|
||||
if (WaitForMultipleObjects(
|
||||
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
|
||||
WAIT_OBJECT_0)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t replayTarget;
|
||||
if (m_transport.GetSharedFrameTarget(current, replayTarget))
|
||||
{
|
||||
bool retry = false;
|
||||
if (replayTarget <= current)
|
||||
{
|
||||
if (m_transport.ReplaySharedFrame(current, retry))
|
||||
continue;
|
||||
|
||||
current = CFrameScheduler::Nanotime();
|
||||
if (cadenceTarget && cadenceTarget <= current)
|
||||
{
|
||||
m_transport.FrameMissed(schedule, current, periodic);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (retry)
|
||||
replayTarget = current + PUBLISH_RETRY_NS;
|
||||
else
|
||||
{
|
||||
if (cadenceTarget)
|
||||
replayTarget = cadenceTarget;
|
||||
else
|
||||
{
|
||||
if (m_publishTimer.Get())
|
||||
CancelWaitableTimer(m_publishTimer.Get());
|
||||
if (WaitForMultipleObjects(
|
||||
ARRAYSIZE(idleHandles), idleHandles, FALSE, INFINITE) ==
|
||||
WAIT_OBJECT_0)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cadenceTarget)
|
||||
replayTarget = min(replayTarget, cadenceTarget);
|
||||
|
||||
current = CFrameScheduler::Nanotime();
|
||||
if (cadenceTarget && cadenceTarget <= current)
|
||||
{
|
||||
m_transport.FrameMissed(schedule, current, periodic);
|
||||
continue;
|
||||
}
|
||||
if (replayTarget <= current)
|
||||
continue;
|
||||
|
||||
ArmPublishTimer(m_publishTimer.Get(), replayTarget - current);
|
||||
if (WaitForMultipleObjects(
|
||||
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
|
||||
WAIT_OBJECT_0)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cadenceTarget)
|
||||
{
|
||||
current = CFrameScheduler::Nanotime();
|
||||
if (cadenceTarget <= current)
|
||||
{
|
||||
m_transport.FrameMissed(schedule, current, periodic);
|
||||
continue;
|
||||
}
|
||||
|
||||
ArmPublishTimer(m_publishTimer.Get(), cadenceTarget - current);
|
||||
if (WaitForMultipleObjects(
|
||||
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
|
||||
WAIT_OBJECT_0)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_publishTimer.Get())
|
||||
CancelWaitableTimer(m_publishTimer.Get());
|
||||
if (WaitForMultipleObjects(
|
||||
ARRAYSIZE(idleHandles), idleHandles, FALSE, INFINITE) ==
|
||||
WAIT_OBJECT_0)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t current = CFrameScheduler::Nanotime();
|
||||
uint64_t replayTarget;
|
||||
if (m_transport.GetSharedFrameTarget(current, replayTarget) &&
|
||||
replayTarget < target)
|
||||
{
|
||||
if (replayTarget <= current)
|
||||
{
|
||||
m_transport.ProcessFrameQueue();
|
||||
current = CFrameScheduler::Nanotime();
|
||||
bool retry = false;
|
||||
if (m_transport.ReplaySharedFrame(current, retry))
|
||||
continue;
|
||||
|
||||
current = CFrameScheduler::Nanotime();
|
||||
if (retry)
|
||||
replayTarget = current + PUBLISH_RETRY_NS;
|
||||
else
|
||||
replayTarget = target;
|
||||
}
|
||||
|
||||
replayTarget = min(replayTarget, target);
|
||||
current = CFrameScheduler::Nanotime();
|
||||
if (target > current)
|
||||
{
|
||||
if (replayTarget <= current)
|
||||
continue;
|
||||
|
||||
ArmPublishTimer(m_publishTimer.Get(), replayTarget - current);
|
||||
if (WaitForMultipleObjects(
|
||||
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
|
||||
WAIT_OBJECT_0)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
current = CFrameScheduler::Nanotime();
|
||||
if (target > current)
|
||||
{
|
||||
ArmPublishTimer(m_publishTimer.Get(), target - current);
|
||||
if (WaitForMultipleObjects(
|
||||
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
|
||||
WAIT_OBJECT_0)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint64_t publishStart = CFrameScheduler::Nanotime();
|
||||
m_transport.ProcessFrameQueue();
|
||||
if (!m_transport.FrameBufferAvailable(schedule) ||
|
||||
!m_frameProcessor->Publish(schedule, periodic, publishStart))
|
||||
{
|
||||
ArmPublishTimer(m_publishTimer.Get(), PUBLISH_RETRY_NS);
|
||||
if (WaitForMultipleObjects(
|
||||
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
|
||||
WAIT_OBJECT_0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (avTaskHandle)
|
||||
AvRevertMmThreadCharacteristics(avTaskHandle);
|
||||
}
|
||||
40
idd/LGIdd/capture/FrameBufferTypes.h
Normal file
40
idd/LGIdd/capture/FrameBufferTypes.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// FrameBuffer overlays LGMP shared memory and has a variable-length payload.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4200)
|
||||
struct FrameBuffer
|
||||
{
|
||||
volatile uint32_t wp;
|
||||
uint8_t data[0];
|
||||
};
|
||||
#pragma warning(pop)
|
||||
|
||||
struct PreparedFrameBuffer
|
||||
{
|
||||
unsigned frameIndex;
|
||||
uint8_t * mem;
|
||||
bool fullCopy;
|
||||
};
|
||||
Reference in New Issue
Block a user