telemetry and timing updates
This commit is contained in:
@@ -54,6 +54,12 @@ struct VideoIOState
|
|||||||
uint64_t actualDeckLinkBufferedFrames = 0;
|
uint64_t actualDeckLinkBufferedFrames = 0;
|
||||||
double deckLinkScheduleCallMilliseconds = 0.0;
|
double deckLinkScheduleCallMilliseconds = 0.0;
|
||||||
uint64_t deckLinkScheduleFailureCount = 0;
|
uint64_t deckLinkScheduleFailureCount = 0;
|
||||||
|
bool deckLinkScheduleLeadAvailable = false;
|
||||||
|
int64_t deckLinkPlaybackStreamTime = 0;
|
||||||
|
uint64_t deckLinkPlaybackFrameIndex = 0;
|
||||||
|
uint64_t deckLinkNextScheduleFrameIndex = 0;
|
||||||
|
int64_t deckLinkScheduleLeadFrames = 0;
|
||||||
|
uint64_t deckLinkScheduleRealignmentCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VideoIOFrame
|
struct VideoIOFrame
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public:
|
|||||||
double FrameBudgetMilliseconds() const;
|
double FrameBudgetMilliseconds() const;
|
||||||
uint64_t ScheduledFrameIndex() const { return mScheduledFrameIndex; }
|
uint64_t ScheduledFrameIndex() const { return mScheduledFrameIndex; }
|
||||||
uint64_t CompletedFrameIndex() const { return mCompletedFrameIndex; }
|
uint64_t CompletedFrameIndex() const { return mCompletedFrameIndex; }
|
||||||
|
int64_t FrameDuration() const { return mFrameDuration; }
|
||||||
uint64_t LateStreak() const { return mLateStreak; }
|
uint64_t LateStreak() const { return mLateStreak; }
|
||||||
uint64_t DropStreak() const { return mDropStreak; }
|
uint64_t DropStreak() const { return mDropStreak; }
|
||||||
int64_t TimeScale() const { return mTimeScale; }
|
int64_t TimeScale() const { return mTimeScale; }
|
||||||
|
|||||||
@@ -532,17 +532,13 @@ bool DeckLinkSession::ScheduleFrame(IDeckLinkMutableVideoFrame* outputVideoFrame
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
BMDTimeValue streamTime = 0;
|
if (mScheduleRealignmentPending)
|
||||||
double playbackSpeed = 0.0;
|
|
||||||
if (output->GetScheduledStreamTime(mScheduler.TimeScale(), &streamTime, &playbackSpeed) == S_OK && playbackSpeed > 0.0)
|
|
||||||
{
|
{
|
||||||
RefreshBufferedVideoFrameCount();
|
RealignScheduleCursorToPlayback();
|
||||||
const uint64_t leadFrames = mState.actualDeckLinkBufferedFramesAvailable
|
mScheduleRealignmentPending = false;
|
||||||
? static_cast<uint64_t>(mState.actualDeckLinkBufferedFrames) + 1
|
|
||||||
: static_cast<uint64_t>(mPlayoutPolicy.targetPrerollFrames);
|
|
||||||
mScheduler.AlignNextScheduleTimeToPlayback(streamTime, leadFrames);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateScheduleLeadTelemetry();
|
||||||
const VideoIOScheduleTime scheduleTime = mScheduler.NextScheduleTime();
|
const VideoIOScheduleTime scheduleTime = mScheduler.NextScheduleTime();
|
||||||
const auto scheduleStart = std::chrono::steady_clock::now();
|
const auto scheduleStart = std::chrono::steady_clock::now();
|
||||||
const HRESULT result = output->ScheduleVideoFrame(outputVideoFrame, scheduleTime.streamTime, scheduleTime.duration, scheduleTime.timeScale);
|
const HRESULT result = output->ScheduleVideoFrame(outputVideoFrame, scheduleTime.streamTime, scheduleTime.duration, scheduleTime.timeScale);
|
||||||
@@ -554,6 +550,49 @@ bool DeckLinkSession::ScheduleFrame(IDeckLinkMutableVideoFrame* outputVideoFrame
|
|||||||
return result == S_OK;
|
return result == S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeckLinkSession::UpdateScheduleLeadTelemetry()
|
||||||
|
{
|
||||||
|
if (output == nullptr)
|
||||||
|
{
|
||||||
|
mState.deckLinkScheduleLeadAvailable = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BMDTimeValue streamTime = 0;
|
||||||
|
double playbackSpeed = 0.0;
|
||||||
|
if (output->GetScheduledStreamTime(mScheduler.TimeScale(), &streamTime, &playbackSpeed) != S_OK || playbackSpeed <= 0.0)
|
||||||
|
{
|
||||||
|
mState.deckLinkScheduleLeadAvailable = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t playbackFrameIndex = streamTime >= 0 && mScheduler.FrameDuration() > 0
|
||||||
|
? static_cast<uint64_t>(streamTime / mScheduler.FrameDuration())
|
||||||
|
: 0;
|
||||||
|
const uint64_t nextScheduleFrameIndex = mScheduler.ScheduledFrameIndex();
|
||||||
|
mState.deckLinkScheduleLeadAvailable = true;
|
||||||
|
mState.deckLinkPlaybackStreamTime = streamTime;
|
||||||
|
mState.deckLinkPlaybackFrameIndex = playbackFrameIndex;
|
||||||
|
mState.deckLinkNextScheduleFrameIndex = nextScheduleFrameIndex;
|
||||||
|
mState.deckLinkScheduleLeadFrames = static_cast<int64_t>(nextScheduleFrameIndex) - static_cast<int64_t>(playbackFrameIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckLinkSession::RealignScheduleCursorToPlayback()
|
||||||
|
{
|
||||||
|
if (output == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BMDTimeValue streamTime = 0;
|
||||||
|
double playbackSpeed = 0.0;
|
||||||
|
if (output->GetScheduledStreamTime(mScheduler.TimeScale(), &streamTime, &playbackSpeed) != S_OK || playbackSpeed <= 0.0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy);
|
||||||
|
mScheduler.AlignNextScheduleTimeToPlayback(streamTime, policy.targetPrerollFrames);
|
||||||
|
++mState.deckLinkScheduleRealignmentCount;
|
||||||
|
UpdateScheduleLeadTelemetry();
|
||||||
|
}
|
||||||
|
|
||||||
bool DeckLinkSession::ScheduleSystemMemoryFrame(const VideoIOOutputFrame& frame)
|
bool DeckLinkSession::ScheduleSystemMemoryFrame(const VideoIOOutputFrame& frame)
|
||||||
{
|
{
|
||||||
if (output == nullptr || frame.bytes == nullptr || frame.rowBytes <= 0 || frame.height == 0)
|
if (output == nullptr || frame.bytes == nullptr || frame.rowBytes <= 0 || frame.height == 0)
|
||||||
@@ -838,6 +877,18 @@ void DeckLinkSession::HandlePlayoutFrameCompleted(IDeckLinkVideoFrame* completed
|
|||||||
|
|
||||||
VideoIOCompletion completion;
|
VideoIOCompletion completion;
|
||||||
completion.result = TranslateCompletionResult(completionResult);
|
completion.result = TranslateCompletionResult(completionResult);
|
||||||
|
if (completion.result == VideoIOCompletionResult::DisplayedLate || completion.result == VideoIOCompletionResult::Dropped)
|
||||||
|
{
|
||||||
|
if (mScheduleRealignmentArmed)
|
||||||
|
{
|
||||||
|
mScheduleRealignmentPending = true;
|
||||||
|
mScheduleRealignmentArmed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (completion.result == VideoIOCompletionResult::Completed)
|
||||||
|
{
|
||||||
|
mScheduleRealignmentArmed = true;
|
||||||
|
}
|
||||||
completion.outputFrameBuffer = completedSystemBuffer;
|
completion.outputFrameBuffer = completedSystemBuffer;
|
||||||
mOutputFrameCallback(completion);
|
mOutputFrameCallback(completion);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ private:
|
|||||||
bool AcquireNextOutputVideoFrame(CComPtr<IDeckLinkMutableVideoFrame>& outputVideoFrame);
|
bool AcquireNextOutputVideoFrame(CComPtr<IDeckLinkMutableVideoFrame>& outputVideoFrame);
|
||||||
bool PopulateOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame, VideoIOOutputFrame& frame);
|
bool PopulateOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame, VideoIOOutputFrame& frame);
|
||||||
bool ScheduleFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
|
bool ScheduleFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
|
||||||
|
void UpdateScheduleLeadTelemetry();
|
||||||
|
void RealignScheduleCursorToPlayback();
|
||||||
bool ScheduleSystemMemoryFrame(const VideoIOOutputFrame& frame);
|
bool ScheduleSystemMemoryFrame(const VideoIOOutputFrame& frame);
|
||||||
bool ScheduleBlackFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
|
bool ScheduleBlackFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
|
||||||
void RefreshBufferedVideoFrameCount();
|
void RefreshBufferedVideoFrameCount();
|
||||||
@@ -91,6 +93,8 @@ private:
|
|||||||
VideoIOState mState;
|
VideoIOState mState;
|
||||||
VideoPlayoutPolicy mPlayoutPolicy;
|
VideoPlayoutPolicy mPlayoutPolicy;
|
||||||
VideoPlayoutScheduler mScheduler;
|
VideoPlayoutScheduler mScheduler;
|
||||||
|
bool mScheduleRealignmentPending = false;
|
||||||
|
bool mScheduleRealignmentArmed = true;
|
||||||
InputFrameCallback mInputFrameCallback;
|
InputFrameCallback mInputFrameCallback;
|
||||||
OutputFrameCallback mOutputFrameCallback;
|
OutputFrameCallback mOutputFrameCallback;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ DeckLinkOutputThread
|
|||||||
never renders
|
never renders
|
||||||
```
|
```
|
||||||
|
|
||||||
Startup builds one settled output reserve before DeckLink scheduled playback starts: the completed-frame reserve must reach the configured depth and remain ready for the configured settle window. When DeckLink input is available, startup also waits briefly for three ready input frames before the render thread starts so the first render ticks are deliberate rather than lucky.
|
Startup builds a small output preroll reserve before DeckLink scheduled playback starts. When DeckLink input is available, startup also waits briefly for three ready input frames before the render thread starts so the first render ticks are deliberate rather than lucky.
|
||||||
|
|
||||||
## Current Scope
|
## Current Scope
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ Included now:
|
|||||||
- render-thread-owned input texture upload
|
- render-thread-owned input texture upload
|
||||||
- async PBO readback
|
- async PBO readback
|
||||||
- latest-N system-memory frame exchange
|
- latest-N system-memory frame exchange
|
||||||
- settled completed-frame output reserve before DeckLink preroll, with DeckLink scheduled depth still targeted at four
|
- bounded completed-frame output preroll reserve before DeckLink playback, with DeckLink scheduled depth still targeted at four
|
||||||
- background Slang compile of `shaders/happy-accident`
|
- background Slang compile of `shaders/happy-accident`
|
||||||
- app-owned display/render layer model for shader build readiness
|
- app-owned display/render layer model for shader build readiness
|
||||||
- app-owned submission of a completed shader artifact
|
- app-owned submission of a completed shader artifact
|
||||||
@@ -198,7 +198,6 @@ Currently consumed fields:
|
|||||||
- `autoReload`
|
- `autoReload`
|
||||||
- `maxTemporalHistoryFrames`
|
- `maxTemporalHistoryFrames`
|
||||||
- `previewFps`
|
- `previewFps`
|
||||||
- `startupSettleMs`
|
|
||||||
- `enableExternalKeying`
|
- `enableExternalKeying`
|
||||||
|
|
||||||
The loaded config is treated as a read-only startup snapshot. Subsystems that need config should receive this snapshot or a narrowed config struct from app orchestration; they should not reload files independently.
|
The loaded config is treated as a read-only startup snapshot. Subsystems that need config should receive this snapshot or a narrowed config struct from app orchestration; they should not reload files independently.
|
||||||
@@ -238,7 +237,7 @@ DeckLink output is an optional edge service in this app.
|
|||||||
Startup order is:
|
Startup order is:
|
||||||
|
|
||||||
1. start render thread
|
1. start render thread
|
||||||
2. build a settled completed-frame output reserve at normal render cadence
|
2. build a bounded completed-frame output preroll reserve at normal render cadence
|
||||||
3. try to attach DeckLink output
|
3. try to attach DeckLink output
|
||||||
4. start telemetry and HTTP either way
|
4. start telemetry and HTTP either way
|
||||||
|
|
||||||
@@ -286,7 +285,7 @@ Input telemetry:
|
|||||||
- `renderFrameMaxMs`: maximum observed render-thread draw duration for this process
|
- `renderFrameMaxMs`: maximum observed render-thread draw duration for this process
|
||||||
- `readbackQueueMs`: time spent queueing the most recent async BGRA8 PBO readback
|
- `readbackQueueMs`: time spent queueing the most recent async BGRA8 PBO readback
|
||||||
- `completedReadbackCopyMs`: time spent mapping/copying the most recent completed readback into system-memory frame storage
|
- `completedReadbackCopyMs`: time spent mapping/copying the most recent completed readback into system-memory frame storage
|
||||||
- `completedDrops`: completed unscheduled system-memory frames dropped by latest-N acquire paths; expected to stay flat in the cadence compositor output path
|
- `completedDrops`: completed unscheduled system-memory frames dropped; expected to stay flat in the cadence compositor output path
|
||||||
- `acquireMisses`: times render/readback could not acquire a writable system-memory frame slot; completed frames waiting for playout are preserved instead of being displaced
|
- `acquireMisses`: times render/readback could not acquire a writable system-memory frame slot; completed frames waiting for playout are preserved instead of being displaced
|
||||||
- `inputConsumeMisses`: render ticks where no ready input frame was available to upload
|
- `inputConsumeMisses`: render ticks where no ready input frame was available to upload
|
||||||
- `inputUploadMisses`: input texture upload attempts that reused the previous GL input texture
|
- `inputUploadMisses`: input texture upload attempts that reused the previous GL input texture
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
constexpr std::size_t kDeckLinkTargetBufferedFrames = 4;
|
||||||
|
constexpr std::size_t kReadbackDepth = 6;
|
||||||
|
constexpr std::size_t kWritableOutputReserveFrames = kReadbackDepth + 2;
|
||||||
|
|
||||||
class ComInitGuard
|
class ComInitGuard
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -95,7 +99,11 @@ int main(int argc, char** argv)
|
|||||||
frameExchangeConfig.height);
|
frameExchangeConfig.height);
|
||||||
frameExchangeConfig.pixelFormat = VideoIOPixelFormat::Bgra8;
|
frameExchangeConfig.pixelFormat = VideoIOPixelFormat::Bgra8;
|
||||||
frameExchangeConfig.rowBytes = VideoIORowBytes(frameExchangeConfig.pixelFormat, frameExchangeConfig.width);
|
frameExchangeConfig.rowBytes = VideoIORowBytes(frameExchangeConfig.pixelFormat, frameExchangeConfig.width);
|
||||||
frameExchangeConfig.capacity = 12;
|
frameExchangeConfig.capacity =
|
||||||
|
appConfig.warmupCompletedFrames +
|
||||||
|
kDeckLinkTargetBufferedFrames +
|
||||||
|
kWritableOutputReserveFrames;
|
||||||
|
frameExchangeConfig.maxCompletedFrames = appConfig.warmupCompletedFrames;
|
||||||
|
|
||||||
SystemFrameExchange frameExchange(frameExchangeConfig);
|
SystemFrameExchange frameExchange(frameExchangeConfig);
|
||||||
|
|
||||||
@@ -128,6 +136,10 @@ int main(int argc, char** argv)
|
|||||||
"Unsupported DeckLink outputVideoFormat/outputFrameRate in config/runtime-host.json; render cadence will use parsed frame-rate fallback: " +
|
"Unsupported DeckLink outputVideoFormat/outputFrameRate in config/runtime-host.json; render cadence will use parsed frame-rate fallback: " +
|
||||||
appConfig.outputVideoFormat + " / " + appConfig.outputFrameRate);
|
appConfig.outputVideoFormat + " / " + appConfig.outputFrameRate);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appConfig.deckLink.outputVideoMode = outputVideoMode;
|
||||||
|
}
|
||||||
|
|
||||||
RenderCadenceCompositor::DeckLinkInput deckLinkInput(inputMailbox);
|
RenderCadenceCompositor::DeckLinkInput deckLinkInput(inputMailbox);
|
||||||
RenderCadenceCompositor::DeckLinkInputThread deckLinkInputThread(deckLinkInput);
|
RenderCadenceCompositor::DeckLinkInputThread deckLinkInputThread(deckLinkInput);
|
||||||
@@ -186,7 +198,7 @@ int main(int argc, char** argv)
|
|||||||
renderConfig.frameDurationMilliseconds = outputVideoModeResolved
|
renderConfig.frameDurationMilliseconds = outputVideoModeResolved
|
||||||
? RenderCadenceCompositor::FrameDurationMillisecondsFromDisplayMode(outputVideoMode.displayMode, fallbackFrameDurationMilliseconds)
|
? RenderCadenceCompositor::FrameDurationMillisecondsFromDisplayMode(outputVideoMode.displayMode, fallbackFrameDurationMilliseconds)
|
||||||
: fallbackFrameDurationMilliseconds;
|
: fallbackFrameDurationMilliseconds;
|
||||||
renderConfig.pboDepth = 6;
|
renderConfig.pboDepth = kReadbackDepth;
|
||||||
|
|
||||||
RenderThread renderThread(frameExchange, &inputMailbox, renderConfig);
|
RenderThread renderThread(frameExchange, &inputMailbox, renderConfig);
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,8 @@ AppConfig DefaultAppConfig()
|
|||||||
config.autoReload = true;
|
config.autoReload = true;
|
||||||
config.maxTemporalHistoryFrames = 12;
|
config.maxTemporalHistoryFrames = 12;
|
||||||
config.previewFps = 30.0;
|
config.previewFps = 30.0;
|
||||||
config.warmupCompletedFrames = 8;
|
config.warmupCompletedFrames = 4;
|
||||||
config.warmupTimeout = std::chrono::seconds(3);
|
config.warmupTimeout = std::chrono::seconds(3);
|
||||||
config.startupSettle = std::chrono::seconds(5);
|
|
||||||
config.prerollTimeout = std::chrono::seconds(3);
|
config.prerollTimeout = std::chrono::seconds(3);
|
||||||
config.prerollPoll = std::chrono::milliseconds(2);
|
config.prerollPoll = std::chrono::milliseconds(2);
|
||||||
config.runtimeShaderId = "happy-accident";
|
config.runtimeShaderId = "happy-accident";
|
||||||
|
|||||||
@@ -30,9 +30,8 @@ struct AppConfig
|
|||||||
bool autoReload = true;
|
bool autoReload = true;
|
||||||
std::size_t maxTemporalHistoryFrames = 12;
|
std::size_t maxTemporalHistoryFrames = 12;
|
||||||
double previewFps = 30.0;
|
double previewFps = 30.0;
|
||||||
std::size_t warmupCompletedFrames = 8;
|
std::size_t warmupCompletedFrames = 4;
|
||||||
std::chrono::milliseconds warmupTimeout = std::chrono::seconds(3);
|
std::chrono::milliseconds warmupTimeout = std::chrono::seconds(3);
|
||||||
std::chrono::milliseconds startupSettle = std::chrono::seconds(5);
|
|
||||||
std::chrono::milliseconds prerollTimeout = std::chrono::seconds(3);
|
std::chrono::milliseconds prerollTimeout = std::chrono::seconds(3);
|
||||||
std::chrono::milliseconds prerollPoll = std::chrono::milliseconds(2);
|
std::chrono::milliseconds prerollPoll = std::chrono::milliseconds(2);
|
||||||
std::string runtimeShaderId = "happy-accident";
|
std::string runtimeShaderId = "happy-accident";
|
||||||
|
|||||||
@@ -134,9 +134,6 @@ bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& err
|
|||||||
ApplySize(root, "maxTemporalHistoryFrames", mConfig.maxTemporalHistoryFrames);
|
ApplySize(root, "maxTemporalHistoryFrames", mConfig.maxTemporalHistoryFrames);
|
||||||
ApplyDouble(root, "previewFps", mConfig.previewFps);
|
ApplyDouble(root, "previewFps", mConfig.previewFps);
|
||||||
ApplyBool(root, "enableExternalKeying", mConfig.deckLink.externalKeyingEnabled);
|
ApplyBool(root, "enableExternalKeying", mConfig.deckLink.externalKeyingEnabled);
|
||||||
std::size_t startupSettleMilliseconds = static_cast<std::size_t>(mConfig.startupSettle.count());
|
|
||||||
ApplySize(root, "startupSettleMs", startupSettleMilliseconds);
|
|
||||||
mConfig.startupSettle = std::chrono::milliseconds(startupSettleMilliseconds);
|
|
||||||
|
|
||||||
mLoadedFromFile = true;
|
mLoadedFromFile = true;
|
||||||
error.clear();
|
error.clear();
|
||||||
|
|||||||
@@ -163,23 +163,24 @@ private:
|
|||||||
mVideoOutputEnabled = true;
|
mVideoOutputEnabled = true;
|
||||||
mVideoOutputStatus = "DeckLink scheduled output running.";
|
mVideoOutputStatus = "DeckLink scheduled output running.";
|
||||||
Log("app", mVideoOutputStatus);
|
Log("app", mVideoOutputStatus);
|
||||||
|
Log(
|
||||||
|
"app",
|
||||||
|
"DeckLink output mode: " + mOutput.State().outputDisplayModeName +
|
||||||
|
", frame budget " + std::to_string(mOutput.State().frameBudgetMilliseconds) + " ms.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BuildSettledOutputReserve(std::string& error)
|
bool BuildSettledOutputReserve(std::string& error)
|
||||||
{
|
{
|
||||||
const auto reserveTimeout = mConfig.warmupTimeout + mConfig.startupSettle + mConfig.warmupTimeout;
|
const auto reserveTimeout = mConfig.warmupTimeout;
|
||||||
Log("app",
|
Log("app",
|
||||||
"Building settled output reserve: waiting for " + std::to_string(mConfig.warmupCompletedFrames) +
|
"Building output preroll reserve: waiting for " + std::to_string(mConfig.warmupCompletedFrames) +
|
||||||
" completed frame(s) to remain ready for " + std::to_string(mConfig.startupSettle.count()) + " ms.");
|
" completed frame(s).");
|
||||||
if (mFrameExchange.WaitForStableCompletedDepth(
|
if (mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, reserveTimeout))
|
||||||
mConfig.warmupCompletedFrames,
|
|
||||||
mConfig.startupSettle,
|
|
||||||
reserveTimeout))
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = "Timed out waiting for settled output reserve.";
|
error = "Timed out waiting for output preroll reserve.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -251,7 +251,6 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
|||||||
writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(input.config.maxTemporalHistoryFrames));
|
writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(input.config.maxTemporalHistoryFrames));
|
||||||
writer.KeyDouble("previewFps", input.config.previewFps);
|
writer.KeyDouble("previewFps", input.config.previewFps);
|
||||||
writer.KeyBool("enableExternalKeying", input.config.deckLink.externalKeyingEnabled);
|
writer.KeyBool("enableExternalKeying", input.config.deckLink.externalKeyingEnabled);
|
||||||
writer.KeyUInt("startupSettleMs", static_cast<uint64_t>(input.config.startupSettle.count()));
|
|
||||||
writer.KeyString("inputVideoFormat", input.config.inputVideoFormat);
|
writer.KeyString("inputVideoFormat", input.config.inputVideoFormat);
|
||||||
writer.KeyString("inputFrameRate", input.config.inputFrameRate);
|
writer.KeyString("inputFrameRate", input.config.inputFrameRate);
|
||||||
writer.KeyString("outputVideoFormat", input.config.outputVideoFormat);
|
writer.KeyString("outputVideoFormat", input.config.outputVideoFormat);
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ bool SystemFrameExchange::PublishCompleted(const SystemFrame& frame)
|
|||||||
slot.state = SystemFrameSlotState::Completed;
|
slot.state = SystemFrameSlotState::Completed;
|
||||||
slot.frameIndex = frame.frameIndex;
|
slot.frameIndex = frame.frameIndex;
|
||||||
mCompletedIndices.push_back(frame.index);
|
mCompletedIndices.push_back(frame.index);
|
||||||
|
TrimCompletedLocked();
|
||||||
++mCounters.completedFrames;
|
++mCounters.completedFrames;
|
||||||
mCondition.notify_all();
|
mCondition.notify_all();
|
||||||
return true;
|
return true;
|
||||||
@@ -231,6 +232,38 @@ bool SystemFrameExchange::AcquireFreeLocked(SystemFrame& frame)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SystemFrameExchange::DropOldestCompletedLocked()
|
||||||
|
{
|
||||||
|
while (!mCompletedIndices.empty())
|
||||||
|
{
|
||||||
|
const std::size_t index = mCompletedIndices.front();
|
||||||
|
mCompletedIndices.pop_front();
|
||||||
|
if (index >= mSlots.size() || mSlots[index].state != SystemFrameSlotState::Completed)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Slot& slot = mSlots[index];
|
||||||
|
slot.state = SystemFrameSlotState::Free;
|
||||||
|
slot.frameIndex = 0;
|
||||||
|
++slot.generation;
|
||||||
|
++mCounters.completedDrops;
|
||||||
|
mCondition.notify_all();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemFrameExchange::TrimCompletedLocked()
|
||||||
|
{
|
||||||
|
if (mConfig.maxCompletedFrames == 0)
|
||||||
|
return;
|
||||||
|
while (CompletedCountLocked() > mConfig.maxCompletedFrames)
|
||||||
|
{
|
||||||
|
if (!DropOldestCompletedLocked())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool SystemFrameExchange::IsValidLocked(const SystemFrame& frame) const
|
bool SystemFrameExchange::IsValidLocked(const SystemFrame& frame) const
|
||||||
{
|
{
|
||||||
return frame.index < mSlots.size() && mSlots[frame.index].generation == frame.generation;
|
return frame.index < mSlots.size() && mSlots[frame.index].generation == frame.generation;
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool AcquireFreeLocked(SystemFrame& frame);
|
bool AcquireFreeLocked(SystemFrame& frame);
|
||||||
|
bool DropOldestCompletedLocked();
|
||||||
|
void TrimCompletedLocked();
|
||||||
bool IsValidLocked(const SystemFrame& frame) const;
|
bool IsValidLocked(const SystemFrame& frame) const;
|
||||||
void FillFrameLocked(std::size_t index, SystemFrame& frame);
|
void FillFrameLocked(std::size_t index, SystemFrame& frame);
|
||||||
std::size_t CompletedCountLocked() const;
|
std::size_t CompletedCountLocked() const;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ struct SystemFrameExchangeConfig
|
|||||||
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
|
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
|
||||||
unsigned rowBytes = 0;
|
unsigned rowBytes = 0;
|
||||||
std::size_t capacity = 0;
|
std::size_t capacity = 0;
|
||||||
|
std::size_t maxCompletedFrames = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SystemFrame
|
struct SystemFrame
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ struct CadenceTelemetrySnapshot
|
|||||||
bool deckLinkBufferedAvailable = false;
|
bool deckLinkBufferedAvailable = false;
|
||||||
uint64_t deckLinkBuffered = 0;
|
uint64_t deckLinkBuffered = 0;
|
||||||
double deckLinkScheduleCallMilliseconds = 0.0;
|
double deckLinkScheduleCallMilliseconds = 0.0;
|
||||||
|
bool deckLinkScheduleLeadAvailable = false;
|
||||||
|
int64_t deckLinkPlaybackStreamTime = 0;
|
||||||
|
uint64_t deckLinkPlaybackFrameIndex = 0;
|
||||||
|
uint64_t deckLinkNextScheduleFrameIndex = 0;
|
||||||
|
int64_t deckLinkScheduleLeadFrames = 0;
|
||||||
|
uint64_t deckLinkScheduleRealignments = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CadenceTelemetry
|
class CadenceTelemetry
|
||||||
@@ -92,6 +98,12 @@ public:
|
|||||||
snapshot.deckLinkBufferedAvailable = outputMetrics.actualBufferedFramesAvailable;
|
snapshot.deckLinkBufferedAvailable = outputMetrics.actualBufferedFramesAvailable;
|
||||||
snapshot.deckLinkBuffered = outputMetrics.actualBufferedFrames;
|
snapshot.deckLinkBuffered = outputMetrics.actualBufferedFrames;
|
||||||
snapshot.deckLinkScheduleCallMilliseconds = outputMetrics.scheduleCallMilliseconds;
|
snapshot.deckLinkScheduleCallMilliseconds = outputMetrics.scheduleCallMilliseconds;
|
||||||
|
snapshot.deckLinkScheduleLeadAvailable = outputMetrics.scheduleLeadAvailable;
|
||||||
|
snapshot.deckLinkPlaybackStreamTime = outputMetrics.playbackStreamTime;
|
||||||
|
snapshot.deckLinkPlaybackFrameIndex = outputMetrics.playbackFrameIndex;
|
||||||
|
snapshot.deckLinkNextScheduleFrameIndex = outputMetrics.nextScheduleFrameIndex;
|
||||||
|
snapshot.deckLinkScheduleLeadFrames = outputMetrics.scheduleLeadFrames;
|
||||||
|
snapshot.deckLinkScheduleRealignments = outputMetrics.scheduleRealignmentCount;
|
||||||
|
|
||||||
if (mHasLastSample && seconds > 0.0)
|
if (mHasLastSample && seconds > 0.0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -61,6 +61,16 @@ inline void WriteCadenceTelemetryJson(JsonWriter& writer, const CadenceTelemetry
|
|||||||
else
|
else
|
||||||
writer.Null();
|
writer.Null();
|
||||||
writer.KeyDouble("scheduleCallMs", snapshot.deckLinkScheduleCallMilliseconds);
|
writer.KeyDouble("scheduleCallMs", snapshot.deckLinkScheduleCallMilliseconds);
|
||||||
|
writer.KeyBool("deckLinkScheduleLeadAvailable", snapshot.deckLinkScheduleLeadAvailable);
|
||||||
|
writer.Key("deckLinkScheduleLeadFrames");
|
||||||
|
if (snapshot.deckLinkScheduleLeadAvailable)
|
||||||
|
writer.Int(snapshot.deckLinkScheduleLeadFrames);
|
||||||
|
else
|
||||||
|
writer.Null();
|
||||||
|
writer.KeyUInt("deckLinkPlaybackFrameIndex", snapshot.deckLinkPlaybackFrameIndex);
|
||||||
|
writer.KeyUInt("deckLinkNextScheduleFrameIndex", snapshot.deckLinkNextScheduleFrameIndex);
|
||||||
|
writer.KeyInt("deckLinkPlaybackStreamTime", snapshot.deckLinkPlaybackStreamTime);
|
||||||
|
writer.KeyUInt("deckLinkScheduleRealignments", snapshot.deckLinkScheduleRealignments);
|
||||||
writer.EndObject();
|
writer.EndObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ bool DeckLinkOutput::Initialize(const DeckLinkOutputConfig& config, CompletionCa
|
|||||||
mCompletionCallback = completionCallback;
|
mCompletionCallback = completionCallback;
|
||||||
|
|
||||||
VideoFormatSelection formats;
|
VideoFormatSelection formats;
|
||||||
|
formats.output = config.outputVideoMode;
|
||||||
if (!mSession.DiscoverDevicesAndModes(formats, error))
|
if (!mSession.DiscoverDevicesAndModes(formats, error))
|
||||||
return false;
|
return false;
|
||||||
if (!mSession.SelectPreferredFormats(formats, config.outputAlphaRequired, error))
|
if (!mSession.SelectPreferredFormats(formats, config.outputAlphaRequired, error))
|
||||||
@@ -76,6 +77,12 @@ DeckLinkOutputMetrics DeckLinkOutput::Metrics() const
|
|||||||
metrics.actualBufferedFramesAvailable = state.actualDeckLinkBufferedFramesAvailable;
|
metrics.actualBufferedFramesAvailable = state.actualDeckLinkBufferedFramesAvailable;
|
||||||
metrics.actualBufferedFrames = state.actualDeckLinkBufferedFrames;
|
metrics.actualBufferedFrames = state.actualDeckLinkBufferedFrames;
|
||||||
metrics.scheduleCallMilliseconds = state.deckLinkScheduleCallMilliseconds;
|
metrics.scheduleCallMilliseconds = state.deckLinkScheduleCallMilliseconds;
|
||||||
|
metrics.scheduleLeadAvailable = state.deckLinkScheduleLeadAvailable;
|
||||||
|
metrics.playbackStreamTime = state.deckLinkPlaybackStreamTime;
|
||||||
|
metrics.playbackFrameIndex = state.deckLinkPlaybackFrameIndex;
|
||||||
|
metrics.nextScheduleFrameIndex = state.deckLinkNextScheduleFrameIndex;
|
||||||
|
metrics.scheduleLeadFrames = state.deckLinkScheduleLeadFrames;
|
||||||
|
metrics.scheduleRealignmentCount = state.deckLinkScheduleRealignmentCount;
|
||||||
return metrics;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "DeckLinkDisplayMode.h"
|
||||||
#include "DeckLinkSession.h"
|
#include "DeckLinkSession.h"
|
||||||
#include "VideoIOTypes.h"
|
#include "VideoIOTypes.h"
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ namespace RenderCadenceCompositor
|
|||||||
{
|
{
|
||||||
struct DeckLinkOutputConfig
|
struct DeckLinkOutputConfig
|
||||||
{
|
{
|
||||||
|
VideoFormat outputVideoMode;
|
||||||
bool externalKeyingEnabled = false;
|
bool externalKeyingEnabled = false;
|
||||||
bool outputAlphaRequired = false;
|
bool outputAlphaRequired = false;
|
||||||
};
|
};
|
||||||
@@ -26,6 +28,12 @@ struct DeckLinkOutputMetrics
|
|||||||
bool actualBufferedFramesAvailable = false;
|
bool actualBufferedFramesAvailable = false;
|
||||||
uint64_t actualBufferedFrames = 0;
|
uint64_t actualBufferedFrames = 0;
|
||||||
double scheduleCallMilliseconds = 0.0;
|
double scheduleCallMilliseconds = 0.0;
|
||||||
|
bool scheduleLeadAvailable = false;
|
||||||
|
int64_t playbackStreamTime = 0;
|
||||||
|
uint64_t playbackFrameIndex = 0;
|
||||||
|
uint64_t nextScheduleFrameIndex = 0;
|
||||||
|
int64_t scheduleLeadFrames = 0;
|
||||||
|
uint64_t scheduleRealignmentCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DeckLinkOutput
|
class DeckLinkOutput
|
||||||
|
|||||||
@@ -77,7 +77,11 @@ private:
|
|||||||
while (!mStopping)
|
while (!mStopping)
|
||||||
{
|
{
|
||||||
const auto exchangeMetrics = mExchange.Metrics();
|
const auto exchangeMetrics = mExchange.Metrics();
|
||||||
if (exchangeMetrics.scheduledCount >= mConfig.targetBufferedFrames)
|
const auto outputMetrics = mOutput.Metrics();
|
||||||
|
const std::size_t bufferedFrames = outputMetrics.actualBufferedFramesAvailable
|
||||||
|
? static_cast<std::size_t>(outputMetrics.actualBufferedFrames)
|
||||||
|
: exchangeMetrics.scheduledCount;
|
||||||
|
if (bufferedFrames >= mConfig.targetBufferedFrames)
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(mConfig.idleSleep);
|
std::this_thread::sleep_for(mConfig.idleSleep);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -11,6 +11,5 @@
|
|||||||
"autoReload": true,
|
"autoReload": true,
|
||||||
"maxTemporalHistoryFrames": 12,
|
"maxTemporalHistoryFrames": 12,
|
||||||
"previewFps": 30,
|
"previewFps": 30,
|
||||||
"startupSettleMs": 5000,
|
|
||||||
"enableExternalKeying": true
|
"enableExternalKeying": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -557,8 +557,6 @@ components:
|
|||||||
type: number
|
type: number
|
||||||
previewFps:
|
previewFps:
|
||||||
type: number
|
type: number
|
||||||
startupSettleMs:
|
|
||||||
type: number
|
|
||||||
enableExternalKeying:
|
enableExternalKeying:
|
||||||
type: boolean
|
type: boolean
|
||||||
inputVideoFormat:
|
inputVideoFormat:
|
||||||
@@ -721,6 +719,25 @@ components:
|
|||||||
type: number
|
type: number
|
||||||
inputCaptureFormat:
|
inputCaptureFormat:
|
||||||
type: string
|
type: string
|
||||||
|
deckLinkScheduleLeadAvailable:
|
||||||
|
type: boolean
|
||||||
|
description: Whether DeckLink playback stream-time lead telemetry is currently available.
|
||||||
|
deckLinkScheduleLeadFrames:
|
||||||
|
type: number
|
||||||
|
nullable: true
|
||||||
|
description: Estimated number of frame intervals between the next app schedule timestamp and the DeckLink playback frame index.
|
||||||
|
deckLinkPlaybackFrameIndex:
|
||||||
|
type: number
|
||||||
|
description: DeckLink playback stream time converted to frame index at the configured output cadence.
|
||||||
|
deckLinkNextScheduleFrameIndex:
|
||||||
|
type: number
|
||||||
|
description: Next frame index the app scheduler will assign to a DeckLink output frame.
|
||||||
|
deckLinkPlaybackStreamTime:
|
||||||
|
type: number
|
||||||
|
description: Raw DeckLink scheduled playback stream time in the output mode time scale.
|
||||||
|
deckLinkScheduleRealignments:
|
||||||
|
type: number
|
||||||
|
description: Count of schedule-cursor recovery realignments triggered by DeckLink late/drop pressure.
|
||||||
BackendPlayoutStatus:
|
BackendPlayoutStatus:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ std::filesystem::path WriteConfigFixture()
|
|||||||
<< " \"autoReload\": false,\n"
|
<< " \"autoReload\": false,\n"
|
||||||
<< " \"maxTemporalHistoryFrames\": 8,\n"
|
<< " \"maxTemporalHistoryFrames\": 8,\n"
|
||||||
<< " \"previewFps\": 24,\n"
|
<< " \"previewFps\": 24,\n"
|
||||||
<< " \"startupSettleMs\": 2500,\n"
|
|
||||||
<< " \"enableExternalKeying\": true\n"
|
<< " \"enableExternalKeying\": true\n"
|
||||||
<< "}\n";
|
<< "}\n";
|
||||||
return path;
|
return path;
|
||||||
@@ -68,7 +67,6 @@ void TestLoadsRuntimeHostConfig()
|
|||||||
Expect(!config.autoReload, "auto reload loads");
|
Expect(!config.autoReload, "auto reload loads");
|
||||||
Expect(config.maxTemporalHistoryFrames == 8, "history length loads");
|
Expect(config.maxTemporalHistoryFrames == 8, "history length loads");
|
||||||
Expect(config.previewFps == 24.0, "preview fps loads");
|
Expect(config.previewFps == 24.0, "preview fps loads");
|
||||||
Expect(config.startupSettle == std::chrono::milliseconds(2500), "startup settle loads");
|
|
||||||
Expect(config.deckLink.externalKeyingEnabled, "external keying loads");
|
Expect(config.deckLink.externalKeyingEnabled, "external keying loads");
|
||||||
|
|
||||||
std::filesystem::remove(path);
|
std::filesystem::remove(path);
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ SystemFrameExchangeConfig MakeConfig(std::size_t capacity = 2)
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SystemFrameExchangeConfig MakeBoundedCompletedConfig(std::size_t capacity = 4, std::size_t maxCompletedFrames = 2)
|
||||||
|
{
|
||||||
|
SystemFrameExchangeConfig config = MakeConfig(capacity);
|
||||||
|
config.maxCompletedFrames = maxCompletedFrames;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
void TestAcquirePublishesAndSchedules()
|
void TestAcquirePublishesAndSchedules()
|
||||||
{
|
{
|
||||||
SystemFrameExchange exchange(MakeConfig(1));
|
SystemFrameExchange exchange(MakeConfig(1));
|
||||||
@@ -82,6 +89,31 @@ void TestAcquirePreservesCompletedFrames()
|
|||||||
Expect(metrics.acquireMisses == 1, "preserving acquire miss is counted");
|
Expect(metrics.acquireMisses == 1, "preserving acquire miss is counted");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestCompletedReserveIsBoundedFifo()
|
||||||
|
{
|
||||||
|
SystemFrameExchange exchange(MakeBoundedCompletedConfig(4, 2));
|
||||||
|
|
||||||
|
for (uint64_t frameIndex = 1; frameIndex <= 3; ++frameIndex)
|
||||||
|
{
|
||||||
|
SystemFrame frame;
|
||||||
|
Expect(exchange.AcquireForRender(frame), "bounded reserve frame can be acquired");
|
||||||
|
frame.frameIndex = frameIndex;
|
||||||
|
Expect(exchange.PublishCompleted(frame), "bounded reserve frame can be completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemFrame firstScheduled;
|
||||||
|
Expect(exchange.ConsumeCompletedForSchedule(firstScheduled), "bounded reserve oldest retained frame can be scheduled");
|
||||||
|
Expect(firstScheduled.frameIndex == 2, "bounded reserve drops oldest overflow and keeps FIFO order");
|
||||||
|
|
||||||
|
SystemFrame secondScheduled;
|
||||||
|
Expect(exchange.ConsumeCompletedForSchedule(secondScheduled), "bounded reserve second retained frame can be scheduled");
|
||||||
|
Expect(secondScheduled.frameIndex == 3, "bounded reserve schedules next retained frame");
|
||||||
|
|
||||||
|
SystemFrameExchangeMetrics metrics = exchange.Metrics();
|
||||||
|
Expect(metrics.completedDrops == 1, "bounded completed reserve records oldest overflow drop");
|
||||||
|
Expect(metrics.scheduledFrames == 2, "bounded reserve schedules retained frames");
|
||||||
|
}
|
||||||
|
|
||||||
void TestScheduledFramesAreNotDropped()
|
void TestScheduledFramesAreNotDropped()
|
||||||
{
|
{
|
||||||
SystemFrameExchange exchange(MakeConfig(1));
|
SystemFrameExchange exchange(MakeConfig(1));
|
||||||
@@ -177,6 +209,7 @@ int main()
|
|||||||
{
|
{
|
||||||
TestAcquirePublishesAndSchedules();
|
TestAcquirePublishesAndSchedules();
|
||||||
TestAcquirePreservesCompletedFrames();
|
TestAcquirePreservesCompletedFrames();
|
||||||
|
TestCompletedReserveIsBoundedFifo();
|
||||||
TestScheduledFramesAreNotDropped();
|
TestScheduledFramesAreNotDropped();
|
||||||
TestGenerationValidationRejectsStaleFrames();
|
TestGenerationValidationRejectsStaleFrames();
|
||||||
TestPixelFormatAwareSizing();
|
TestPixelFormatAwareSizing();
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ struct FakeOutputMetrics
|
|||||||
bool actualBufferedFramesAvailable = false;
|
bool actualBufferedFramesAvailable = false;
|
||||||
uint64_t actualBufferedFrames = 0;
|
uint64_t actualBufferedFrames = 0;
|
||||||
double scheduleCallMilliseconds = 0.0;
|
double scheduleCallMilliseconds = 0.0;
|
||||||
|
bool scheduleLeadAvailable = false;
|
||||||
|
int64_t playbackStreamTime = 0;
|
||||||
|
uint64_t playbackFrameIndex = 0;
|
||||||
|
uint64_t nextScheduleFrameIndex = 0;
|
||||||
|
int64_t scheduleLeadFrames = 0;
|
||||||
|
uint64_t scheduleRealignmentCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FakeOutput
|
struct FakeOutput
|
||||||
@@ -109,6 +115,12 @@ void TestTelemetrySamplesCompletedPollMissesAndShaderCounts()
|
|||||||
FakeOutput output;
|
FakeOutput output;
|
||||||
output.metrics.actualBufferedFramesAvailable = true;
|
output.metrics.actualBufferedFramesAvailable = true;
|
||||||
output.metrics.actualBufferedFrames = 4;
|
output.metrics.actualBufferedFrames = 4;
|
||||||
|
output.metrics.scheduleLeadAvailable = true;
|
||||||
|
output.metrics.playbackStreamTime = 10010;
|
||||||
|
output.metrics.playbackFrameIndex = 10;
|
||||||
|
output.metrics.nextScheduleFrameIndex = 14;
|
||||||
|
output.metrics.scheduleLeadFrames = 4;
|
||||||
|
output.metrics.scheduleRealignmentCount = 1;
|
||||||
|
|
||||||
FakeOutputThread outputThread;
|
FakeOutputThread outputThread;
|
||||||
outputThread.metrics.completedPollMisses = 12;
|
outputThread.metrics.completedPollMisses = 12;
|
||||||
@@ -163,6 +175,12 @@ void TestTelemetrySamplesCompletedPollMissesAndShaderCounts()
|
|||||||
Expect(snapshot.inputSignalPresent, "input signal present is sampled");
|
Expect(snapshot.inputSignalPresent, "input signal present is sampled");
|
||||||
Expect(snapshot.deckLinkBufferedAvailable, "buffer telemetry availability is sampled");
|
Expect(snapshot.deckLinkBufferedAvailable, "buffer telemetry availability is sampled");
|
||||||
Expect(snapshot.deckLinkBuffered == 4, "buffer depth is sampled");
|
Expect(snapshot.deckLinkBuffered == 4, "buffer depth is sampled");
|
||||||
|
Expect(snapshot.deckLinkScheduleLeadAvailable, "schedule lead availability is sampled");
|
||||||
|
Expect(snapshot.deckLinkPlaybackStreamTime == 10010, "playback stream time is sampled");
|
||||||
|
Expect(snapshot.deckLinkPlaybackFrameIndex == 10, "playback frame index is sampled");
|
||||||
|
Expect(snapshot.deckLinkNextScheduleFrameIndex == 14, "next schedule frame index is sampled");
|
||||||
|
Expect(snapshot.deckLinkScheduleLeadFrames == 4, "schedule lead frames are sampled");
|
||||||
|
Expect(snapshot.deckLinkScheduleRealignments == 1, "schedule realignment count is sampled");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestTelemetryComputesRatesFromDeltas()
|
void TestTelemetryComputesRatesFromDeltas()
|
||||||
@@ -234,6 +252,12 @@ void TestTelemetrySerializesToJson()
|
|||||||
snapshot.deckLinkBufferedAvailable = true;
|
snapshot.deckLinkBufferedAvailable = true;
|
||||||
snapshot.deckLinkBuffered = 4;
|
snapshot.deckLinkBuffered = 4;
|
||||||
snapshot.deckLinkScheduleCallMilliseconds = 1.25;
|
snapshot.deckLinkScheduleCallMilliseconds = 1.25;
|
||||||
|
snapshot.deckLinkScheduleLeadAvailable = true;
|
||||||
|
snapshot.deckLinkScheduleLeadFrames = 4;
|
||||||
|
snapshot.deckLinkPlaybackFrameIndex = 10;
|
||||||
|
snapshot.deckLinkNextScheduleFrameIndex = 14;
|
||||||
|
snapshot.deckLinkPlaybackStreamTime = 10010;
|
||||||
|
snapshot.deckLinkScheduleRealignments = 1;
|
||||||
|
|
||||||
const std::string json = RenderCadenceCompositor::CadenceTelemetryToJson(snapshot);
|
const std::string json = RenderCadenceCompositor::CadenceTelemetryToJson(snapshot);
|
||||||
const std::string expected =
|
const std::string expected =
|
||||||
@@ -259,7 +283,13 @@ void TestTelemetrySerializesToJson()
|
|||||||
"\"inputUnsupportedFrames\":3,\"inputSubmitMisses\":4,"
|
"\"inputUnsupportedFrames\":3,\"inputSubmitMisses\":4,"
|
||||||
"\"inputCaptureFormat\":\"UYVY8\","
|
"\"inputCaptureFormat\":\"UYVY8\","
|
||||||
"\"deckLinkBufferedAvailable\":true,\"deckLinkBuffered\":4,"
|
"\"deckLinkBufferedAvailable\":true,\"deckLinkBuffered\":4,"
|
||||||
"\"scheduleCallMs\":1.25}";
|
"\"scheduleCallMs\":1.25,"
|
||||||
|
"\"deckLinkScheduleLeadAvailable\":true,"
|
||||||
|
"\"deckLinkScheduleLeadFrames\":4,"
|
||||||
|
"\"deckLinkPlaybackFrameIndex\":10,"
|
||||||
|
"\"deckLinkNextScheduleFrameIndex\":14,"
|
||||||
|
"\"deckLinkPlaybackStreamTime\":10010,"
|
||||||
|
"\"deckLinkScheduleRealignments\":1}";
|
||||||
Expect(json == expected, "telemetry snapshot serializes to stable JSON");
|
Expect(json == expected, "telemetry snapshot serializes to stable JSON");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user