input testing
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 3m2s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
Aiden
2026-05-12 20:06:23 +10:00
parent 2c5e925b97
commit ce28904891
19 changed files with 911 additions and 198 deletions

View File

@@ -0,0 +1,408 @@
#include "DeckLinkInput.h"
#include "DeckLinkVideoIOFormat.h"
#include "../logging/Logger.h"
#include <algorithm>
#include <chrono>
#include <new>
namespace RenderCadenceCompositor
{
namespace
{
bool FindInputDisplayMode(IDeckLinkInput* input, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode)
{
if (input == nullptr || foundMode == nullptr)
return false;
*foundMode = nullptr;
CComPtr<IDeckLinkDisplayModeIterator> iterator;
if (input->GetDisplayModeIterator(&iterator) != S_OK)
return false;
return FindDeckLinkDisplayMode(iterator, targetMode, foundMode);
}
unsigned char ClampToByte(double value)
{
if (value <= 0.0)
return 0;
if (value >= 255.0)
return 255;
return static_cast<unsigned char>(value + 0.5);
}
void StoreRec709UyvyAsBgra(unsigned char yByte, unsigned char uByte, unsigned char vByte, unsigned char* destination)
{
const double y = (static_cast<double>(yByte) - 16.0) / 219.0;
const double cb = (static_cast<double>(uByte) - 16.0) / 224.0 - 0.5;
const double cr = (static_cast<double>(vByte) - 16.0) / 224.0 - 0.5;
const double red = y + 1.5748 * cr;
const double green = y - 0.1873 * cb - 0.4681 * cr;
const double blue = y + 1.8556 * cb;
destination[0] = ClampToByte(blue * 255.0);
destination[1] = ClampToByte(green * 255.0);
destination[2] = ClampToByte(red * 255.0);
destination[3] = 255;
}
}
DeckLinkInputCallback::DeckLinkInputCallback(DeckLinkInput& owner) :
mOwner(owner)
{
}
HRESULT STDMETHODCALLTYPE DeckLinkInputCallback::QueryInterface(REFIID iid, LPVOID* ppv)
{
if (ppv == nullptr)
return E_POINTER;
if (iid == IID_IUnknown || iid == IID_IDeckLinkInputCallback)
{
*ppv = static_cast<IDeckLinkInputCallback*>(this);
AddRef();
return S_OK;
}
*ppv = nullptr;
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE DeckLinkInputCallback::AddRef()
{
return ++mRefCount;
}
ULONG STDMETHODCALLTYPE DeckLinkInputCallback::Release()
{
const ULONG refCount = --mRefCount;
if (refCount == 0)
delete this;
return refCount;
}
HRESULT STDMETHODCALLTYPE DeckLinkInputCallback::VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket*)
{
if (videoFrame != nullptr)
mOwner.HandleFrameArrived(videoFrame);
return S_OK;
}
HRESULT STDMETHODCALLTYPE DeckLinkInputCallback::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents, IDeckLinkDisplayMode*, BMDDetectedVideoInputFormatFlags)
{
mOwner.HandleFormatChanged();
return S_OK;
}
DeckLinkInput::DeckLinkInput(InputFrameMailbox& mailbox) :
mMailbox(mailbox)
{
}
DeckLinkInput::~DeckLinkInput()
{
ReleaseResources();
}
bool DeckLinkInput::Initialize(const DeckLinkInputConfig& config, std::string& error)
{
ReleaseResources();
mConfig = config;
Log("decklink-input", "Initializing DeckLink input for " + config.videoFormat.displayName + ".");
if (!DiscoverInput(config, error))
return false;
if (mInput->EnableVideoInput(config.videoFormat.displayMode, mCapturePixelFormat, bmdVideoInputFlagDefault) != S_OK)
{
error = "DeckLink input setup failed while enabling " +
std::string(mCapturePixelFormat == bmdFormat8BitBGRA ? "BGRA8" : "UYVY8") +
" input for " + config.videoFormat.displayName + ".";
ReleaseResources();
return false;
}
Log(
"decklink-input",
std::string("DeckLink input enabled in ") + (mCapturePixelFormat == bmdFormat8BitBGRA ? "BGRA8" : "UYVY8-to-BGRA8 conversion") + " mode.");
mCallback.Attach(new (std::nothrow) DeckLinkInputCallback(*this));
if (mCallback == nullptr)
{
error = "DeckLink input setup failed while creating the capture callback.";
ReleaseResources();
return false;
}
if (mInput->SetCallback(mCallback) != S_OK)
{
error = "DeckLink input setup failed while installing the capture callback.";
ReleaseResources();
return false;
}
Log("decklink-input", "DeckLink input callback installed.");
return true;
}
bool DeckLinkInput::Start(std::string& error)
{
if (mInput == nullptr)
{
error = "DeckLink input has not been initialized.";
return false;
}
if (mRunning.load(std::memory_order_acquire))
return true;
if (mInput->StartStreams() != S_OK)
{
error = "DeckLink input stream failed to start.";
return false;
}
mRunning.store(true, std::memory_order_release);
Log("decklink-input", "DeckLink input stream started.");
return true;
}
void DeckLinkInput::Stop()
{
if (mInput != nullptr && mRunning.exchange(false, std::memory_order_acq_rel))
{
mInput->StopStreams();
Log("decklink-input", "DeckLink input stream stopped.");
}
}
void DeckLinkInput::ReleaseResources()
{
Stop();
if (mInput != nullptr)
{
mInput->SetCallback(nullptr);
mInput->DisableVideoInput();
}
mCallback.Release();
mInput.Release();
}
DeckLinkInputMetrics DeckLinkInput::Metrics() const
{
DeckLinkInputMetrics metrics;
metrics.capturedFrames = mCapturedFrames.load(std::memory_order_relaxed);
metrics.noInputSourceFrames = mNoInputSourceFrames.load(std::memory_order_relaxed);
metrics.unsupportedFrames = mUnsupportedFrames.load(std::memory_order_relaxed);
metrics.submitMisses = mSubmitMisses.load(std::memory_order_relaxed);
metrics.convertMilliseconds = mConvertMilliseconds.load(std::memory_order_relaxed);
metrics.submitMilliseconds = mSubmitMilliseconds.load(std::memory_order_relaxed);
metrics.captureFormat = CaptureFormatName();
return metrics;
}
void DeckLinkInput::HandleFrameArrived(IDeckLinkVideoInputFrame* inputFrame)
{
if (inputFrame == nullptr)
return;
if ((inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource)
{
mNoInputSourceFrames.fetch_add(1, std::memory_order_relaxed);
bool expected = false;
if (mLoggedNoInputSource.compare_exchange_strong(expected, true, std::memory_order_relaxed))
LogWarning("decklink-input", "DeckLink input callback reports no input source.");
return;
}
if (inputFrame->GetWidth() != static_cast<long>(mMailbox.Config().width) ||
inputFrame->GetHeight() != static_cast<long>(mMailbox.Config().height))
{
mUnsupportedFrames.fetch_add(1, std::memory_order_relaxed);
bool expected = false;
if (mLoggedUnsupportedFrame.compare_exchange_strong(expected, true, std::memory_order_relaxed))
LogWarning("decklink-input", "DeckLink input frame dimensions do not match the configured mailbox.");
return;
}
CComPtr<IDeckLinkVideoBuffer> inputFrameBuffer;
if (inputFrame->QueryInterface(IID_IDeckLinkVideoBuffer, reinterpret_cast<void**>(&inputFrameBuffer)) != S_OK)
{
mUnsupportedFrames.fetch_add(1, std::memory_order_relaxed);
bool expected = false;
if (mLoggedUnsupportedFrame.compare_exchange_strong(expected, true, std::memory_order_relaxed))
LogWarning("decklink-input", "DeckLink input frame does not expose IDeckLinkVideoBuffer.");
return;
}
if (inputFrameBuffer->StartAccess(bmdBufferAccessRead) != S_OK)
{
mUnsupportedFrames.fetch_add(1, std::memory_order_relaxed);
bool expected = false;
if (mLoggedUnsupportedFrame.compare_exchange_strong(expected, true, std::memory_order_relaxed))
LogWarning("decklink-input", "DeckLink input frame buffer could not be opened for read access.");
return;
}
void* bytes = nullptr;
inputFrameBuffer->GetBytes(&bytes);
bool submitted = false;
if (mCapturePixelFormat == bmdFormat8BitBGRA)
submitted = SubmitBgra8Frame(inputFrame, bytes);
else if (mCapturePixelFormat == bmdFormat8BitYUV)
submitted = SubmitUyvy8Frame(inputFrame, bytes);
else
mUnsupportedFrames.fetch_add(1, std::memory_order_relaxed);
if (!submitted)
{
mSubmitMisses.fetch_add(1, std::memory_order_relaxed);
bool expected = false;
if (mLoggedSubmitMiss.compare_exchange_strong(expected, true, std::memory_order_relaxed))
LogWarning("decklink-input", "DeckLink input frame could not be submitted to InputFrameMailbox.");
}
mCapturedFrames.fetch_add(1, std::memory_order_relaxed);
bool expectedFirstFrame = false;
if (mLoggedFirstFrame.compare_exchange_strong(expectedFirstFrame, true, std::memory_order_relaxed))
{
Log(
"decklink-input",
std::string("First DeckLink ") + (mCapturePixelFormat == bmdFormat8BitBGRA ? "BGRA8" : "UYVY8 converted BGRA8") + " input frame submitted to InputFrameMailbox.");
}
inputFrameBuffer->EndAccess(bmdBufferAccessRead);
}
void DeckLinkInput::HandleFormatChanged()
{
mUnsupportedFrames.fetch_add(1, std::memory_order_relaxed);
LogWarning("decklink-input", "DeckLink input format changed; input edge does not auto-switch formats yet.");
}
bool DeckLinkInput::DiscoverInput(const DeckLinkInputConfig& config, std::string& error)
{
CComPtr<IDeckLinkIterator> iterator;
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast<void**>(&iterator));
if (FAILED(result))
{
error = "DeckLink input discovery failed. Blackmagic DeckLink drivers may not be installed.";
return false;
}
CComPtr<IDeckLink> deckLink;
while (iterator->Next(&deckLink) == S_OK)
{
CComPtr<IDeckLinkInput> candidateInput;
if (deckLink->QueryInterface(IID_IDeckLinkInput, reinterpret_cast<void**>(&candidateInput)) == S_OK && candidateInput != nullptr)
{
CComPtr<IDeckLinkDisplayMode> displayMode;
if (FindInputDisplayMode(candidateInput, config.videoFormat.displayMode, &displayMode) &&
SupportsInputFormat(candidateInput, config.videoFormat.displayMode, bmdFormat8BitBGRA))
{
mInput = candidateInput;
mCapturePixelFormat = bmdFormat8BitBGRA;
Log("decklink-input", "DeckLink input device selected for BGRA8 capture.");
return true;
}
if (displayMode != nullptr &&
SupportsInputFormat(candidateInput, config.videoFormat.displayMode, bmdFormat8BitYUV))
{
mInput = candidateInput;
mCapturePixelFormat = bmdFormat8BitYUV;
Log("decklink-input", "DeckLink input device selected for UYVY8 capture with CPU BGRA8 conversion.");
return true;
}
}
deckLink.Release();
}
error = "No DeckLink input device supports BGRA8 or UYVY8 capture for " + config.videoFormat.displayName + ".";
return false;
}
bool DeckLinkInput::SupportsInputFormat(IDeckLinkInput* input, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat) const
{
if (input == nullptr)
return false;
BOOL supported = FALSE;
BMDDisplayMode actualMode = bmdModeUnknown;
const HRESULT result = input->DoesSupportVideoMode(
bmdVideoConnectionUnspecified,
displayMode,
pixelFormat,
bmdNoVideoInputConversion,
bmdSupportedVideoModeDefault,
&actualMode,
&supported);
return result == S_OK && supported != FALSE;
}
bool DeckLinkInput::SubmitBgra8Frame(IDeckLinkVideoInputFrame* inputFrame, const void* bytes)
{
if (inputFrame == nullptr || bytes == nullptr)
return false;
const uint64_t frameIndex = mCapturedFrames.load(std::memory_order_relaxed);
mConvertMilliseconds.store(0.0, std::memory_order_relaxed);
const auto submitStart = std::chrono::steady_clock::now();
const bool submitted = mMailbox.SubmitFrame(bytes, static_cast<unsigned>(inputFrame->GetRowBytes()), frameIndex);
const auto submitEnd = std::chrono::steady_clock::now();
mSubmitMilliseconds.store(
std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(submitEnd - submitStart).count(),
std::memory_order_relaxed);
return submitted;
}
bool DeckLinkInput::SubmitUyvy8Frame(IDeckLinkVideoInputFrame* inputFrame, const void* bytes)
{
if (inputFrame == nullptr || bytes == nullptr)
return false;
const unsigned width = static_cast<unsigned>(inputFrame->GetWidth());
const unsigned height = static_cast<unsigned>(inputFrame->GetHeight());
const long sourceRowBytes = inputFrame->GetRowBytes();
if (width == 0 || height == 0 || sourceRowBytes < static_cast<long>(width * 2u))
return false;
const unsigned destinationRowBytes = VideoIORowBytes(VideoIOPixelFormat::Bgra8, width);
const auto convertStart = std::chrono::steady_clock::now();
mConversionBuffer.resize(static_cast<std::size_t>(destinationRowBytes) * static_cast<std::size_t>(height));
const unsigned char* sourceBytes = static_cast<const unsigned char*>(bytes);
for (unsigned y = 0; y < height; ++y)
{
const unsigned char* sourceRow = sourceBytes + static_cast<std::size_t>(y) * static_cast<std::size_t>(sourceRowBytes);
unsigned char* destinationRow = mConversionBuffer.data() + static_cast<std::size_t>(y) * static_cast<std::size_t>(destinationRowBytes);
for (unsigned x = 0; x < width; x += 2)
{
const unsigned pairOffset = x * 2u;
const unsigned char u = sourceRow[pairOffset + 0];
const unsigned char y0 = sourceRow[pairOffset + 1];
const unsigned char v = sourceRow[pairOffset + 2];
const unsigned char y1 = sourceRow[pairOffset + 3];
StoreRec709UyvyAsBgra(y0, u, v, destinationRow + static_cast<std::size_t>(x) * 4u);
if (x + 1u < width)
StoreRec709UyvyAsBgra(y1, u, v, destinationRow + static_cast<std::size_t>(x + 1u) * 4u);
}
}
const auto convertEnd = std::chrono::steady_clock::now();
mConvertMilliseconds.store(
std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(convertEnd - convertStart).count(),
std::memory_order_relaxed);
const uint64_t frameIndex = mCapturedFrames.load(std::memory_order_relaxed);
const auto submitStart = std::chrono::steady_clock::now();
const bool submitted = mMailbox.SubmitFrame(mConversionBuffer.data(), destinationRowBytes, frameIndex);
const auto submitEnd = std::chrono::steady_clock::now();
mSubmitMilliseconds.store(
std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(submitEnd - submitStart).count(),
std::memory_order_relaxed);
return submitted;
}
const char* DeckLinkInput::CaptureFormatName() const
{
if (mInput == nullptr)
return "none";
if (mCapturePixelFormat == bmdFormat8BitBGRA)
return "BGRA8";
if (mCapturePixelFormat == bmdFormat8BitYUV)
return "UYVY8";
return "unsupported";
}
}

