Improvement
This commit is contained in:
@@ -200,6 +200,34 @@ void TestOutputRenderPipelineTiming()
|
||||
Expect(playout.outputCachedFallbackCount == 1, "output render timing counts cached fallbacks");
|
||||
Expect(playout.outputSyncFallbackCount == 1, "output render timing counts sync fallbacks");
|
||||
}
|
||||
|
||||
void TestSystemMemoryPlayoutStats()
|
||||
{
|
||||
HealthTelemetry telemetry;
|
||||
telemetry.RecordSystemMemoryPlayoutStats(2, 3, 1, 4, 5, 6, 12.5, 24.0);
|
||||
|
||||
HealthTelemetry::BackendPlayoutSnapshot playout = telemetry.GetBackendPlayoutSnapshot();
|
||||
Expect(playout.systemFramePoolFree == 2, "system-memory playout stores free frame count");
|
||||
Expect(playout.systemFramePoolReady == 3, "system-memory playout stores ready frame count");
|
||||
Expect(playout.systemFramePoolScheduled == 1, "system-memory playout stores scheduled frame count");
|
||||
Expect(playout.systemFrameUnderrunCount == 4, "system-memory playout stores underrun count");
|
||||
Expect(playout.systemFrameRepeatCount == 5, "system-memory playout stores repeat count");
|
||||
Expect(playout.systemFrameDropCount == 6, "system-memory playout stores drop count");
|
||||
Expect(playout.systemFrameAgeAtScheduleMilliseconds == 12.5, "system-memory playout stores schedule age");
|
||||
Expect(playout.systemFrameAgeAtCompletionMilliseconds == 24.0, "system-memory playout stores completion age");
|
||||
|
||||
Expect(telemetry.TryRecordSystemMemoryPlayoutStats(1, 0, 2, 7, 8, 9, -1.0, -2.0),
|
||||
"try system-memory playout stats succeeds when uncontended");
|
||||
playout = telemetry.GetBackendPlayoutSnapshot();
|
||||
Expect(playout.systemFramePoolFree == 1, "try system-memory playout stores free frame count");
|
||||
Expect(playout.systemFramePoolReady == 0, "try system-memory playout stores ready frame count");
|
||||
Expect(playout.systemFramePoolScheduled == 2, "try system-memory playout stores scheduled frame count");
|
||||
Expect(playout.systemFrameUnderrunCount == 7, "try system-memory playout stores underrun count");
|
||||
Expect(playout.systemFrameRepeatCount == 8, "try system-memory playout stores repeat count");
|
||||
Expect(playout.systemFrameDropCount == 9, "try system-memory playout stores drop count");
|
||||
Expect(playout.systemFrameAgeAtScheduleMilliseconds == 0.0, "system-memory playout clamps negative schedule age");
|
||||
Expect(playout.systemFrameAgeAtCompletionMilliseconds == 0.0, "system-memory playout clamps negative completion age");
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -210,6 +238,7 @@ int main()
|
||||
TestPersistenceWriteHealth();
|
||||
TestBackendPlayoutHealth();
|
||||
TestOutputRenderPipelineTiming();
|
||||
TestSystemMemoryPlayoutStats();
|
||||
|
||||
if (gFailures != 0)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
namespace
|
||||
{
|
||||
int gFailures = 0;
|
||||
int gReleasedFrames = 0;
|
||||
|
||||
void Expect(bool condition, const char* message)
|
||||
{
|
||||
@@ -23,6 +24,22 @@ RenderOutputFrame MakeFrame(uint64_t index)
|
||||
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;
|
||||
@@ -58,6 +75,25 @@ void TestBoundedQueueDropsOldestFrame()
|
||||
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 TestUnderrunIsCounted()
|
||||
{
|
||||
RenderOutputQueue queue;
|
||||
@@ -90,14 +126,53 @@ void TestConfigureShrinksDepthToNewCapacity()
|
||||
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();
|
||||
TestUnderrunIsCounted();
|
||||
TestConfigureShrinksDepthToNewCapacity();
|
||||
TestConfigureReleasesTrimmedFrames();
|
||||
TestClearReleasesQueuedFrames();
|
||||
|
||||
if (gFailures != 0)
|
||||
{
|
||||
|
||||
170
tests/SystemOutputFramePoolTests.cpp
Normal file
170
tests/SystemOutputFramePoolTests.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#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.acquiredCount == 2, "acquired slots are counted");
|
||||
Expect(metrics.acquireMissCount == 1, "capacity miss is counted");
|
||||
}
|
||||
|
||||
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 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();
|
||||
TestReadySlotsAreConsumedFifo();
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user