legacy code cleanup
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Failing after 2m31s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
2026-05-22 15:45:54 +10:00
parent d831b418d7
commit 2fdb1741f9
26 changed files with 27 additions and 3684 deletions

View File

@@ -146,37 +146,6 @@ add_video_shader_test(VideoOutputThreadTests
"${TEST_DIR}/VideoOutputThreadTests.cpp"
)
add_video_shader_test(OutputProductionControllerTests
"${SRC_DIR}/video/playout/OutputProductionController.cpp"
"${TEST_DIR}/OutputProductionControllerTests.cpp"
)
add_video_shader_test(RenderOutputQueueTests
"${SRC_DIR}/video/playout/RenderOutputQueue.cpp"
"${TEST_DIR}/RenderOutputQueueTests.cpp"
)
add_video_shader_test(RenderCadenceControllerTests
"${SRC_DIR}/video/playout/RenderCadenceController.cpp"
"${TEST_DIR}/RenderCadenceControllerTests.cpp"
)
add_video_shader_test(SystemOutputFramePoolTests
"${SRC_DIR}/video/playout/SystemOutputFramePool.cpp"
${VIDEO_FORMAT_SOURCES}
"${TEST_DIR}/SystemOutputFramePoolTests.cpp"
)
add_video_shader_test(VideoBackendLifecycleTests
"${SRC_DIR}/video/legacy/VideoBackendLifecycle.cpp"
"${TEST_DIR}/VideoBackendLifecycleTests.cpp"
)
add_video_shader_test(VideoIODeviceFakeTests
${VIDEO_FORMAT_SOURCES}
"${TEST_DIR}/VideoIODeviceFakeTests.cpp"
)
set_tests_properties(RenderCadenceCompositorLoggerTests PROPERTIES
ENVIRONMENT "VIDEO_SHADER_TEST_LOG_TO_CONSOLE=0"
)

View File

@@ -1,142 +0,0 @@
#include "OutputProductionController.h"
#include <iostream>
#include <string>
namespace
{
int gFailures = 0;
void Expect(bool condition, const char* message)
{
if (condition)
return;
std::cerr << "FAIL: " << message << "\n";
++gFailures;
}
void TestLowQueueRequestsProductionToTarget()
{
VideoPlayoutPolicy policy;
policy.targetReadyFrames = 3;
policy.maxReadyFrames = 5;
OutputProductionController controller(policy);
OutputProductionPressure pressure;
pressure.readyQueueDepth = 1;
pressure.readyQueueCapacity = 5;
const OutputProductionDecision decision = controller.Decide(pressure);
Expect(decision.action == OutputProductionAction::Produce, "low ready queue requests production");
Expect(decision.requestedFrames == 2, "low ready queue requests enough frames to reach target");
Expect(decision.targetReadyFrames == 3, "decision reports effective target");
Expect(decision.maxReadyFrames == 5, "decision reports effective max");
Expect(decision.reason == "ready-queue-below-target", "low queue decision names reason");
}
void TestFullQueueThrottles()
{
VideoPlayoutPolicy policy;
policy.targetReadyFrames = 2;
policy.maxReadyFrames = 4;
OutputProductionController controller(policy);
OutputProductionPressure pressure;
pressure.readyQueueDepth = 4;
pressure.readyQueueCapacity = 4;
const OutputProductionDecision decision = controller.Decide(pressure);
Expect(decision.action == OutputProductionAction::Throttle, "full ready queue throttles production");
Expect(decision.requestedFrames == 0, "full ready queue requests no frames");
Expect(decision.reason == "ready-queue-full", "full queue decision names reason");
}
void TestAtTargetWaitsWithoutPressure()
{
VideoPlayoutPolicy policy;
policy.targetReadyFrames = 2;
policy.maxReadyFrames = 4;
OutputProductionController controller(policy);
OutputProductionPressure pressure;
pressure.readyQueueDepth = 2;
pressure.readyQueueCapacity = 4;
const OutputProductionDecision decision = controller.Decide(pressure);
Expect(decision.action == OutputProductionAction::Wait, "ready queue at target waits without pressure");
Expect(decision.requestedFrames == 0, "wait decision requests no frames");
Expect(decision.reason == "ready-queue-at-target", "wait decision names reason");
}
void TestLateDropPressureRequestsHeadroom()
{
VideoPlayoutPolicy policy;
policy.targetReadyFrames = 2;
policy.maxReadyFrames = 4;
OutputProductionController controller(policy);
OutputProductionPressure pressure;
pressure.readyQueueDepth = 2;
pressure.readyQueueCapacity = 4;
pressure.lateStreak = 1;
OutputProductionDecision decision = controller.Decide(pressure);
Expect(decision.action == OutputProductionAction::Produce, "late pressure requests extra headroom");
Expect(decision.requestedFrames == 1, "late pressure requests one frame");
Expect(decision.reason == "playout-pressure", "late pressure decision names reason");
pressure.lateStreak = 0;
pressure.dropStreak = 2;
decision = controller.Decide(pressure);
Expect(decision.action == OutputProductionAction::Produce, "drop pressure requests extra headroom");
pressure.dropStreak = 0;
pressure.readyQueueUnderrunCount = 1;
decision = controller.Decide(pressure);
Expect(decision.action == OutputProductionAction::Produce, "underrun pressure requests extra headroom");
}
void TestPolicyNormalizesAndClampsToCapacity()
{
VideoPlayoutPolicy policy;
policy.targetReadyFrames = 0;
policy.maxReadyFrames = 8;
OutputProductionController controller(policy);
OutputProductionPressure pressure;
pressure.readyQueueDepth = 1;
pressure.readyQueueCapacity = 3;
const OutputProductionDecision decision = controller.Decide(pressure);
Expect(decision.action == OutputProductionAction::Wait, "normalized target at current depth waits");
Expect(decision.targetReadyFrames == 1, "target normalizes to at least one frame");
Expect(decision.maxReadyFrames == 3, "max ready frames clamps to queue capacity");
}
void TestActionNames()
{
Expect(OutputProductionActionName(OutputProductionAction::Produce) == std::string("Produce"), "produce action has name");
Expect(OutputProductionActionName(OutputProductionAction::Wait) == std::string("Wait"), "wait action has name");
Expect(OutputProductionActionName(OutputProductionAction::Throttle) == std::string("Throttle"), "throttle action has name");
}
}
int main()
{
TestLowQueueRequestsProductionToTarget();
TestFullQueueThrottles();
TestAtTargetWaitsWithoutPressure();
TestLateDropPressureRequestsHeadroom();
TestPolicyNormalizesAndClampsToCapacity();
TestActionNames();
if (gFailures != 0)
{
std::cerr << gFailures << " OutputProductionController test failure(s).\n";
return 1;
}
std::cout << "OutputProductionController tests passed.\n";
return 0;
}

