Files
video-shader-toys/tests/VideoOutputThreadTests.cpp
2026-05-22 14:58:42 +10:00

149 lines
3.9 KiB
C++

#include "VideoOutputThread.h"
#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
namespace
{
int gFailures = 0;
void Expect(bool condition, const char* message)
{
if (condition)
return;
std::cerr << "FAIL: " << message << "\n";
++gFailures;
}
struct FakeExchangeMetrics
{
std::size_t scheduledCount = 0;
};
class FakeExchange
{
public:
FakeExchange()
{
mFrame.bytes = mBytes;
mFrame.rowBytes = 8;
mFrame.width = 2;
mFrame.height = 2;
mFrame.pixelFormat = VideoIOPixelFormat::Bgra8;
}
FakeExchangeMetrics Metrics() const
{
FakeExchangeMetrics metrics;
metrics.scheduledCount = mScheduled.load();
return metrics;
}
bool ConsumeCompletedForSchedule(SystemFrame& frame)
{
bool expected = true;
if (!mHasFrame.compare_exchange_strong(expected, false))
return false;
frame = mFrame;
mScheduled.fetch_add(1);
return true;
}
void ReleaseScheduledByBytes(void*)
{
mReleased.fetch_add(1);
}
std::atomic<std::size_t> mScheduled{ 0 };
std::atomic<std::size_t> mReleased{ 0 };
private:
std::atomic<bool> mHasFrame{ true };
unsigned char mBytes[16] = {};
SystemFrame mFrame;
};
class FakeOutputEdge final : public RenderCadenceCompositor::IVideoOutputEdge
{
public:
bool StartScheduledPlayback(std::string&) override { return true; }
bool ScheduleFrame(const VideoIOOutputFrame& frame) override
{
mLastWidth = frame.width;
mScheduled.fetch_add(1);
return mScheduleResult;
}
void Stop() override {}
void ReleaseResources() override {}
const VideoIOState& State() const override { return mState; }
RenderCadenceCompositor::VideoOutputEdgeMetrics Metrics() const override { return mMetrics; }
bool mScheduleResult = true;
std::atomic<std::size_t> mScheduled{ 0 };
unsigned mLastWidth = 0;
private:
VideoIOState mState;
RenderCadenceCompositor::VideoOutputEdgeMetrics mMetrics;
};
void TestSchedulesCompletedFrameThroughGenericEdge()
{
FakeExchange exchange;
FakeOutputEdge output;
RenderCadenceCompositor::VideoOutputThreadConfig config;
config.targetBufferedFrames = 1;
config.idleSleep = std::chrono::milliseconds(1);
RenderCadenceCompositor::VideoOutputThread<FakeExchange> thread(output, exchange, config);
Expect(thread.Start(), "video output thread starts");
std::this_thread::sleep_for(std::chrono::milliseconds(20));
thread.Stop();
const auto metrics = thread.Metrics();
Expect(output.mScheduled.load() == 1, "generic output edge receives one scheduled frame");
Expect(output.mLastWidth == 2, "scheduled frame data comes from exchange");
Expect(metrics.scheduledFrames == 1, "thread scheduled frame metric increments");
Expect(metrics.scheduleFailures == 0, "no schedule failure is reported");
Expect(exchange.mReleased.load() == 0, "successful schedule keeps frame scheduled");
}
void TestReleasesFrameWhenGenericEdgeRejectsSchedule()
{
FakeExchange exchange;
FakeOutputEdge output;
output.mScheduleResult = false;
RenderCadenceCompositor::VideoOutputThreadConfig config;
config.targetBufferedFrames = 1;
config.idleSleep = std::chrono::milliseconds(1);
RenderCadenceCompositor::VideoOutputThread<FakeExchange> thread(output, exchange, config);
Expect(thread.Start(), "video output thread starts for failure path");
std::this_thread::sleep_for(std::chrono::milliseconds(20));
thread.Stop();
const auto metrics = thread.Metrics();
Expect(output.mScheduled.load() == 1, "generic output edge receives failed schedule attempt");
Expect(metrics.scheduledFrames == 0, "failed schedule does not increment scheduled frames");
Expect(metrics.scheduleFailures == 1, "schedule failure metric increments");
Expect(exchange.mReleased.load() == 1, "failed schedule releases the frame back to exchange");
}
}
int main()
{
TestSchedulesCompletedFrameThroughGenericEdge();
TestReleasesFrameWhenGenericEdgeRejectsSchedule();
if (gFailures != 0)
{
std::cerr << gFailures << " VideoOutputThread test failure(s).\n";
return 1;
}
std::cout << "VideoOutputThread tests passed.\n";
return 0;
}