mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 23:21:29 +00:00
[idd] transport: isolate interactive services
This commit is contained in:
@@ -20,120 +20,529 @@
|
||||
|
||||
#include "transport/CControlHub.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#include "CDebug.h"
|
||||
|
||||
std::vector<std::shared_ptr<CControlHub::Sink>> CControlHub::Snapshot() const
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
CControlHub::CControlHub()
|
||||
{
|
||||
CSRWSharedLock lock(m_sinkLock);
|
||||
return m_sinks;
|
||||
m_stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
if (!m_stopEvent)
|
||||
return;
|
||||
|
||||
m_valid = true;
|
||||
for (unsigned i = 0; i < MAX_SINKS; ++i)
|
||||
{
|
||||
Sink& sink = m_sinks[i];
|
||||
sink.owner = this;
|
||||
sink.index = i;
|
||||
sink.wake = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
sink.idle = CreateEvent(nullptr, TRUE, TRUE, nullptr);
|
||||
if (!sink.wake || !sink.idle)
|
||||
{
|
||||
m_valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
sink.thread = CreateThread(nullptr, 0, WorkerProc, &sink, 0, nullptr);
|
||||
if (!sink.thread)
|
||||
{
|
||||
m_valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_valid)
|
||||
{
|
||||
SetEvent(m_stopEvent);
|
||||
for (Sink& sink : m_sinks)
|
||||
if (sink.wake)
|
||||
SetEvent(sink.wake);
|
||||
}
|
||||
}
|
||||
|
||||
CControlHub::~CControlHub()
|
||||
{
|
||||
for (Sink& sink : m_sinks)
|
||||
{
|
||||
BackendId backend;
|
||||
uint32_t epoch;
|
||||
{
|
||||
CSRWSharedLock lock(sink.lock);
|
||||
backend = sink.backend;
|
||||
epoch = sink.epoch;
|
||||
}
|
||||
if (backend && epoch)
|
||||
Remove(backend, epoch);
|
||||
}
|
||||
|
||||
if (m_stopEvent)
|
||||
SetEvent(m_stopEvent);
|
||||
for (Sink& sink : m_sinks)
|
||||
if (sink.wake)
|
||||
SetEvent(sink.wake);
|
||||
|
||||
for (Sink& sink : m_sinks)
|
||||
{
|
||||
if (sink.thread)
|
||||
WaitForSingleObject(sink.thread, INFINITE);
|
||||
if (sink.thread)
|
||||
CloseHandle(sink.thread);
|
||||
if (sink.idle)
|
||||
CloseHandle(sink.idle);
|
||||
if (sink.wake)
|
||||
CloseHandle(sink.wake);
|
||||
}
|
||||
if (m_stopEvent)
|
||||
CloseHandle(m_stopEvent);
|
||||
}
|
||||
|
||||
DWORD WINAPI CControlHub::WorkerProc(void * opaque)
|
||||
{
|
||||
Sink * sink = static_cast<Sink *>(opaque);
|
||||
sink->owner->Worker(*sink);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t CControlHub::NextRevision(uint64_t revision)
|
||||
{
|
||||
++revision;
|
||||
return revision ? revision : 1;
|
||||
}
|
||||
|
||||
bool CControlHub::TokenMatches(
|
||||
const Sink& sink, const ControlToken& token)
|
||||
{
|
||||
return sink.backend == token.backend && sink.epoch == token.epoch;
|
||||
}
|
||||
|
||||
bool CControlHub::Add(
|
||||
BackendId backend, uint32_t epoch, IControlTransport& control)
|
||||
BackendId backend, uint32_t epoch, IControlSink& control)
|
||||
{
|
||||
std::shared_ptr<Sink> sink(new (std::nothrow) Sink);
|
||||
if (!sink)
|
||||
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||
if (!backend || !epoch || !m_valid)
|
||||
return false;
|
||||
|
||||
sink->backend = backend;
|
||||
sink->epoch = epoch;
|
||||
sink->control = &control;
|
||||
Sink * selected = nullptr;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_sinkLock);
|
||||
for (const auto& current : m_sinks)
|
||||
if (current->backend == backend && current->epoch == epoch)
|
||||
CSRWExclusiveLock lock(m_listLock);
|
||||
for (Sink& sink : m_sinks)
|
||||
{
|
||||
CSRWSharedLock sinkLock(sink.lock);
|
||||
if ((sink.active || sink.reserved) &&
|
||||
sink.backend == backend && sink.epoch == epoch)
|
||||
return false;
|
||||
m_sinks.push_back(sink);
|
||||
}
|
||||
}
|
||||
|
||||
Replay(sink);
|
||||
for (Sink& sink : m_sinks)
|
||||
{
|
||||
CSRWExclusiveLock sinkLock(sink.lock);
|
||||
if (!sink.active && !sink.failed && !sink.reserved)
|
||||
{
|
||||
sink.reserved = true;
|
||||
sink.backend = backend;
|
||||
sink.epoch = epoch;
|
||||
selected = &sink;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!selected)
|
||||
return false;
|
||||
|
||||
const ControlToken token = { backend, epoch };
|
||||
{
|
||||
CSRWExclusiveLock lock(selected->lock);
|
||||
selected->target = &control;
|
||||
selected->deliveredPosition = 0;
|
||||
selected->deliveredShape = 0;
|
||||
selected->deliveredTransform = 0;
|
||||
memset(selected->retryAt, 0, sizeof(selected->retryAt));
|
||||
selected->nextWork = 0;
|
||||
selected->bindingSerial = NextRevision(selected->bindingSerial);
|
||||
selected->replaySerial = NextRevision(selected->replaySerial);
|
||||
selected->calling = false;
|
||||
selected->active = false;
|
||||
selected->failed = false;
|
||||
selected->failurePending = false;
|
||||
}
|
||||
control.SetControlEvents(this, token);
|
||||
bool attached = false;
|
||||
{
|
||||
CSRWExclusiveLock lock(selected->lock);
|
||||
if (selected->target == &control && selected->backend == backend &&
|
||||
selected->epoch == epoch && selected->reserved && !selected->failed)
|
||||
{
|
||||
selected->replaySerial = NextRevision(selected->replaySerial);
|
||||
selected->active = true;
|
||||
selected->reserved = false;
|
||||
attached = true;
|
||||
}
|
||||
}
|
||||
if (!attached)
|
||||
{
|
||||
control.SetControlEvents(nullptr, {});
|
||||
SetEvent(selected->wake);
|
||||
WaitForSingleObject(selected->idle, INFINITE);
|
||||
CSRWExclusiveLock listLock(m_listLock);
|
||||
CSRWExclusiveLock sinkLock(selected->lock);
|
||||
if (selected->target == &control && selected->backend == backend &&
|
||||
selected->epoch == epoch)
|
||||
{
|
||||
selected->target = nullptr;
|
||||
selected->backend = 0;
|
||||
selected->epoch = 0;
|
||||
selected->active = false;
|
||||
selected->reserved = false;
|
||||
selected->failed = false;
|
||||
selected->failurePending = false;
|
||||
selected->calling = false;
|
||||
memset(selected->retryAt, 0, sizeof(selected->retryAt));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
SetEvent(selected->wake);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CControlHub::Remove(BackendId backend, uint32_t epoch)
|
||||
{
|
||||
std::shared_ptr<Sink> removed;
|
||||
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||
Sink * selected = nullptr;
|
||||
IControlSink * target = nullptr;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_sinkLock);
|
||||
for (auto current = m_sinks.begin(); current != m_sinks.end(); ++current)
|
||||
if ((*current)->backend == backend && (*current)->epoch == epoch)
|
||||
CSRWExclusiveLock listLock(m_listLock);
|
||||
for (Sink& sink : m_sinks)
|
||||
{
|
||||
CSRWExclusiveLock lock(sink.lock);
|
||||
if ((sink.active || sink.failed) &&
|
||||
sink.backend == backend && sink.epoch == epoch)
|
||||
{
|
||||
removed = *current;
|
||||
m_sinks.erase(current);
|
||||
sink.active = false;
|
||||
sink.failed = false;
|
||||
sink.failurePending = false;
|
||||
sink.reserved = true;
|
||||
target = sink.target;
|
||||
selected = &sink;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!selected)
|
||||
return;
|
||||
|
||||
SetEvent(selected->wake);
|
||||
WaitForSingleObject(selected->idle, INFINITE);
|
||||
if (target)
|
||||
target->SetControlEvents(nullptr, {});
|
||||
|
||||
if (removed)
|
||||
{
|
||||
CSRWExclusiveLock lock(removed->lock);
|
||||
removed->control = nullptr;
|
||||
CSRWExclusiveLock listLock(m_listLock);
|
||||
CSRWExclusiveLock sinkLock(selected->lock);
|
||||
if (selected->reserved && selected->backend == backend &&
|
||||
selected->epoch == epoch)
|
||||
{
|
||||
selected->target = nullptr;
|
||||
selected->backend = 0;
|
||||
selected->epoch = 0;
|
||||
selected->reserved = false;
|
||||
selected->failed = false;
|
||||
selected->failurePending = false;
|
||||
selected->calling = false;
|
||||
memset(selected->retryAt, 0, sizeof(selected->retryAt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CControlHub::Replay(const std::shared_ptr<Sink>& sink)
|
||||
bool CControlHub::TakeFailure(ControlToken& token)
|
||||
{
|
||||
IDARG_OUT_QUERY_HWCURSOR cursor = {};
|
||||
std::vector<BYTE> cursorData;
|
||||
UINT sdrWhiteLevel;
|
||||
bool cursorValid;
|
||||
std::shared_ptr<const D12ColorTransform> transform;
|
||||
CSRWSharedLock lifecycleLock(m_lifecycleLock);
|
||||
CSRWSharedLock listLock(m_listLock);
|
||||
for (Sink& sink : m_sinks)
|
||||
{
|
||||
CSRWExclusiveLock lock(sink.lock);
|
||||
if (!sink.failurePending)
|
||||
continue;
|
||||
token.backend = sink.backend;
|
||||
token.epoch = sink.epoch;
|
||||
sink.failurePending = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CControlHub::OnControlReplay(const ControlToken& token)
|
||||
{
|
||||
CSRWSharedLock listLock(m_listLock);
|
||||
for (Sink& sink : m_sinks)
|
||||
{
|
||||
CSRWExclusiveLock lock(sink.lock);
|
||||
if ((!sink.active && !sink.reserved) ||
|
||||
!TokenMatches(sink, token))
|
||||
continue;
|
||||
|
||||
sink.deliveredPosition = 0;
|
||||
sink.deliveredShape = 0;
|
||||
sink.deliveredTransform = 0;
|
||||
sink.replaySerial = NextRevision(sink.replaySerial);
|
||||
memset(sink.retryAt, 0, sizeof(sink.retryAt));
|
||||
SetEvent(sink.wake);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool CControlHub::BeginWork(Sink& sink, Work& work, DWORD& wait)
|
||||
{
|
||||
const uint64_t now = GetTickCount64();
|
||||
State state;
|
||||
{
|
||||
CSRWSharedLock lock(m_stateLock);
|
||||
cursor = m_cursor;
|
||||
cursorData = m_cursorData;
|
||||
sdrWhiteLevel = m_sdrWhiteLevel;
|
||||
cursorValid = m_cursorValid;
|
||||
transform = m_colorTransform;
|
||||
state.transform = m_state.transform;
|
||||
state.cursor = m_state.cursor;
|
||||
state.cursorData = m_state.cursorData;
|
||||
state.sdrWhiteLevel = m_state.sdrWhiteLevel;
|
||||
state.positionRevision = m_state.positionRevision;
|
||||
state.shapeRevision = m_state.shapeRevision;
|
||||
state.transformRevision = m_state.transformRevision;
|
||||
}
|
||||
|
||||
CSRWSharedLock lock(sink->lock);
|
||||
if (!sink->control)
|
||||
return;
|
||||
sink->control->SetColorTransform(std::move(transform));
|
||||
if (cursorValid)
|
||||
sink->control->SendCursor(
|
||||
cursor, cursorData.empty() ? nullptr : cursorData.data(),
|
||||
sdrWhiteLevel);
|
||||
CSRWExclusiveLock lock(sink.lock);
|
||||
if (!sink.active || !sink.target)
|
||||
{
|
||||
wait = INFINITE;
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool pending[] =
|
||||
{
|
||||
state.positionRevision != sink.deliveredPosition,
|
||||
state.shapeRevision != sink.deliveredShape,
|
||||
state.transformRevision != sink.deliveredTransform,
|
||||
};
|
||||
|
||||
unsigned selected = static_cast<unsigned>(WorkType::COUNT);
|
||||
uint64_t earliest = 0;
|
||||
for (unsigned offset = 0;
|
||||
offset < static_cast<unsigned>(WorkType::COUNT); ++offset)
|
||||
{
|
||||
const unsigned index =
|
||||
(sink.nextWork + offset) % static_cast<unsigned>(WorkType::COUNT);
|
||||
if (!pending[index])
|
||||
continue;
|
||||
if (!sink.retryAt[index] || sink.retryAt[index] <= now)
|
||||
{
|
||||
selected = index;
|
||||
break;
|
||||
}
|
||||
if (!earliest || sink.retryAt[index] < earliest)
|
||||
earliest = sink.retryAt[index];
|
||||
}
|
||||
|
||||
if (selected == static_cast<unsigned>(WorkType::COUNT))
|
||||
{
|
||||
wait = earliest ? static_cast<DWORD>(std::min<uint64_t>(
|
||||
earliest - now, MAXDWORD - 1)) : INFINITE;
|
||||
return false;
|
||||
}
|
||||
|
||||
sink.nextWork =
|
||||
(selected + 1) % static_cast<unsigned>(WorkType::COUNT);
|
||||
sink.calling = true;
|
||||
ResetEvent(sink.idle);
|
||||
|
||||
work.type = static_cast<WorkType>(selected);
|
||||
work.target = sink.target;
|
||||
work.sdrWhiteLevel = state.sdrWhiteLevel;
|
||||
work.positionRevision = state.positionRevision;
|
||||
work.shapeRevision = state.shapeRevision;
|
||||
work.transformRevision = state.transformRevision;
|
||||
work.bindingSerial = sink.bindingSerial;
|
||||
work.replaySerial = sink.replaySerial;
|
||||
|
||||
if (work.type == WorkType::TRANSFORM)
|
||||
work.transform = std::move(state.transform);
|
||||
else
|
||||
{
|
||||
work.cursor.IsCursorVisible = state.cursor.IsCursorVisible;
|
||||
work.cursor.X = state.cursor.X;
|
||||
work.cursor.Y = state.cursor.Y;
|
||||
if (work.type == WorkType::SHAPE)
|
||||
{
|
||||
work.cursor.IsCursorShapeUpdated = true;
|
||||
work.cursor.CursorShapeInfo = state.cursor.CursorShapeInfo;
|
||||
work.cursorData = std::move(state.cursorData);
|
||||
}
|
||||
else
|
||||
work.cursor.CursorShapeInfo.CursorType =
|
||||
IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED;
|
||||
}
|
||||
wait = INFINITE;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CControlHub::CompleteWork(
|
||||
Sink& sink, const Work& work, ControlResult result)
|
||||
{
|
||||
CSRWExclusiveLock lock(sink.lock);
|
||||
if (sink.target != work.target ||
|
||||
sink.bindingSerial != work.bindingSerial)
|
||||
return false;
|
||||
if (!sink.active)
|
||||
{
|
||||
sink.calling = false;
|
||||
SetEvent(sink.idle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result == ControlResult::FAILED)
|
||||
{
|
||||
sink.active = false;
|
||||
sink.failed = true;
|
||||
sink.failurePending = true;
|
||||
sink.calling = false;
|
||||
SetEvent(sink.idle);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sink.replaySerial != work.replaySerial)
|
||||
{
|
||||
sink.calling = false;
|
||||
SetEvent(sink.idle);
|
||||
return true;
|
||||
}
|
||||
|
||||
const unsigned index = static_cast<unsigned>(work.type);
|
||||
if (result == ControlResult::RETRY)
|
||||
sink.retryAt[index] = GetTickCount64() + RETRY_MS;
|
||||
else
|
||||
{
|
||||
sink.retryAt[index] = 0;
|
||||
switch (work.type)
|
||||
{
|
||||
case WorkType::POSITION:
|
||||
sink.deliveredPosition = work.positionRevision;
|
||||
break;
|
||||
|
||||
case WorkType::SHAPE:
|
||||
sink.deliveredPosition = work.positionRevision;
|
||||
sink.deliveredShape = work.shapeRevision;
|
||||
break;
|
||||
|
||||
case WorkType::TRANSFORM:
|
||||
sink.deliveredTransform = work.transformRevision;
|
||||
break;
|
||||
|
||||
case WorkType::COUNT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sink.calling = false;
|
||||
SetEvent(sink.idle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CControlHub::Worker(Sink& sink)
|
||||
{
|
||||
DWORD wait = INFINITE;
|
||||
HANDLE events[] = { m_stopEvent, sink.wake };
|
||||
for (;;)
|
||||
{
|
||||
const DWORD status = WaitForMultipleObjects(2, events, FALSE, wait);
|
||||
if (status == WAIT_OBJECT_0 || status == WAIT_FAILED)
|
||||
return;
|
||||
|
||||
Work work;
|
||||
if (!BeginWork(sink, work, wait))
|
||||
continue;
|
||||
|
||||
ControlResult result;
|
||||
if (work.type == WorkType::TRANSFORM)
|
||||
result = work.target->SetColorTransform(std::move(work.transform));
|
||||
else
|
||||
result = work.target->SendCursor(work.cursor,
|
||||
!work.cursorData || work.cursorData->empty() ? nullptr :
|
||||
work.cursorData->data(),
|
||||
work.cursorData ? work.cursorData->size() : 0,
|
||||
work.sdrWhiteLevel);
|
||||
|
||||
const bool completed = CompleteWork(sink, work, result);
|
||||
if (result == ControlResult::FAILED && completed)
|
||||
DEBUG_WARN("Control update delivery failed");
|
||||
if (completed)
|
||||
{
|
||||
wait = result == ControlResult::RETRY ? RETRY_MS : 0;
|
||||
if (result != ControlResult::RETRY)
|
||||
SetEvent(sink.wake);
|
||||
}
|
||||
else
|
||||
wait = INFINITE;
|
||||
}
|
||||
}
|
||||
|
||||
void CControlHub::WakeAll(WorkType type)
|
||||
{
|
||||
const unsigned index = static_cast<unsigned>(type);
|
||||
CSRWSharedLock listLock(m_listLock);
|
||||
for (Sink& sink : m_sinks)
|
||||
{
|
||||
CSRWExclusiveLock lock(sink.lock);
|
||||
if (!sink.active)
|
||||
continue;
|
||||
sink.retryAt[index] = 0;
|
||||
SetEvent(sink.wake);
|
||||
}
|
||||
}
|
||||
|
||||
void CControlHub::SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info,
|
||||
const BYTE * data, UINT sdrWhiteLevel)
|
||||
{
|
||||
const bool shape = info.CursorShapeInfo.CursorType !=
|
||||
IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED;
|
||||
std::shared_ptr<std::vector<BYTE>> cursorData;
|
||||
if (shape)
|
||||
{
|
||||
if (info.CursorShapeInfo.Height &&
|
||||
info.CursorShapeInfo.Pitch >
|
||||
SIZE_MAX / info.CursorShapeInfo.Height)
|
||||
{
|
||||
DEBUG_ERROR("Pointer shape size overflow");
|
||||
return;
|
||||
}
|
||||
const size_t size = static_cast<size_t>(
|
||||
info.CursorShapeInfo.Height) * info.CursorShapeInfo.Pitch;
|
||||
if (size && !data)
|
||||
{
|
||||
DEBUG_ERROR("Missing pointer shape payload");
|
||||
return;
|
||||
}
|
||||
cursorData = std::make_shared<std::vector<BYTE>>(size);
|
||||
if (size)
|
||||
memcpy(cursorData->data(), data, size);
|
||||
}
|
||||
|
||||
{
|
||||
CSRWExclusiveLock lock(m_stateLock);
|
||||
m_cursor.IsCursorVisible = info.IsCursorVisible;
|
||||
m_cursor.X = info.X;
|
||||
m_cursor.Y = info.Y;
|
||||
m_cursorValid = true;
|
||||
m_sdrWhiteLevel = sdrWhiteLevel;
|
||||
m_state.cursor.IsCursorVisible = info.IsCursorVisible;
|
||||
m_state.cursor.X = info.X;
|
||||
m_state.cursor.Y = info.Y;
|
||||
m_state.sdrWhiteLevel = sdrWhiteLevel;
|
||||
m_state.positionRevision = NextRevision(m_state.positionRevision);
|
||||
|
||||
if (info.CursorShapeInfo.CursorType !=
|
||||
IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED)
|
||||
if (shape)
|
||||
{
|
||||
const size_t size =
|
||||
(size_t)info.CursorShapeInfo.Height * info.CursorShapeInfo.Pitch;
|
||||
m_cursor.IsCursorShapeUpdated = info.IsCursorShapeUpdated;
|
||||
m_cursor.CursorShapeInfo = info.CursorShapeInfo;
|
||||
m_cursorData.resize(size);
|
||||
if (size)
|
||||
memcpy(m_cursorData.data(), data, size);
|
||||
m_shapeValid = true;
|
||||
m_state.cursor.IsCursorShapeUpdated = info.IsCursorShapeUpdated;
|
||||
m_state.cursor.CursorShapeInfo = info.CursorShapeInfo;
|
||||
m_state.cursorData = std::move(cursorData);
|
||||
m_state.shapeRevision = NextRevision(m_state.shapeRevision);
|
||||
}
|
||||
else if (!m_shapeValid)
|
||||
m_cursor.CursorShapeInfo.CursorType =
|
||||
IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED;
|
||||
}
|
||||
|
||||
const auto sinks = Snapshot();
|
||||
for (const auto& sink : sinks)
|
||||
{
|
||||
CSRWSharedLock lock(sink->lock);
|
||||
if (sink->control)
|
||||
sink->control->SendCursor(info, data, sdrWhiteLevel);
|
||||
}
|
||||
WakeAll(WorkType::POSITION);
|
||||
if (shape)
|
||||
WakeAll(WorkType::SHAPE);
|
||||
}
|
||||
|
||||
void CControlHub::SetColorTransform(
|
||||
@@ -141,21 +550,15 @@ void CControlHub::SetColorTransform(
|
||||
{
|
||||
{
|
||||
CSRWExclusiveLock lock(m_stateLock);
|
||||
m_colorTransform = transform;
|
||||
}
|
||||
|
||||
const auto sinks = Snapshot();
|
||||
for (const auto& sink : sinks)
|
||||
{
|
||||
CSRWSharedLock lock(sink->lock);
|
||||
if (sink->control)
|
||||
sink->control->SetColorTransform(transform);
|
||||
m_state.transform = std::move(transform);
|
||||
m_state.transformRevision = NextRevision(m_state.transformRevision);
|
||||
}
|
||||
WakeAll(WorkType::TRANSFORM);
|
||||
}
|
||||
|
||||
std::shared_ptr<const D12ColorTransform>
|
||||
CControlHub::GetColorTransform() const
|
||||
{
|
||||
CSRWSharedLock lock(m_stateLock);
|
||||
return m_colorTransform;
|
||||
return m_state.transform;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user