357 lines
14 KiB
C++
357 lines
14 KiB
C++
#include "SystemFrameExchange.h"
|
|
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
|
|
namespace
|
|
{
|
|
int gFailures = 0;
|
|
|
|
void Expect(bool condition, const char* message)
|
|
{
|
|
if (condition)
|
|
return;
|
|
|
|
std::cerr << "FAIL: " << message << "\n";
|
|
++gFailures;
|
|
}
|
|
|
|
SystemFrameExchangeConfig MakeConfig(std::size_t capacity = 2)
|
|
{
|
|
SystemFrameExchangeConfig config;
|
|
config.width = 4;
|
|
config.height = 3;
|
|
config.pixelFormat = VideoIOPixelFormat::Bgra8;
|
|
config.capacity = capacity;
|
|
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()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(1));
|
|
|
|
SystemFrame frame;
|
|
Expect(exchange.AcquireForRender(frame), "frame can be acquired for render");
|
|
Expect(frame.bytes != nullptr, "acquired frame has storage");
|
|
Expect(frame.width == 4, "frame width is configured");
|
|
Expect(frame.height == 3, "frame height is configured");
|
|
Expect(frame.rowBytes == 16, "BGRA8 row bytes are inferred");
|
|
Expect(frame.pixelFormat == VideoIOPixelFormat::Bgra8, "pixel format is configured");
|
|
|
|
frame.frameIndex = 42;
|
|
Expect(exchange.PublishCompleted(frame), "rendering frame can be completed");
|
|
Expect(exchange.WaitForCompletedDepth(1, std::chrono::milliseconds(0)), "completed depth can be observed");
|
|
|
|
SystemFrame scheduled;
|
|
Expect(exchange.ConsumeCompletedForSchedule(scheduled), "completed frame can be scheduled");
|
|
Expect(scheduled.index == frame.index, "scheduled frame uses completed slot");
|
|
Expect(scheduled.generation == frame.generation, "scheduled frame keeps generation");
|
|
Expect(scheduled.frameIndex == 42, "frame index is preserved");
|
|
|
|
Expect(exchange.ReleaseScheduledByBytes(scheduled.bytes), "scheduled frame can be released by bytes");
|
|
|
|
SystemFrameExchangeMetrics metrics = exchange.Metrics();
|
|
Expect(metrics.freeCount == 1, "released slot returns to free");
|
|
Expect(metrics.completedFrames == 1, "completed metric is counted");
|
|
Expect(metrics.scheduledFrames == 1, "scheduled metric is counted");
|
|
}
|
|
|
|
void TestAcquirePreservesCompletedFrames()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(2));
|
|
|
|
SystemFrame first;
|
|
SystemFrame second;
|
|
SystemFrame third;
|
|
Expect(exchange.AcquireForRender(first), "first preserving frame can be acquired");
|
|
first.frameIndex = 1;
|
|
Expect(exchange.PublishCompleted(first), "first preserving frame can be completed");
|
|
Expect(exchange.AcquireForRender(second), "second preserving frame can be acquired");
|
|
second.frameIndex = 2;
|
|
Expect(exchange.PublishCompleted(second), "second preserving frame can be completed");
|
|
|
|
Expect(!exchange.AcquireForRender(third), "render acquire refuses to drop completed frames");
|
|
|
|
SystemFrame scheduled;
|
|
Expect(exchange.ConsumeCompletedForSchedule(scheduled), "oldest completed frame survives acquire miss");
|
|
Expect(scheduled.frameIndex == 1, "preserving acquire keeps FIFO output continuity");
|
|
|
|
SystemFrameExchangeMetrics metrics = exchange.Metrics();
|
|
Expect(metrics.completedDrops == 0, "preserving acquire does not count completed drops");
|
|
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()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(1));
|
|
|
|
SystemFrame frame;
|
|
Expect(exchange.AcquireForRender(frame), "single frame can be acquired");
|
|
Expect(exchange.PublishCompleted(frame), "single frame can be completed");
|
|
|
|
SystemFrame scheduled;
|
|
Expect(exchange.ConsumeCompletedForSchedule(scheduled), "single frame can be scheduled");
|
|
|
|
SystemFrame extra;
|
|
Expect(!exchange.AcquireForRender(extra), "scheduled frame is not dropped for render acquire");
|
|
|
|
SystemFrameExchangeMetrics metrics = exchange.Metrics();
|
|
Expect(metrics.acquireMisses == 1, "blocked acquire miss is counted");
|
|
Expect(metrics.completedDrops == 0, "scheduled frame is not counted as a completed drop");
|
|
}
|
|
|
|
void TestGenerationValidationRejectsStaleFrames()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(1));
|
|
|
|
SystemFrame first;
|
|
Expect(exchange.AcquireForRender(first), "frame can be acquired");
|
|
Expect(exchange.PublishCompleted(first), "frame can be completed");
|
|
|
|
SystemFrame scheduled;
|
|
Expect(exchange.ConsumeCompletedForSchedule(scheduled), "frame can be scheduled");
|
|
Expect(exchange.ReleaseScheduledByBytes(scheduled.bytes), "frame can be released");
|
|
|
|
SystemFrame second;
|
|
Expect(exchange.AcquireForRender(second), "slot can be reacquired");
|
|
Expect(second.index == first.index, "same slot is reused");
|
|
Expect(second.generation != first.generation, "reacquire invalidates stale generation");
|
|
Expect(!exchange.PublishCompleted(first), "stale frame cannot be completed");
|
|
}
|
|
|
|
void TestPixelFormatAwareSizing()
|
|
{
|
|
SystemFrameExchangeConfig config;
|
|
config.width = 7;
|
|
config.height = 2;
|
|
config.pixelFormat = VideoIOPixelFormat::V210;
|
|
config.capacity = 1;
|
|
|
|
SystemFrameExchange exchange(config);
|
|
SystemFrame frame;
|
|
Expect(exchange.AcquireForRender(frame), "v210 frame can be acquired");
|
|
Expect(frame.pixelFormat == VideoIOPixelFormat::V210, "v210 pixel format is preserved");
|
|
Expect(frame.rowBytes == static_cast<long>(VideoIORowBytes(VideoIOPixelFormat::V210, 7)), "v210 row bytes are inferred");
|
|
|
|
config.pixelFormat = VideoIOPixelFormat::Uyvy8;
|
|
config.rowBytes = 64;
|
|
exchange.Configure(config);
|
|
Expect(exchange.AcquireForRender(frame), "explicit row-byte frame can be acquired");
|
|
Expect(frame.pixelFormat == VideoIOPixelFormat::Uyvy8, "reconfigured pixel format is preserved");
|
|
Expect(frame.rowBytes == 64, "explicit row bytes are preserved");
|
|
}
|
|
|
|
void TestCompletedPollMissIsCounted()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(1));
|
|
SystemFrame frame;
|
|
Expect(!exchange.ConsumeCompletedForSchedule(frame), "empty completed queue cannot be consumed");
|
|
|
|
SystemFrameExchangeMetrics metrics = exchange.Metrics();
|
|
Expect(metrics.completedPollMisses == 1, "completed poll miss is counted");
|
|
}
|
|
|
|
void TestLatestPublishedFrameCanBePreviewedWithoutConsuming()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(2));
|
|
|
|
SystemFrame frame;
|
|
Expect(exchange.AcquireForRender(frame), "copy snapshot frame can be acquired");
|
|
frame.frameIndex = 77;
|
|
const unsigned char marker = 0x42;
|
|
std::memset(frame.bytes, marker, static_cast<std::size_t>(frame.rowBytes) * frame.height);
|
|
Expect(exchange.PublishCompleted(frame), "copy snapshot frame can be completed");
|
|
|
|
SystemFrame preview;
|
|
Expect(exchange.TryAcquireLatestForPreview(preview), "latest published frame can be acquired for preview");
|
|
Expect(preview.frameIndex == 77, "preview frame keeps frame index");
|
|
Expect(preview.width == 4 && preview.height == 3, "preview frame keeps frame dimensions");
|
|
Expect(preview.bytes != nullptr && static_cast<unsigned char*>(preview.bytes)[0] == marker, "preview frame points at frame bytes");
|
|
Expect(exchange.ReleasePreviewFrame(preview), "preview frame can be released");
|
|
|
|
SystemFrame scheduled;
|
|
Expect(exchange.ConsumeCompletedForSchedule(scheduled), "previewing frame does not consume completed frame");
|
|
}
|
|
|
|
void TestLatestPublishedFrameCanPreviewScheduledFrame()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(1));
|
|
|
|
SystemFrame frame;
|
|
Expect(exchange.AcquireForRender(frame), "scheduled snapshot frame can be acquired");
|
|
frame.frameIndex = 88;
|
|
Expect(exchange.PublishCompleted(frame), "scheduled snapshot frame can be completed");
|
|
|
|
SystemFrame scheduled;
|
|
Expect(exchange.ConsumeCompletedForSchedule(scheduled), "snapshot test frame can be scheduled");
|
|
|
|
SystemFrame preview;
|
|
Expect(exchange.TryAcquireLatestForPreview(preview), "latest scheduled frame can be acquired for preview");
|
|
Expect(preview.frameIndex == 88, "scheduled preview keeps frame index");
|
|
Expect(exchange.ReleasePreviewFrame(preview), "scheduled preview frame can be released");
|
|
}
|
|
|
|
void TestPreviewFramePinsReleasedSlotUntilPreviewRelease()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(1));
|
|
|
|
SystemFrame frame;
|
|
Expect(exchange.AcquireForRender(frame), "preview pin frame can be acquired");
|
|
frame.frameIndex = 99;
|
|
Expect(exchange.PublishCompleted(frame), "preview pin frame can be completed");
|
|
|
|
SystemFrame preview;
|
|
Expect(exchange.TryAcquireLatestForPreview(preview), "preview can acquire frame before schedule");
|
|
|
|
SystemFrame scheduled;
|
|
Expect(exchange.ConsumeCompletedForSchedule(scheduled), "previewed frame can still be scheduled");
|
|
Expect(exchange.ReleaseScheduledByBytes(scheduled.bytes), "scheduled frame can be released while preview holds it");
|
|
|
|
SystemFrame blocked;
|
|
Expect(!exchange.AcquireForRender(blocked), "preview reader prevents immediate slot reuse");
|
|
Expect(exchange.ReleasePreviewFrame(preview), "preview pin can be released");
|
|
Expect(exchange.AcquireForRender(blocked), "slot can be reused after preview release");
|
|
}
|
|
|
|
void TestMultiplePreviewReadersPinReleasedSlotUntilAllRelease()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(1));
|
|
|
|
SystemFrame frame;
|
|
Expect(exchange.AcquireForRender(frame), "multi-preview frame can be acquired");
|
|
frame.frameIndex = 100;
|
|
Expect(exchange.PublishCompleted(frame), "multi-preview frame can be completed");
|
|
|
|
SystemFrame firstPreview;
|
|
SystemFrame secondPreview;
|
|
Expect(exchange.TryAcquireLatestForPreview(firstPreview), "first preview reader can acquire");
|
|
Expect(exchange.TryAcquireLatestForPreview(secondPreview), "second preview reader can acquire");
|
|
|
|
SystemFrame scheduled;
|
|
Expect(exchange.ConsumeCompletedForSchedule(scheduled), "multi-preview frame can be scheduled");
|
|
Expect(exchange.ReleaseScheduledByBytes(scheduled.bytes), "multi-preview scheduled frame can be released");
|
|
|
|
SystemFrame blocked;
|
|
Expect(!exchange.AcquireForRender(blocked), "slot remains pinned while two preview readers exist");
|
|
Expect(exchange.ReleasePreviewFrame(firstPreview), "first preview reader can release");
|
|
Expect(!exchange.AcquireForRender(blocked), "slot remains pinned until last preview reader releases");
|
|
Expect(exchange.ReleasePreviewFrame(secondPreview), "second preview reader can release");
|
|
Expect(exchange.AcquireForRender(blocked), "slot is reusable after all preview readers release");
|
|
}
|
|
|
|
void TestInvalidPreviewReleaseIsRejected()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(1));
|
|
|
|
SystemFrame invalid;
|
|
Expect(!exchange.ReleasePreviewFrame(invalid), "empty preview frame release is rejected");
|
|
|
|
SystemFrame frame;
|
|
Expect(exchange.AcquireForRender(frame), "invalid release source frame can be acquired");
|
|
frame.frameIndex = 101;
|
|
Expect(exchange.PublishCompleted(frame), "invalid release source frame can be completed");
|
|
|
|
SystemFrame preview;
|
|
Expect(exchange.TryAcquireLatestForPreview(preview), "preview frame can be acquired for invalid release test");
|
|
Expect(exchange.ReleasePreviewFrame(preview), "valid preview release succeeds");
|
|
Expect(!exchange.ReleasePreviewFrame(preview), "double preview release is rejected");
|
|
}
|
|
|
|
void TestStalePreviewReleaseIsRejectedAfterReconfigure()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(1));
|
|
|
|
SystemFrame frame;
|
|
Expect(exchange.AcquireForRender(frame), "stale preview source frame can be acquired");
|
|
frame.frameIndex = 102;
|
|
Expect(exchange.PublishCompleted(frame), "stale preview source frame can be completed");
|
|
|
|
SystemFrame preview;
|
|
Expect(exchange.TryAcquireLatestForPreview(preview), "preview frame can be acquired before reconfigure");
|
|
exchange.Configure(MakeConfig(1));
|
|
Expect(!exchange.ReleasePreviewFrame(preview), "preview release after reconfigure is rejected as stale");
|
|
}
|
|
|
|
void TestStableCompletedDepthCanBeObserved()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(1));
|
|
SystemFrame frame;
|
|
Expect(exchange.AcquireForRender(frame), "stable-depth frame can be acquired");
|
|
Expect(exchange.PublishCompleted(frame), "stable-depth frame can be completed");
|
|
|
|
Expect(
|
|
exchange.WaitForStableCompletedDepth(1, std::chrono::milliseconds(1), std::chrono::milliseconds(50)),
|
|
"stable completed depth can be observed");
|
|
}
|
|
|
|
void TestStableCompletedDepthTimesOut()
|
|
{
|
|
SystemFrameExchange exchange(MakeConfig(1));
|
|
Expect(
|
|
!exchange.WaitForStableCompletedDepth(1, std::chrono::milliseconds(1), std::chrono::milliseconds(1)),
|
|
"missing stable completed depth times out");
|
|
}
|
|
}
|
|
|
|
int main()
|
|
{
|
|
TestAcquirePublishesAndSchedules();
|
|
TestAcquirePreservesCompletedFrames();
|
|
TestCompletedReserveIsBoundedFifo();
|
|
TestScheduledFramesAreNotDropped();
|
|
TestGenerationValidationRejectsStaleFrames();
|
|
TestPixelFormatAwareSizing();
|
|
TestCompletedPollMissIsCounted();
|
|
TestLatestPublishedFrameCanBePreviewedWithoutConsuming();
|
|
TestLatestPublishedFrameCanPreviewScheduledFrame();
|
|
TestPreviewFramePinsReleasedSlotUntilPreviewRelease();
|
|
TestMultiplePreviewReadersPinReleasedSlotUntilAllRelease();
|
|
TestInvalidPreviewReleaseIsRejected();
|
|
TestStalePreviewReleaseIsRejectedAfterReconfigure();
|
|
TestStableCompletedDepthCanBeObserved();
|
|
TestStableCompletedDepthTimesOut();
|
|
|
|
if (gFailures != 0)
|
|
{
|
|
std::cerr << gFailures << " frame exchange test failure(s).\n";
|
|
return 1;
|
|
}
|
|
|
|
std::cout << "RenderCadenceCompositor frame exchange tests passed.\n";
|
|
return 0;
|
|
}
|