View File

@@ -0,0 +1,96 @@
#pragma once
#include "../frames/InputFrameMailbox.h"
#include "DeckLinkAPI_h.h"
#include "DeckLinkDisplayMode.h"
#include <atlbase.h>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <string>
#include <vector>
namespace RenderCadenceCompositor
{
struct DeckLinkInputConfig
{
VideoFormat videoFormat;
};
struct DeckLinkInputMetrics
{
uint64_t capturedFrames = 0;
uint64_t noInputSourceFrames = 0;
uint64_t unsupportedFrames = 0;
uint64_t submitMisses = 0;
double convertMilliseconds = 0.0;
double submitMilliseconds = 0.0;
const char* captureFormat = "none";
};
class DeckLinkInput;
class DeckLinkInputCallback final : public IDeckLinkInputCallback
{
public:
explicit DeckLinkInputCallback(DeckLinkInput& owner);
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv) override;
ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override;
HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioPacket) override;
HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode* newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags) override;
private:
DeckLinkInput& mOwner;
std::atomic<ULONG> mRefCount{ 1 };
};
class DeckLinkInput
{
public:
DeckLinkInput(InputFrameMailbox& mailbox);
DeckLinkInput(const DeckLinkInput&) = delete;
DeckLinkInput& operator=(const DeckLinkInput&) = delete;
~DeckLinkInput();
bool Initialize(const DeckLinkInputConfig& config, std::string& error);
bool Start(std::string& error);
void Stop();
void ReleaseResources();
bool IsInitialized() const { return mInput != nullptr; }
bool IsRunning() const { return mRunning.load(std::memory_order_acquire); }
DeckLinkInputMetrics Metrics() const;
void HandleFrameArrived(IDeckLinkVideoInputFrame* inputFrame);
void HandleFormatChanged();
private:
bool DiscoverInput(const DeckLinkInputConfig& config, std::string& error);
bool SupportsInputFormat(IDeckLinkInput* input, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat) const;
bool SubmitBgra8Frame(IDeckLinkVideoInputFrame* inputFrame, const void* bytes);
bool SubmitUyvy8Frame(IDeckLinkVideoInputFrame* inputFrame, const void* bytes);
const char* CaptureFormatName() const;
InputFrameMailbox& mMailbox;
DeckLinkInputConfig mConfig;
BMDPixelFormat mCapturePixelFormat = bmdFormat8BitBGRA;
CComPtr<IDeckLinkInput> mInput;
CComPtr<DeckLinkInputCallback> mCallback;
std::vector<unsigned char> mConversionBuffer;
std::atomic<bool> mRunning{ false };
std::atomic<uint64_t> mCapturedFrames{ 0 };
std::atomic<uint64_t> mNoInputSourceFrames{ 0 };
std::atomic<uint64_t> mUnsupportedFrames{ 0 };
std::atomic<uint64_t> mSubmitMisses{ 0 };
std::atomic<double> mConvertMilliseconds{ 0.0 };
std::atomic<double> mSubmitMilliseconds{ 0.0 };
std::atomic<bool> mLoggedFirstFrame{ false };
std::atomic<bool> mLoggedNoInputSource{ false };
std::atomic<bool> mLoggedUnsupportedFrame{ false };
std::atomic<bool> mLoggedSubmitMiss{ false };
};
}