View File

@@ -1,172 +0,0 @@
#include "RenderCadenceController.h"
#include <chrono>
#include <iostream>
#include <string>
namespace
{
int gFailures = 0;
using Clock = RenderCadenceController::Clock;
using Duration = RenderCadenceController::Duration;
using TimePoint = RenderCadenceController::TimePoint;
void Expect(bool condition, const char* message)
{
if (condition)
return;
std::cerr << "FAIL: " << message << "\n";
++gFailures;
}
Duration Ms(int64_t value)
{
return std::chrono::duration_cast<Duration>(std::chrono::milliseconds(value));
}
void TestExactCadenceAdvancesFrameIndexAndNextTick()
{
RenderCadenceController controller;
const TimePoint start = Clock::time_point(Ms(1000));
controller.Configure(Ms(20), start);
RenderCadenceDecision first = controller.Tick(start);
Expect(first.action == RenderCadenceAction::Render, "first exact tick renders");
Expect(first.frameIndex == 0, "first exact tick renders frame zero");
Expect(first.renderTargetTime == start, "first exact target is configured start");
Expect(first.nextRenderTime == start + Ms(20), "first exact tick advances next render time");
Expect(first.skippedTicks == 0, "first exact tick skips no ticks");
Expect(first.lateness == Duration::zero(), "first exact tick records no lateness");
RenderCadenceDecision second = controller.Tick(start + Ms(20));
Expect(second.action == RenderCadenceAction::Render, "second exact tick renders");
Expect(second.frameIndex == 1, "second exact tick renders frame one");
Expect(controller.NextFrameIndex() == 2, "controller tracks next frame index after exact ticks");
Expect(controller.Metrics().renderedFrameCount == 2, "metrics count exact rendered frames");
}
void TestEarlyTickWaitsWithoutAdvancing()
{
RenderCadenceController controller;
const TimePoint start = Clock::time_point(Ms(0));
controller.Configure(Ms(20), start);
(void)controller.Tick(start);
RenderCadenceDecision decision = controller.Tick(start + Ms(10));
Expect(decision.action == RenderCadenceAction::Wait, "early tick waits");
Expect(decision.waitDuration == Ms(10), "early tick reports wait duration");
Expect(decision.frameIndex == 1, "early tick reports next pending frame");
Expect(controller.NextFrameIndex() == 1, "early tick does not advance frame index");
Expect(controller.NextRenderTime() == start + Ms(20), "early tick does not advance next render time");
}
void TestSlightLatenessRendersAndRecordsMetrics()
{
RenderCadencePolicy policy;
policy.skipThresholdFrames = 3.0;
RenderCadenceController controller;
const TimePoint start = Clock::time_point(Ms(0));
controller.Configure(Ms(20), start, policy);
RenderCadenceDecision decision = controller.Tick(start + Ms(5));
Expect(decision.action == RenderCadenceAction::Render, "slightly late tick renders");
Expect(decision.frameIndex == 0, "slightly late tick keeps pending frame");
Expect(decision.skippedTicks == 0, "slightly late tick skips no ticks");
Expect(decision.lateness == Ms(5), "slightly late tick reports lateness");
Expect(controller.Metrics().lateFrameCount == 1, "metrics count late rendered frame");
Expect(controller.Metrics().lastLateness == Ms(5), "metrics keep last lateness");
Expect(controller.Metrics().maxLateness == Ms(5), "metrics keep max lateness");
}
void TestLargeLatenessSkipsTicksAccordingToPolicy()
{
RenderCadencePolicy policy;
policy.skipLateTicks = true;
policy.skipThresholdFrames = 2.0;
policy.maxSkippedTicksPerDecision = 8;
RenderCadenceController controller;
const TimePoint start = Clock::time_point(Ms(0));
controller.Configure(Ms(20), start, policy);
RenderCadenceDecision decision = controller.Tick(start + Ms(70));
Expect(decision.action == RenderCadenceAction::Render, "large late tick renders newest allowed frame");
Expect(decision.skippedTicks == 3, "large late tick skips elapsed render ticks");
Expect(decision.frameIndex == 3, "large late tick renders skipped-to frame");
Expect(decision.renderTargetTime == start + Ms(60), "large late tick targets newest elapsed tick");
Expect(decision.lateness == Ms(10), "large late tick measures residual lateness");
Expect(controller.NextFrameIndex() == 4, "large late tick advances past rendered frame");
Expect(controller.NextRenderTime() == start + Ms(80), "large late tick advances to following cadence");
Expect(controller.Metrics().skippedTickCount == 3, "metrics count skipped ticks");
}
void TestSkipPolicyCanDisableOrCapSkippedTicks()
{
const TimePoint start = Clock::time_point(Ms(0));
RenderCadencePolicy disabledPolicy;
disabledPolicy.skipLateTicks = false;
RenderCadenceController disabledController;
disabledController.Configure(Ms(20), start, disabledPolicy);
RenderCadenceDecision disabled = disabledController.Tick(start + Ms(90));
Expect(disabled.skippedTicks == 0, "disabled skip policy renders pending frame");
Expect(disabled.frameIndex == 0, "disabled skip policy preserves pending frame index");
RenderCadencePolicy cappedPolicy;
cappedPolicy.skipThresholdFrames = 1.0;
cappedPolicy.maxSkippedTicksPerDecision = 2;
RenderCadenceController cappedController;
cappedController.Configure(Ms(20), start, cappedPolicy);
RenderCadenceDecision capped = cappedController.Tick(start + Ms(90));
Expect(capped.skippedTicks == 2, "skip policy caps skipped ticks");
Expect(capped.frameIndex == 2, "capped skip renders capped frame index");
}
void TestResetRestartsCadenceAndMetrics()
{
RenderCadenceController controller;
const TimePoint start = Clock::time_point(Ms(0));
controller.Configure(Ms(20), start);
(void)controller.Tick(start + Ms(50));
const TimePoint restarted = start + Ms(200);
controller.Reset(restarted);
Expect(controller.NextFrameIndex() == 0, "reset restarts frame index");
Expect(controller.NextRenderTime() == restarted, "reset restarts next render time");
Expect(controller.Metrics().renderedFrameCount == 0, "reset clears rendered metrics");
RenderCadenceDecision decision = controller.Tick(restarted);
Expect(decision.action == RenderCadenceAction::Render, "reset cadence renders at new start");
Expect(decision.frameIndex == 0, "reset cadence renders frame zero");
}
void TestActionNames()
{
Expect(RenderCadenceActionName(RenderCadenceAction::Render) == std::string("Render"), "render action has name");
Expect(RenderCadenceActionName(RenderCadenceAction::Wait) == std::string("Wait"), "wait action has name");
}
}
int main()
{
TestExactCadenceAdvancesFrameIndexAndNextTick();
TestEarlyTickWaitsWithoutAdvancing();
TestSlightLatenessRendersAndRecordsMetrics();
TestLargeLatenessSkipsTicksAccordingToPolicy();
TestSkipPolicyCanDisableOrCapSkippedTicks();
TestResetRestartsCadenceAndMetrics();
TestActionNames();
if (gFailures != 0)
{
std::cerr << gFailures << " RenderCadenceController test failure(s).\n";
return 1;
}
std::cout << "RenderCadenceController tests passed.\n";
return 0;
}

