280 lines
7.7 KiB
C++
280 lines
7.7 KiB
C++
#pragma once
|
|
|
|
#include "AppConfig.h"
|
|
#include "AppConfigProvider.h"
|
|
#include "../json/JsonWriter.h"
|
|
#include "../logging/Logger.h"
|
|
#include "../runtime/RuntimeShaderBridge.h"
|
|
#include "../telemetry/CadenceTelemetryJson.h"
|
|
#include "../telemetry/TelemetryPrinter.h"
|
|
#include "../video/DeckLinkOutput.h"
|
|
#include "../video/DeckLinkOutputThread.h"
|
|
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#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)
|
|
{
|
|
Log("app", "Initializing DeckLink output.");
|
|
if (!mOutput.Initialize(
|
|
mConfig.deckLink,
|
|
[this](const VideoIOCompletion& completion) {
|
|
mFrameExchange.ReleaseScheduledByBytes(completion.outputFrameBuffer);
|
|
},
|
|
error))
|
|
{
|
|
LogError("app", "DeckLink output initialization failed: " + error);
|
|
return false;
|
|
}
|
|
|
|
Log("app", "Starting render thread.");
|
|
if (!detail::StartRenderThread(mRenderThread, error, 0))
|
|
{
|
|
LogError("app", "Render thread start failed: " + error);
|
|
Stop();
|
|
return false;
|
|
}
|
|
StartRuntimeShaderBuild();
|
|
|
|
Log("app", "Waiting for rendered warmup frames.");
|
|
if (!mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, mConfig.warmupTimeout))
|
|
{
|
|
error = "Timed out waiting for rendered warmup frames.";
|
|
LogError("app", error);
|
|
Stop();
|
|
return false;
|
|
}
|
|
|
|
Log("app", "Starting DeckLink output thread.");
|
|
if (!mOutputThread.Start())
|
|
{
|
|
error = "DeckLink output thread failed to start.";
|
|
LogError("app", error);
|
|
Stop();
|
|
return false;
|
|
}
|
|
|
|
Log("app", "Waiting for DeckLink preroll frames.");
|
|
if (!WaitForPreroll())
|
|
{
|
|
error = "Timed out waiting for DeckLink preroll frames.";
|
|
LogError("app", error);
|
|
Stop();
|
|
return false;
|
|
}
|
|
|
|
Log("app", "Starting DeckLink scheduled playback.");
|
|
if (!mOutput.StartScheduledPlayback(error))
|
|
{
|
|
LogError("app", "DeckLink scheduled playback failed: " + error);
|
|
Stop();
|
|
return false;
|
|
}
|
|
|
|
mTelemetry.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
|
StartHttpServer();
|
|
Log("app", "RenderCadenceCompositor started.");
|
|
mStarted = true;
|
|
return true;
|
|
}
|
|
|
|
void Stop()
|
|
{
|
|
mHttpServer.Stop();
|
|
mTelemetry.Stop();
|
|
mOutputThread.Stop();
|
|
mOutput.Stop();
|
|
StopRuntimeShaderBuild();
|
|
mRenderThread.Stop();
|
|
mOutput.ReleaseResources();
|
|
if (mStarted)
|
|
Log("app", "RenderCadenceCompositor shutdown complete.");
|
|
mStarted = false;
|
|
}
|
|
|
|
bool Started() const { return mStarted; }
|
|
const DeckLinkOutput& Output() const { return mOutput; }
|
|
|
|
private:
|
|
void StartHttpServer()
|
|
{
|
|
HttpControlServerCallbacks callbacks;
|
|
callbacks.getStateJson = [this]() {
|
|
return BuildStateJson();
|
|
};
|
|
|
|
std::string error;
|
|
if (!mHttpServer.Start(std::filesystem::current_path() / "docs", mConfig.http, callbacks, error))
|
|
{
|
|
LogWarning("http", "HTTP control server did not start: " + error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::string BuildStateJson()
|
|
{
|
|
CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
|
|
|
JsonWriter writer;
|
|
writer.BeginObject();
|
|
writer.Key("app");
|
|
writer.BeginObject();
|
|
writer.KeyUInt("serverPort", mHttpServer.Port());
|
|
writer.KeyUInt("oscPort", mConfig.oscPort);
|
|
writer.KeyString("oscBindAddress", mConfig.oscBindAddress);
|
|
writer.KeyDouble("oscSmoothing", mConfig.oscSmoothing);
|
|
writer.KeyBool("autoReload", mConfig.autoReload);
|
|
writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(mConfig.maxTemporalHistoryFrames));
|
|
writer.KeyDouble("previewFps", mConfig.previewFps);
|
|
writer.KeyBool("enableExternalKeying", mConfig.deckLink.externalKeyingEnabled);
|
|
writer.KeyString("inputVideoFormat", mConfig.inputVideoFormat);
|
|
writer.KeyString("inputFrameRate", mConfig.inputFrameRate);
|
|
writer.KeyString("outputVideoFormat", mConfig.outputVideoFormat);
|
|
writer.KeyString("outputFrameRate", mConfig.outputFrameRate);
|
|
writer.EndObject();
|
|
|
|
writer.Key("runtime");
|
|
writer.BeginObject();
|
|
writer.KeyUInt("layerCount", 0);
|
|
writer.KeyBool("compileSucceeded", true);
|
|
writer.KeyString("compileMessage", "Runtime state is not ported into RenderCadenceCompositor yet.");
|
|
writer.EndObject();
|
|
|
|
writer.KeyNull("video");
|
|
writer.KeyNull("decklink");
|
|
writer.KeyNull("videoIO");
|
|
|
|
writer.Key("performance");
|
|
writer.BeginObject();
|
|
writer.KeyDouble("frameBudgetMs", FrameDurationMillisecondsFromRateString(mConfig.outputFrameRate));
|
|
writer.KeyNull("renderMs");
|
|
writer.KeyNull("smoothedRenderMs");
|
|
writer.KeyNull("budgetUsedPercent");
|
|
writer.KeyNull("completionIntervalMs");
|
|
writer.KeyNull("smoothedCompletionIntervalMs");
|
|
writer.KeyNull("maxCompletionIntervalMs");
|
|
writer.KeyUInt("lateFrameCount", telemetry.displayedLate);
|
|
writer.KeyUInt("droppedFrameCount", telemetry.dropped);
|
|
writer.KeyNull("flushedFrameCount");
|
|
writer.Key("cadence");
|
|
WriteCadenceTelemetryJson(writer, telemetry);
|
|
writer.EndObject();
|
|
|
|
writer.KeyNull("backendPlayout");
|
|
writer.KeyNull("runtimeEvents");
|
|
writer.Key("shaders");
|
|
writer.BeginArray();
|
|
writer.EndArray();
|
|
writer.Key("stackPresets");
|
|
writer.BeginArray();
|
|
writer.EndArray();
|
|
writer.Key("layers");
|
|
writer.BeginArray();
|
|
writer.EndArray();
|
|
writer.EndObject();
|
|
return writer.StringValue();
|
|
}
|
|
|
|
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()
|
|
{
|
|
if (mConfig.runtimeShaderId.empty())
|
|
{
|
|
Log("runtime-shader", "Runtime shader build disabled.");
|
|
return;
|
|
}
|
|
|
|
Log("runtime-shader", "Starting background Slang build for shader '" + mConfig.runtimeShaderId + "'.");
|
|
mShaderBridge.Start(
|
|
mConfig.runtimeShaderId,
|
|
[this](const RuntimeShaderArtifact& artifact) {
|
|
mRenderThread.SubmitRuntimeShaderArtifact(artifact);
|
|
},
|
|
[](const std::string& message) {
|
|
LogError("runtime-shader", "Runtime Slang build failed: " + message);
|
|
});
|
|
}
|
|
|
|
void StopRuntimeShaderBuild()
|
|
{
|
|
mShaderBridge.Stop();
|
|
}
|
|
|
|
RenderThread& mRenderThread;
|
|
SystemFrameExchange& mFrameExchange;
|
|
AppConfig mConfig;
|
|
DeckLinkOutput mOutput;
|
|
DeckLinkOutputThread<SystemFrameExchange> mOutputThread;
|
|
TelemetryPrinter mTelemetry;
|
|
CadenceTelemetry mHttpTelemetry;
|
|
HttpControlServer mHttpServer;
|
|
RuntimeShaderBridge mShaderBridge;
|
|
bool mStarted = false;
|
|
};
|
|
}
|