View File

@@ -0,0 +1,87 @@
#pragma once
#include "DeckLinkInput.h"
#include <atomic>
#include <chrono>
#include <string>
#include <thread>
namespace RenderCadenceCompositor
{
struct DeckLinkInputThreadConfig
{
std::chrono::milliseconds idleSleep = std::chrono::milliseconds(100);
};
class DeckLinkInputThread
{
public:
DeckLinkInputThread(DeckLinkInput& input, DeckLinkInputThreadConfig config = DeckLinkInputThreadConfig()) :
mInput(input),
mConfig(config)
{
}
DeckLinkInputThread(const DeckLinkInputThread&) = delete;
DeckLinkInputThread& operator=(const DeckLinkInputThread&) = delete;
~DeckLinkInputThread()
{
Stop();
}
bool Start(std::string& error)
{
if (mThread.joinable())
return true;
mStartSucceeded.store(false, std::memory_order_release);
mStartCompleted.store(false, std::memory_order_release);
mStopping.store(false, std::memory_order_release);
mThread = std::thread([this]() { ThreadMain(); });
while (!mStartCompleted.load(std::memory_order_acquire))
std::this_thread::sleep_for(std::chrono::milliseconds(1));
if (mStartSucceeded.load(std::memory_order_acquire))
return true;
error = mStartError;
Stop();
return false;
}
void Stop()
{
mStopping.store(true, std::memory_order_release);
if (mThread.joinable())
mThread.join();
}
private:
void ThreadMain()
{
std::string error;
if (!mInput.Start(error))
{
mStartError = error;
mStartCompleted.store(true, std::memory_order_release);
return;
}
mStartSucceeded.store(true, std::memory_order_release);
mStartCompleted.store(true, std::memory_order_release);
while (!mStopping.load(std::memory_order_acquire))
std::this_thread::sleep_for(mConfig.idleSleep);
mInput.Stop();
}
DeckLinkInput& mInput;
DeckLinkInputThreadConfig mConfig;
std::thread mThread;
std::atomic<bool> mStopping{ false };
std::atomic<bool> mStartCompleted{ false };
std::atomic<bool> mStartSucceeded{ false };
std::string mStartError;
};
}