View File

@@ -1,209 +0,0 @@
#include "RenderOutputQueue.h"
#include <iostream>
namespace
{
int gFailures = 0;
int gReleasedFrames = 0;
void Expect(bool condition, const char* message)
{
if (condition)
return;
std::cerr << "FAIL: " << message << "\n";
++gFailures;
}
RenderOutputFrame MakeFrame(uint64_t index)
{
RenderOutputFrame frame;
frame.frameIndex = index;
frame.frame.nativeFrame = reinterpret_cast<void*>(static_cast<uintptr_t>(index + 1));
return frame;
}
void CountReleasedFrame(VideoIOOutputFrame& frame)
{
if (frame.nativeFrame != nullptr)
{
++gReleasedFrames;
frame.nativeFrame = nullptr;
}
}
RenderOutputFrame MakeOwnedFrame(uint64_t index)
{
RenderOutputFrame frame = MakeFrame(index);
frame.releaseFrame = CountReleasedFrame;
return frame;
}
void TestQueuePreservesOrdering()
{
VideoPlayoutPolicy policy;
policy.maxReadyFrames = 3;
RenderOutputQueue queue(policy);
Expect(queue.Push(MakeFrame(1)), "first ready frame pushes");
Expect(queue.Push(MakeFrame(2)), "second ready frame pushes");
RenderOutputFrame frame;
Expect(queue.TryPop(frame), "first ready frame pops");
Expect(frame.frameIndex == 1, "queue pops first frame first");
Expect(queue.TryPop(frame), "second ready frame pops");
Expect(frame.frameIndex == 2, "queue pops second frame second");
}
void TestBoundedQueueDropsOldestFrame()
{
VideoPlayoutPolicy policy;
policy.maxReadyFrames = 2;
RenderOutputQueue queue(policy);
queue.Push(MakeFrame(1));
queue.Push(MakeFrame(2));
queue.Push(MakeFrame(3));
RenderOutputQueueMetrics metrics = queue.GetMetrics();
Expect(metrics.depth == 2, "bounded queue depth stays at capacity");
Expect(metrics.droppedCount == 1, "bounded queue counts dropped oldest frame");
RenderOutputFrame frame;
Expect(queue.TryPop(frame), "bounded queue pops after drop");
Expect(frame.frameIndex == 2, "oldest frame was dropped when queue overflowed");
}
void TestOverflowReleasesDroppedFrame()
{
gReleasedFrames = 0;
VideoPlayoutPolicy policy;
policy.targetReadyFrames = 1;
policy.maxReadyFrames = 1;
RenderOutputQueue queue(policy);
queue.Push(MakeOwnedFrame(1));
queue.Push(MakeOwnedFrame(2));
Expect(gReleasedFrames == 1, "overflow releases dropped ready frame");
RenderOutputFrame frame;
Expect(queue.TryPop(frame), "newest owned frame remains queued");
Expect(frame.frameIndex == 2, "overflow keeps newest owned frame");
Expect(gReleasedFrames == 1, "pop transfers ownership without releasing");
}
void TestDropOldestFrameReleasesFrame()
{
gReleasedFrames = 0;
VideoPlayoutPolicy policy;
policy.maxReadyFrames = 2;
RenderOutputQueue queue(policy);
queue.Push(MakeOwnedFrame(1));
queue.Push(MakeOwnedFrame(2));
Expect(queue.DropOldestFrame(), "oldest ready frame can be explicitly dropped");
Expect(gReleasedFrames == 1, "explicit drop releases oldest frame");
RenderOutputQueueMetrics metrics = queue.GetMetrics();
Expect(metrics.depth == 1, "explicit drop reduces queue depth");
Expect(metrics.droppedCount == 1, "explicit drop increments dropped count");
RenderOutputFrame frame;
Expect(queue.TryPop(frame), "newest frame remains after explicit drop");
Expect(frame.frameIndex == 2, "explicit drop keeps newest frame");
Expect(!queue.DropOldestFrame(), "empty queue cannot drop a frame");
}
void TestUnderrunIsCounted()
{
RenderOutputQueue queue;
RenderOutputFrame frame;
Expect(!queue.TryPop(frame), "empty queue reports underrun");
RenderOutputQueueMetrics metrics = queue.GetMetrics();
Expect(metrics.underrunCount == 1, "empty pop increments underrun count");
}
void TestConfigureShrinksDepthToNewCapacity()
{
VideoPlayoutPolicy policy;
policy.maxReadyFrames = 4;
RenderOutputQueue queue(policy);
queue.Push(MakeFrame(1));
queue.Push(MakeFrame(2));
queue.Push(MakeFrame(3));
VideoPlayoutPolicy smallerPolicy;
smallerPolicy.targetReadyFrames = 1;
smallerPolicy.maxReadyFrames = 1;
queue.Configure(smallerPolicy);
RenderOutputQueueMetrics metrics = queue.GetMetrics();
Expect(metrics.depth == 1, "configure trims queue to new capacity");
Expect(metrics.droppedCount == 2, "configure counts trimmed frames as drops");
RenderOutputFrame frame;
Expect(queue.TryPop(frame), "trimmed queue still has newest frame");
Expect(frame.frameIndex == 3, "configure keeps newest ready frame");
}
void TestConfigureReleasesTrimmedFrames()
{
gReleasedFrames = 0;
VideoPlayoutPolicy policy;
policy.maxReadyFrames = 3;
RenderOutputQueue queue(policy);
queue.Push(MakeOwnedFrame(1));
queue.Push(MakeOwnedFrame(2));
queue.Push(MakeOwnedFrame(3));
VideoPlayoutPolicy smallerPolicy;
smallerPolicy.targetReadyFrames = 1;
smallerPolicy.maxReadyFrames = 1;
queue.Configure(smallerPolicy);
Expect(gReleasedFrames == 2, "configure releases trimmed ready frames");
RenderOutputFrame frame;
Expect(queue.TryPop(frame), "trimmed owned queue still has newest frame");
Expect(frame.frameIndex == 3, "configure keeps newest owned frame after release");
}
void TestClearReleasesQueuedFrames()
{
gReleasedFrames = 0;
RenderOutputQueue queue;
queue.Push(MakeOwnedFrame(1));
queue.Push(MakeOwnedFrame(2));
queue.Clear();
RenderOutputQueueMetrics metrics = queue.GetMetrics();
Expect(metrics.depth == 0, "clear empties ready queue");
Expect(gReleasedFrames == 2, "clear releases queued ready frames");
}
}
int main()
{
TestQueuePreservesOrdering();
TestBoundedQueueDropsOldestFrame();
TestOverflowReleasesDroppedFrame();
TestDropOldestFrameReleasesFrame();
TestUnderrunIsCounted();
TestConfigureShrinksDepthToNewCapacity();
TestConfigureReleasesTrimmedFrames();
TestClearReleasesQueuedFrames();
if (gFailures != 0)
{
std::cerr << gFailures << " render output queue test failure(s).\n";
return 1;
}
std::cout << "RenderOutputQueue tests passed.\n";
return 0;
}

