V2 working
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m54s
CI / Windows Release Package (push) Successful in 3m14s

This commit is contained in:
Aiden
2026-05-12 01:59:02 +10:00
parent 2531d871e8
commit e0ca548ef5
32 changed files with 3492 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
#include "AppConfig.h"
namespace RenderCadenceCompositor
{
AppConfig DefaultAppConfig()
{
AppConfig config;
config.deckLink.externalKeyingEnabled = false;
config.deckLink.outputAlphaRequired = false;
config.outputThread.targetBufferedFrames = 4;
config.telemetry.interval = std::chrono::seconds(1);
config.warmupCompletedFrames = 4;
config.warmupTimeout = std::chrono::seconds(3);
config.prerollTimeout = std::chrono::seconds(3);
config.prerollPoll = std::chrono::milliseconds(2);
return config;
}
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "../telemetry/TelemetryPrinter.h"
#include "../video/DeckLinkOutput.h"
#include "../video/DeckLinkOutputThread.h"
#include <chrono>
#include <cstddef>
namespace RenderCadenceCompositor
{
struct AppConfig
{
DeckLinkOutputConfig deckLink;
DeckLinkOutputThreadConfig outputThread;
TelemetryPrinterConfig telemetry;
std::size_t warmupCompletedFrames = 4;
std::chrono::milliseconds warmupTimeout = std::chrono::seconds(3);
std::chrono::milliseconds prerollTimeout = std::chrono::seconds(3);
std::chrono::milliseconds prerollPoll = std::chrono::milliseconds(2);
};
AppConfig DefaultAppConfig();
}

View File

@@ -0,0 +1,148 @@
#pragma once
#include "AppConfig.h"
#include "../telemetry/TelemetryPrinter.h"
#include "../video/DeckLinkOutput.h"
#include "../video/DeckLinkOutputThread.h"
#include <chrono>
#include <string>
#include <thread>
#include <type_traits>
namespace RenderCadenceCompositor
{
namespace detail
{
template <typename RenderThread>
auto StartRenderThread(RenderThread& renderThread, std::string& error, int) -> decltype(renderThread.Start(error), bool())
{
return renderThread.Start(error);
}
template <typename RenderThread>
bool StartRenderThreadWithoutError(RenderThread& renderThread, std::true_type)
{
return renderThread.Start();
}
template <typename RenderThread>
bool StartRenderThreadWithoutError(RenderThread& renderThread, std::false_type)
{
renderThread.Start();
return true;
}
template <typename RenderThread>
auto StartRenderThread(RenderThread& renderThread, std::string&, long) -> decltype(renderThread.Start(), bool())
{
return StartRenderThreadWithoutError(renderThread, std::is_same<decltype(renderThread.Start()), bool>());
}
}
template <typename RenderThread, typename SystemFrameExchange>
class RenderCadenceApp
{
public:
RenderCadenceApp(RenderThread& renderThread, SystemFrameExchange& frameExchange, AppConfig config = DefaultAppConfig()) :
mRenderThread(renderThread),
mFrameExchange(frameExchange),
mConfig(config),
mOutputThread(mOutput, mFrameExchange, mConfig.outputThread),
mTelemetry(mConfig.telemetry)
{
}
RenderCadenceApp(const RenderCadenceApp&) = delete;
RenderCadenceApp& operator=(const RenderCadenceApp&) = delete;
~RenderCadenceApp()
{
Stop();
}
bool Start(std::string& error)
{
if (!mOutput.Initialize(
mConfig.deckLink,
[this](const VideoIOCompletion& completion) {
mFrameExchange.ReleaseScheduledByBytes(completion.outputFrameBuffer);
},
error))
{
return false;
}
if (!detail::StartRenderThread(mRenderThread, error, 0))
{
Stop();
return false;
}
if (!mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, mConfig.warmupTimeout))
{
error = "Timed out waiting for rendered warmup frames.";
Stop();
return false;
}
if (!mOutputThread.Start())
{
error = "DeckLink output thread failed to start.";
Stop();
return false;
}
if (!WaitForPreroll())
{
error = "Timed out waiting for DeckLink preroll frames.";
Stop();
return false;
}
if (!mOutput.StartScheduledPlayback(error))
{
Stop();
return false;
}
mTelemetry.Start(mFrameExchange, mOutput, mOutputThread);
mStarted = true;
return true;
}
void Stop()
{
mTelemetry.Stop();
mOutputThread.Stop();
mOutput.Stop();
mRenderThread.Stop();
mOutput.ReleaseResources();
mStarted = false;
}
bool Started() const { return mStarted; }
const DeckLinkOutput& Output() const { return mOutput; }
private:
bool WaitForPreroll() const
{
const auto deadline = std::chrono::steady_clock::now() + mConfig.prerollTimeout;
while (std::chrono::steady_clock::now() < deadline)
{
if (mFrameExchange.Metrics().scheduledCount >= mConfig.outputThread.targetBufferedFrames)
return true;
std::this_thread::sleep_for(mConfig.prerollPoll);
}
return false;
}
RenderThread& mRenderThread;
SystemFrameExchange& mFrameExchange;
AppConfig mConfig;
DeckLinkOutput mOutput;
DeckLinkOutputThread<SystemFrameExchange> mOutputThread;
TelemetryPrinter mTelemetry;
bool mStarted = false;
};
}