View File

@@ -1,111 +0,0 @@
#include "SyntheticInputProducer.h"
#include "VideoIOFormat.h"
#include <algorithm>
#include <cmath>
namespace RenderCadenceCompositor
{
SyntheticInputProducer::SyntheticInputProducer(InputFrameMailbox& mailbox, SyntheticInputProducerConfig config) :
mMailbox(mailbox),
mConfig(config)
{
}
SyntheticInputProducer::~SyntheticInputProducer()
{
Stop();
}
bool SyntheticInputProducer::Start()
{
if (mThread.joinable())
return true;
if (mConfig.width == 0 || mConfig.height == 0)
return false;
mStopping.store(false, std::memory_order_release);
mThread = std::thread([this]() { ThreadMain(); });
return true;
}
void SyntheticInputProducer::Stop()
{
mStopping.store(true, std::memory_order_release);
if (mThread.joinable())
mThread.join();
}
SyntheticInputProducerMetrics SyntheticInputProducer::Metrics() const
{
SyntheticInputProducerMetrics metrics;
metrics.generatedFrames = mGeneratedFrames.load(std::memory_order_relaxed);
metrics.submitMisses = mSubmitMisses.load(std::memory_order_relaxed);
return metrics;
}
void SyntheticInputProducer::ThreadMain()
{
const unsigned rowBytes = VideoIORowBytes(VideoIOPixelFormat::Bgra8, mConfig.width);
std::vector<unsigned char> buffer(static_cast<std::size_t>(rowBytes) * static_cast<std::size_t>(mConfig.height));
const auto frameDuration = std::chrono::duration_cast<std::chrono::steady_clock::duration>(
std::chrono::duration<double, std::milli>(mConfig.frameDurationMilliseconds));
uint64_t frameIndex = 0;
auto nextFrameTime = std::chrono::steady_clock::now();
while (!mStopping.load(std::memory_order_acquire))
{
const auto now = std::chrono::steady_clock::now();
if (now < nextFrameTime)
{
std::this_thread::sleep_for((std::min)(std::chrono::milliseconds(1), std::chrono::duration_cast<std::chrono::milliseconds>(nextFrameTime - now)));
continue;
}
GenerateFrame(frameIndex, buffer);
if (!mMailbox.SubmitFrame(buffer.data(), rowBytes, frameIndex))
mSubmitMisses.fetch_add(1, std::memory_order_relaxed);
mGeneratedFrames.fetch_add(1, std::memory_order_relaxed);
++frameIndex;
nextFrameTime += frameDuration;
if (std::chrono::steady_clock::now() - nextFrameTime > frameDuration * 4)
nextFrameTime = std::chrono::steady_clock::now() + frameDuration;
}
}
void SyntheticInputProducer::GenerateFrame(uint64_t frameIndex, std::vector<unsigned char>& buffer) const
{
const float t = static_cast<float>(frameIndex) / 60.0f;
const unsigned boxWidth = (std::max)(1u, mConfig.width / 5u);
const unsigned boxHeight = (std::max)(1u, mConfig.height / 6u);
const unsigned maxX = mConfig.width > boxWidth ? mConfig.width - boxWidth : 0u;
const unsigned maxY = mConfig.height > boxHeight ? mConfig.height - boxHeight : 0u;
const unsigned boxX = static_cast<unsigned>((0.5f + 0.5f * std::sin(t * 1.4f)) * static_cast<float>(maxX));
const unsigned boxY = static_cast<unsigned>((0.5f + 0.5f * std::sin(t * 0.9f + 1.2f)) * static_cast<float>(maxY));
const unsigned rowBytes = VideoIORowBytes(VideoIOPixelFormat::Bgra8, mConfig.width);
for (unsigned y = 0; y < mConfig.height; ++y)
{
for (unsigned x = 0; x < mConfig.width; ++x)
{
const std::size_t offset = static_cast<std::size_t>(y) * rowBytes + static_cast<std::size_t>(x) * 4;
const unsigned checker = ((x / 80u) + (y / 80u) + static_cast<unsigned>(frameIndex / 15u)) & 1u;
unsigned char red = checker ? 42 : 16;
unsigned char green = checker ? 70 : 28;
unsigned char blue = checker ? 110 : 55;
if (x >= boxX && x < boxX + boxWidth && y >= boxY && y < boxY + boxHeight)
{
red = 245;
green = static_cast<unsigned char>(160 + 60 * (0.5f + 0.5f * std::sin(t * 2.1f)));
blue = 35;
}
buffer[offset + 0] = blue;
buffer[offset + 1] = green;
buffer[offset + 2] = red;
buffer[offset + 3] = 255;
}
}
}
}