View File

@@ -1,231 +0,0 @@
#include "SystemOutputFramePool.h"
#include <cstdint>
#include <iostream>
namespace
{
int gFailures = 0;
void Expect(bool condition, const char* message)
{
if (condition)
return;
std::cerr << "FAIL: " << message << "\n";
++gFailures;
}
SystemOutputFramePoolConfig MakeConfig(std::size_t capacity = 2)
{
SystemOutputFramePoolConfig config;
config.width = 4;
config.height = 3;
config.pixelFormat = VideoIOPixelFormat::Bgra8;
config.capacity = capacity;
return config;
}
void TestAcquireHonorsCapacityAndFrameShape()
{
SystemOutputFramePool pool(MakeConfig(2));
OutputFrameSlot first;
OutputFrameSlot second;
OutputFrameSlot third;
Expect(pool.AcquireFreeSlot(first), "first slot can be acquired");
Expect(pool.AcquireFreeSlot(second), "second slot can be acquired");
Expect(!pool.AcquireFreeSlot(third), "fixed capacity rejects third acquire");
Expect(first.frame.bytes != nullptr, "acquired slot has system memory");
Expect(first.frame.nativeBuffer == first.frame.bytes, "native buffer points at system memory");
Expect(first.frame.nativeFrame == nullptr, "system frame has no native frame");
Expect(first.frame.width == 4, "frame width is configured");
Expect(first.frame.height == 3, "frame height is configured");
Expect(first.frame.rowBytes == 16, "BGRA8 row bytes are inferred");
Expect(first.frame.pixelFormat == VideoIOPixelFormat::Bgra8, "BGRA8 is the default output format");
Expect(first.frame.bytes != second.frame.bytes, "each slot owns distinct memory");
SystemOutputFramePoolMetrics metrics = pool.GetMetrics();
Expect(metrics.freeCount == 0, "all slots are in use");
Expect(metrics.renderingCount == 2, "rendering slots are counted");
Expect(metrics.acquiredCount == 2, "acquired slots are counted");
Expect(metrics.acquireMissCount == 1, "capacity miss is counted");
}
void TestPhase77StateContract()
{
SystemOutputFramePool pool(MakeConfig(1));
SystemOutputFramePoolMetrics metrics = pool.GetMetrics();
Expect(metrics.freeCount == 1, "new pool starts with one free slot");
Expect(metrics.renderingCount == 0, "new pool starts with no rendering slots");
Expect(metrics.completedCount == 0, "new pool starts with no completed slots");
Expect(metrics.scheduledCount == 0, "new pool starts with no scheduled slots");
OutputFrameSlot slot;
Expect(pool.AcquireRenderingSlot(slot), "free slot moves to rendering");
metrics = pool.GetMetrics();
Expect(metrics.freeCount == 0, "rendering slot leaves free pool");
Expect(metrics.renderingCount == 1, "rendering slot is counted");
Expect(pool.PublishCompletedSlot(slot), "rendering slot moves to completed");
metrics = pool.GetMetrics();
Expect(metrics.renderingCount == 0, "completed slot leaves rendering");
Expect(metrics.completedCount == 1, "completed slot is counted");
Expect(metrics.readyCount == 1, "completed slot is available to scheduler");
OutputFrameSlot completed;
Expect(pool.ConsumeCompletedSlot(completed), "completed slot can be dequeued for scheduling");
metrics = pool.GetMetrics();
Expect(metrics.completedCount == 1, "dequeued completed slot remains completed until scheduled");
Expect(metrics.readyCount == 0, "dequeued completed slot leaves ready queue");
Expect(pool.MarkScheduled(completed), "completed slot moves to scheduled");
metrics = pool.GetMetrics();
Expect(metrics.completedCount == 0, "scheduled slot leaves completed state");
Expect(metrics.scheduledCount == 1, "scheduled slot is counted");
Expect(pool.ReleaseScheduledSlot(completed), "scheduled slot returns to free");
metrics = pool.GetMetrics();
Expect(metrics.freeCount == 1, "released scheduled slot returns to free");
Expect(metrics.scheduledCount == 0, "released scheduled slot leaves scheduled state");
}
void TestReadySlotsAreConsumedFifo()
{
SystemOutputFramePool pool(MakeConfig(2));
OutputFrameSlot first;
OutputFrameSlot second;
Expect(pool.AcquireFreeSlot(first), "first FIFO slot can be acquired");
Expect(pool.AcquireFreeSlot(second), "second FIFO slot can be acquired");
Expect(pool.PublishReadySlot(first), "first FIFO slot can be published");
Expect(pool.PublishReadySlot(second), "second FIFO slot can be published");
OutputFrameSlot consumed;
Expect(pool.ConsumeReadySlot(consumed), "first ready slot can be consumed");
Expect(consumed.index == first.index, "first published slot is consumed first");
Expect(pool.MarkScheduled(consumed), "consumed slot can be marked scheduled");
Expect(pool.ReleaseScheduledSlot(consumed), "scheduled slot can be released");
Expect(pool.ConsumeReadySlot(consumed), "second ready slot can be consumed");
Expect(consumed.index == second.index, "second published slot is consumed second");
Expect(pool.ReleaseSlot(consumed), "consumed slot can be released without scheduling");
SystemOutputFramePoolMetrics metrics = pool.GetMetrics();
Expect(metrics.freeCount == 2, "released slots return to free pool");
Expect(metrics.readyCount == 0, "ready queue is empty after consumption");
}
void TestCompletedSlotCannotBeAcquiredUntilReleased()
{
SystemOutputFramePool pool(MakeConfig(1));
OutputFrameSlot slot;
OutputFrameSlot extra;
Expect(pool.AcquireRenderingSlot(slot), "single slot can be acquired for rendering");
Expect(pool.PublishCompletedSlot(slot), "single slot can be published completed");
Expect(!pool.AcquireRenderingSlot(extra), "completed slot is not available for rendering");
OutputFrameSlot completed;
Expect(pool.ConsumeCompletedSlot(completed), "completed slot can be dequeued");
Expect(!pool.AcquireRenderingSlot(extra), "dequeued completed slot is still not free");
Expect(pool.MarkScheduled(completed), "dequeued completed slot can be scheduled");
Expect(!pool.AcquireRenderingSlot(extra), "scheduled slot is still not free");
Expect(pool.ReleaseScheduledSlot(completed), "scheduled slot can be released");
Expect(pool.AcquireRenderingSlot(extra), "released slot can be acquired again");
}
void TestReadySlotCanBeScheduledByBuffer()
{
SystemOutputFramePool pool(MakeConfig(1));
OutputFrameSlot slot;
Expect(pool.AcquireFreeSlot(slot), "buffer schedule slot can be acquired");
void* bytes = slot.frame.bytes;
Expect(pool.PublishReadySlot(slot), "buffer schedule slot can be published");
Expect(pool.MarkScheduledByBuffer(bytes), "ready slot can be marked scheduled by buffer");
SystemOutputFramePoolMetrics metrics = pool.GetMetrics();
Expect(metrics.readyCount == 0, "scheduled-by-buffer removes slot from ready queue");
Expect(metrics.scheduledCount == 1, "scheduled-by-buffer counts scheduled slot");
Expect(pool.ReleaseSlotByBuffer(bytes), "scheduled slot can be released by buffer");
metrics = pool.GetMetrics();
Expect(metrics.freeCount == 1, "released-by-buffer slot returns to free pool");
}
void TestInvalidTransitionsAreRejected()
{
SystemOutputFramePool pool(MakeConfig(1));
OutputFrameSlot slot;
Expect(pool.AcquireFreeSlot(slot), "transition slot can be acquired");
Expect(!pool.MarkScheduled(slot), "acquired slot cannot be marked scheduled");
Expect(pool.PublishReadySlot(slot), "acquired slot can be published");
Expect(!pool.PublishReadySlot(slot), "ready slot cannot be published twice");
Expect(pool.ReleaseSlot(slot), "ready slot can be released to free");
Expect(!pool.ReleaseSlot(slot), "free slot cannot be released again");
OutputFrameSlot next;
Expect(pool.AcquireFreeSlot(next), "slot can be reacquired after release");
Expect(next.index == slot.index, "same storage slot can be reused");
Expect(next.generation != slot.generation, "stale handles are invalidated on reacquire");
Expect(!pool.PublishReadySlot(slot), "stale handle cannot publish reacquired slot");
}
void TestPixelFormatAwareSizing()
{
SystemOutputFramePoolConfig config;
config.width = 7;
config.height = 2;
config.pixelFormat = VideoIOPixelFormat::V210;
config.capacity = 1;
SystemOutputFramePool pool(config);
OutputFrameSlot slot;
Expect(pool.AcquireFreeSlot(slot), "v210 slot can be acquired");
Expect(slot.frame.pixelFormat == VideoIOPixelFormat::V210, "slot keeps configured pixel format");
Expect(slot.frame.rowBytes == static_cast<long>(MinimumV210RowBytes(config.width)), "v210 row bytes are inferred");
SystemOutputFramePoolConfig explicitConfig = config;
explicitConfig.pixelFormat = VideoIOPixelFormat::Uyvy8;
explicitConfig.rowBytes = 64;
pool.Configure(explicitConfig);
Expect(pool.AcquireFreeSlot(slot), "explicit row-byte slot can be acquired");
Expect(slot.frame.pixelFormat == VideoIOPixelFormat::Uyvy8, "slot keeps reconfigured pixel format");
Expect(slot.frame.rowBytes == 64, "explicit row bytes are preserved");
}
void TestEmptyReadyQueueUnderrunIsCounted()
{
SystemOutputFramePool pool(MakeConfig(1));
OutputFrameSlot slot;
Expect(!pool.ConsumeReadySlot(slot), "empty ready queue cannot be consumed");
SystemOutputFramePoolMetrics metrics = pool.GetMetrics();
Expect(metrics.readyUnderrunCount == 1, "ready underrun is counted");
}
}
int main()
{
TestAcquireHonorsCapacityAndFrameShape();
TestPhase77StateContract();
TestReadySlotsAreConsumedFifo();
TestCompletedSlotCannotBeAcquiredUntilReleased();
TestReadySlotCanBeScheduledByBuffer();
TestInvalidTransitionsAreRejected();
TestPixelFormatAwareSizing();
TestEmptyReadyQueueUnderrunIsCounted();
if (gFailures != 0)
{
std::cerr << gFailures << " system output frame pool test failure(s).\n";
return 1;
}
std::cout << "SystemOutputFramePool tests passed.\n";
return 0;
}

