#include "SystemOutputFramePool.h" #include #include 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(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; }