#pragma once #include "AppConfig.h" #include "../runtime/RuntimeShaderBridge.h" #include "../telemetry/TelemetryPrinter.h" #include "../video/DeckLinkOutput.h" #include "../video/DeckLinkOutputThread.h" #include #include #include #include #include namespace RenderCadenceCompositor { namespace detail { template auto StartRenderThread(RenderThread& renderThread, std::string& error, int) -> decltype(renderThread.Start(error), bool()) { return renderThread.Start(error); } template bool StartRenderThreadWithoutError(RenderThread& renderThread, std::true_type) { return renderThread.Start(); } template bool StartRenderThreadWithoutError(RenderThread& renderThread, std::false_type) { renderThread.Start(); return true; } template auto StartRenderThread(RenderThread& renderThread, std::string&, long) -> decltype(renderThread.Start(), bool()) { return StartRenderThreadWithoutError(renderThread, std::is_same()); } } template 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; } StartRuntimeShaderBuild(); 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, mRenderThread); mStarted = true; return true; } void Stop() { mTelemetry.Stop(); mOutputThread.Stop(); mOutput.Stop(); StopRuntimeShaderBuild(); 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; } void StartRuntimeShaderBuild() { mShaderBridge.Start( mConfig.runtimeShaderId, [this](const RuntimeShaderArtifact& artifact) { mRenderThread.SubmitRuntimeShaderArtifact(artifact); }, [](const std::string& message) { std::cout << "Runtime Slang build failed: " << message << "\n"; }); } void StopRuntimeShaderBuild() { mShaderBridge.Stop(); } RenderThread& mRenderThread; SystemFrameExchange& mFrameExchange; AppConfig mConfig; DeckLinkOutput mOutput; DeckLinkOutputThread mOutputThread; TelemetryPrinter mTelemetry; RuntimeShaderBridge mShaderBridge; bool mStarted = false; }; }