View File

@@ -1,96 +0,0 @@
#include "VideoBackendLifecycle.h"
#include <iostream>
#include <string>
namespace
{
int gFailures = 0;
void Expect(bool condition, const char* message)
{
if (condition)
return;
std::cerr << "FAIL: " << message << "\n";
++gFailures;
}
void TestAllowedLifecycleTransitions()
{
VideoBackendLifecycle lifecycle;
Expect(lifecycle.State() == VideoBackendLifecycleState::Uninitialized, "lifecycle starts uninitialized");
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Discovering, "discover").accepted,
"uninitialized can transition to discovering");
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Discovered, "discovered").accepted,
"discovering can transition to discovered");
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Configuring, "configuring").accepted,
"discovered can transition to configuring");
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Configured, "configured").accepted,
"configuring can transition to configured");
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Prerolling, "preroll").accepted,
"configured can transition to prerolling");
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Running, "running").accepted,
"prerolling can transition to running");
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Degraded, "degraded").accepted,
"running can transition to degraded");
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Running, "recovered").accepted,
"degraded can transition back to running");
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Stopping, "stopping").accepted,
"running can transition to stopping");
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Stopped, "stopped").accepted,
"stopping can transition to stopped");
}
void TestInvalidLifecycleTransitionIsRejected()
{
VideoBackendLifecycle lifecycle;
const VideoBackendLifecycleTransition transition =
lifecycle.TransitionTo(VideoBackendLifecycleState::Running, "skip setup");
Expect(!transition.accepted, "uninitialized cannot transition directly to running");
Expect(lifecycle.State() == VideoBackendLifecycleState::Uninitialized, "invalid transition leaves state unchanged");
Expect(transition.errorMessage.find("Invalid video backend lifecycle transition") != std::string::npos,
"invalid transition reports an error");
}
void TestFailureStateRecordsReasonAndCanRecover()
{
VideoBackendLifecycle lifecycle;
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Discovering, "discover").accepted,
"lifecycle can start discovery");
Expect(lifecycle.Fail("no device").accepted, "discovering can transition to failed");
Expect(lifecycle.State() == VideoBackendLifecycleState::Failed, "failure transition sets failed state");
Expect(lifecycle.FailureReason() == "no device", "failure reason is retained");
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Discovering, "retry").accepted,
"failed lifecycle can retry discovery");
Expect(lifecycle.FailureReason().empty(), "successful non-failed transition clears failure reason");
}
void TestStateNamesAreStable()
{
Expect(std::string(VideoBackendLifecycle::StateName(VideoBackendLifecycleState::Uninitialized)) == "uninitialized",
"uninitialized state name is stable");
Expect(std::string(VideoBackendLifecycle::StateName(VideoBackendLifecycleState::Running)) == "running",
"running state name is stable");
Expect(std::string(VideoBackendLifecycle::StateName(VideoBackendLifecycleState::Failed)) == "failed",
"failed state name is stable");
}
}
int main()
{
TestAllowedLifecycleTransitions();
TestInvalidLifecycleTransitionIsRejected();
TestFailureStateRecordsReasonAndCanRecover();
TestStateNamesAreStable();
if (gFailures != 0)
{
std::cerr << gFailures << " video backend lifecycle test failure(s).\n";
return 1;
}
std::cout << "VideoBackendLifecycle tests passed.\n";
return 0;
}