View File

@@ -1,49 +0,0 @@
#pragma once
#include "../frames/InputFrameMailbox.h"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <thread>
#include <vector>
namespace RenderCadenceCompositor
{
struct SyntheticInputProducerConfig
{
unsigned width = 1920;
unsigned height = 1080;
double frameDurationMilliseconds = 1000.0 / 59.94;
};
struct SyntheticInputProducerMetrics
{
uint64_t generatedFrames = 0;
uint64_t submitMisses = 0;
};
class SyntheticInputProducer
{
public:
SyntheticInputProducer(InputFrameMailbox& mailbox, SyntheticInputProducerConfig config);
SyntheticInputProducer(const SyntheticInputProducer&) = delete;
SyntheticInputProducer& operator=(const SyntheticInputProducer&) = delete;
~SyntheticInputProducer();
bool Start();
void Stop();
SyntheticInputProducerMetrics Metrics() const;
private:
void ThreadMain();
void GenerateFrame(uint64_t frameIndex, std::vector<unsigned char>& buffer) const;
InputFrameMailbox& mMailbox;
SyntheticInputProducerConfig mConfig;
std::thread mThread;
std::atomic<bool> mStopping{ false };
std::atomic<uint64_t> mGeneratedFrames{ 0 };
std::atomic<uint64_t> mSubmitMisses{ 0 };
};
}