View File

@@ -1,186 +0,0 @@
#include "VideoIOTypes.h"
#include <array>
#include <iostream>
namespace
{
int gFailures = 0;
void Expect(bool condition, const char* message)
{
if (condition)
return;
std::cerr << "FAIL: " << message << "\n";
++gFailures;
}
class FakeVideoIODevice : public VideoIODevice
{
public:
void ReleaseResources() override {}
bool DiscoverDevicesAndModes(const VideoFormatSelection&, std::string&) override
{
mState.inputFrameSize = { 1920, 1080 };
mState.outputFrameSize = { 1920, 1080 };
mState.inputDisplayModeName = "fake 1080p";
mState.outputModelName = "Fake Video IO";
mState.hasInputDevice = true;
return true;
}
bool SelectPreferredFormats(const VideoFormatSelection&, bool, std::string&) override
{
mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8;
mState.outputPixelFormat = VideoIOPixelFormat::Bgra8;
mState.inputFrameRowBytes = VideoIORowBytes(mState.inputPixelFormat, mState.inputFrameSize.width);
mState.outputFrameRowBytes = VideoIORowBytes(mState.outputPixelFormat, mState.outputFrameSize.width);
mState.captureTextureWidth = PackedTextureWidthFromRowBytes(mState.inputFrameRowBytes);
mState.outputPackTextureWidth = mState.outputFrameSize.width;
return true;
}
bool ConfigureInput(InputFrameCallback callback, const VideoFormat&, std::string&) override
{
mInputCallback = callback;
return true;
}
bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat&, bool, std::string&) override
{
mOutputCallback = callback;
return true;
}
bool PrepareOutputSchedule() override
{
mPreparedOutputSchedule = true;
return true;
}
bool StartInputStreams() override
{
mInputStreamsStarted = true;
mState.hasInputSource = true;
VideoIOFrame input;
input.bytes = mInputBytes.data();
input.rowBytes = static_cast<long>(mState.inputFrameRowBytes);
input.width = mState.inputFrameSize.width;
input.height = mState.inputFrameSize.height;
input.pixelFormat = mState.inputPixelFormat;
if (mInputCallback)
mInputCallback(input);
return true;
}
bool StartScheduledPlayback() override
{
mScheduledPlaybackStarted = true;
if (mOutputCallback)
mOutputCallback(VideoIOCompletion{ VideoIOCompletionResult::Completed });
return true;
}
bool Start() override
{
return PrepareOutputSchedule() && StartInputStreams() && StartScheduledPlayback();
}
bool Stop() override { return true; }
const VideoIOState& State() const override { return mState; }
VideoIOState& MutableState() override { return mState; }
bool BeginOutputFrame(VideoIOOutputFrame& frame) override
{
frame.bytes = mOutputBytes.data();
frame.rowBytes = static_cast<long>(mState.outputFrameRowBytes);
frame.width = mState.outputFrameSize.width;
frame.height = mState.outputFrameSize.height;
frame.pixelFormat = mState.outputPixelFormat;
return true;
}
void EndOutputFrame(VideoIOOutputFrame&) override {}
bool ScheduleOutputFrame(const VideoIOOutputFrame&) override
{
++mScheduledFrames;
return true;
}
VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth) override
{
mLastCompletion = result;
mLastReadyQueueDepth = readyQueueDepth;
VideoPlayoutRecoveryDecision decision;
decision.result = result;
decision.readyQueueDepth = readyQueueDepth;
return decision;
}
unsigned ScheduledFrames() const { return mScheduledFrames; }
bool PreparedOutputSchedule() const { return mPreparedOutputSchedule; }
bool InputStreamsStarted() const { return mInputStreamsStarted; }
bool ScheduledPlaybackStarted() const { return mScheduledPlaybackStarted; }
VideoIOCompletionResult LastCompletion() const { return mLastCompletion; }
uint64_t LastReadyQueueDepth() const { return mLastReadyQueueDepth; }
private:
VideoIOState mState;
InputFrameCallback mInputCallback;
OutputFrameCallback mOutputCallback;
std::array<unsigned char, 3840> mInputBytes = {};
std::array<unsigned char, 7680> mOutputBytes = {};
unsigned mScheduledFrames = 0;
bool mPreparedOutputSchedule = false;
bool mInputStreamsStarted = false;
bool mScheduledPlaybackStarted = false;
VideoIOCompletionResult mLastCompletion = VideoIOCompletionResult::Unknown;
uint64_t mLastReadyQueueDepth = 0;
};
}
int main()
{
FakeVideoIODevice device;
VideoFormatSelection selection;
std::string error;
bool inputSeen = false;
bool outputSeen = false;
Expect(device.DiscoverDevicesAndModes(selection, error), "fake discovery succeeds");
Expect(device.SelectPreferredFormats(selection, false, error), "fake format selection succeeds");
Expect(device.ConfigureInput([&](const VideoIOFrame& frame) {
inputSeen = frame.bytes != nullptr && frame.width == 1920 && frame.pixelFormat == VideoIOPixelFormat::Uyvy8;
}, selection.input, error), "fake input config succeeds");
Expect(device.ConfigureOutput([&](const VideoIOCompletion& completion) {
outputSeen = completion.result == VideoIOCompletionResult::Completed;
}, selection.output, false, error), "fake output config succeeds");
Expect(device.Start(), "fake device starts");
VideoIOOutputFrame outputFrame;
Expect(device.BeginOutputFrame(outputFrame), "fake output frame can be acquired");
device.EndOutputFrame(outputFrame);
device.AccountForCompletionResult(VideoIOCompletionResult::Completed, 2);
Expect(device.ScheduleOutputFrame(outputFrame), "fake output frame can be scheduled");
Expect(inputSeen, "fake input callback emits generic frame");
Expect(outputSeen, "fake output callback emits generic completion");
Expect(device.PreparedOutputSchedule(), "fake output schedule was prepared");
Expect(device.InputStreamsStarted(), "fake input streams started");
Expect(device.ScheduledPlaybackStarted(), "fake scheduled playback started");
Expect(device.ScheduledFrames() == 1, "fake backend schedules one frame");
Expect(device.LastCompletion() == VideoIOCompletionResult::Completed, "fake backend records generic completion");
Expect(device.LastReadyQueueDepth() == 2, "fake backend records ready queue depth");
if (gFailures != 0)
{
std::cerr << gFailures << " VideoIODevice fake test failure(s).\n";
return 1;
}
std::cout << "VideoIODevice fake tests passed.\n";
return 0;
}