#include "RenderThread.h" #include "../frames/InputFrameMailbox.h" #include "../frames/SystemFrameExchange.h" #include "../frames/SystemFrameTypes.h" #include "../logging/Logger.h" #include "../platform/HiddenGlWindow.h" #include "InputFrameTexture.h" #include "readback/Bgra8ReadbackPipeline.h" #include "GLExtensions.h" #include "runtime/RuntimeRenderScene.h" #include "runtime/RuntimeShaderRenderer.h" #include "SimpleMotionRenderer.h" #include #include #include RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) : mFrameExchange(frameExchange), mConfig(config) { } RenderThread::RenderThread(SystemFrameExchange& frameExchange, InputFrameMailbox* inputMailbox, Config config) : mFrameExchange(frameExchange), mInputMailbox(inputMailbox), mConfig(config) { } RenderThread::~RenderThread() { Stop(); } bool RenderThread::Start(std::string& error) { if (mThread.joinable()) return true; { std::lock_guard lock(mStartupMutex); mStarted = false; mStartupError.clear(); } mStopping.store(false, std::memory_order_release); mThread = std::thread([this]() { ThreadMain(); }); std::unique_lock lock(mStartupMutex); if (!mStartupCondition.wait_for(lock, std::chrono::seconds(3), [this]() { return mStarted || !mStartupError.empty(); })) { error = "Timed out starting render thread."; return false; } if (!mStartupError.empty()) { error = mStartupError; lock.unlock(); if (mThread.joinable()) mThread.join(); return false; } return true; } void RenderThread::Stop() { mStopping.store(true, std::memory_order_release); if (mThread.joinable()) mThread.join(); } RenderThread::Metrics RenderThread::GetMetrics() const { Metrics metrics; metrics.renderedFrames = mRenderedFrames.load(std::memory_order_relaxed); metrics.completedReadbacks = mCompletedReadbacks.load(std::memory_order_relaxed); metrics.acquireMisses = mAcquireMisses.load(std::memory_order_relaxed); metrics.pboQueueMisses = mPboQueueMisses.load(std::memory_order_relaxed); metrics.clockOverruns = mClockOverruns.load(std::memory_order_relaxed); metrics.skippedFrames = mSkippedFrames.load(std::memory_order_relaxed); metrics.shaderBuildsCommitted = mShaderBuildsCommitted.load(std::memory_order_relaxed); metrics.shaderBuildFailures = mShaderBuildFailures.load(std::memory_order_relaxed); metrics.renderFrameMilliseconds = mRenderFrameMilliseconds.load(std::memory_order_relaxed); metrics.renderFrameBudgetUsedPercent = mRenderFrameBudgetUsedPercent.load(std::memory_order_relaxed); metrics.renderFrameMaxMilliseconds = mRenderFrameMaxMilliseconds.load(std::memory_order_relaxed); metrics.readbackQueueMilliseconds = mReadbackQueueMilliseconds.load(std::memory_order_relaxed); metrics.completedReadbackCopyMilliseconds = mCompletedReadbackCopyMilliseconds.load(std::memory_order_relaxed); metrics.inputFramesReceived = mInputFramesReceived.load(std::memory_order_relaxed); metrics.inputFramesDropped = mInputFramesDropped.load(std::memory_order_relaxed); metrics.inputConsumeMisses = mInputConsumeMisses.load(std::memory_order_relaxed); metrics.inputUploadMisses = mInputUploadMisses.load(std::memory_order_relaxed); metrics.inputReadyFrames = mInputReadyFrames.load(std::memory_order_relaxed); metrics.inputReadingFrames = mInputReadingFrames.load(std::memory_order_relaxed); metrics.inputLatestAgeMilliseconds = mInputLatestAgeMilliseconds.load(std::memory_order_relaxed); metrics.inputUploadMilliseconds = mInputUploadMilliseconds.load(std::memory_order_relaxed); metrics.inputFormatSupported = mInputFormatSupported.load(std::memory_order_relaxed); metrics.inputSignalPresent = mInputSignalPresent.load(std::memory_order_relaxed); return metrics; } void RenderThread::ThreadMain() { RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread starting."); HiddenGlWindow window; std::string error; if (!window.Create(mConfig.width, mConfig.height, error)) { SignalStartupFailure(error.empty() ? "OpenGL context creation failed." : error); return; } std::unique_ptr prepareWindow = std::make_unique(); if (!prepareWindow->CreateShared(mConfig.width, mConfig.height, window.DeviceContext(), window.Context(), error)) { SignalStartupFailure(error.empty() ? "Runtime shader prepare shared context creation failed." : error); return; } if (!window.MakeCurrent()) { SignalStartupFailure("OpenGL context creation failed."); return; } if (!ResolveGLExtensions()) { SignalStartupFailure("OpenGL extension resolution failed."); return; } SimpleMotionRenderer renderer; RuntimeRenderScene runtimeRenderScene; Bgra8ReadbackPipeline readback; InputFrameTexture inputTexture; if (!runtimeRenderScene.StartPrepareWorker(std::move(prepareWindow), error)) { SignalStartupFailure(error.empty() ? "Runtime shader prepare worker initialization failed." : error); return; } if (!renderer.InitializeGl(mConfig.width, mConfig.height) || !readback.Initialize(mConfig.width, mConfig.height, mConfig.pboDepth)) { SignalStartupFailure("Render pipeline initialization failed."); return; } RenderCadenceClock clock(mConfig.frameDurationMilliseconds); uint64_t frameIndex = 0; mRunning.store(true, std::memory_order_release); SignalStarted(); while (!mStopping.load(std::memory_order_acquire)) { readback.ConsumeCompleted( [this](SystemFrame& frame) { return mFrameExchange.AcquireForRender(frame); }, [this](const SystemFrame& frame) { return mFrameExchange.PublishCompleted(frame); }, [this]() { CountAcquireMiss(); }, [this]() { CountCompleted(); }); PublishReadbackMetrics(readback); const auto now = RenderCadenceClock::Clock::now(); const RenderCadenceClock::Tick tick = clock.Poll(now); if (!tick.due) { if (tick.sleepFor > RenderCadenceClock::Duration::zero()) std::this_thread::sleep_for(tick.sleepFor); continue; } TryCommitReadyRuntimeShader(runtimeRenderScene); const GLuint videoInputTexture = inputTexture.PollAndUpload(mInputMailbox); PublishInputMetrics(inputTexture); if (!readback.RenderAndQueue(frameIndex, [this, &renderer, &runtimeRenderScene, videoInputTexture](uint64_t index) { if (runtimeRenderScene.HasLayers()) runtimeRenderScene.RenderFrame(index, mConfig.width, mConfig.height, videoInputTexture); else if (videoInputTexture != 0) renderer.RenderTexture(videoInputTexture); else renderer.RenderFrame(index); })) { mPboQueueMisses.fetch_add(1, std::memory_order_relaxed); } PublishReadbackMetrics(readback); CountRendered(); ++frameIndex; clock.MarkRendered(RenderCadenceClock::Clock::now()); mClockOverruns.store(clock.OverrunCount(), std::memory_order_relaxed); mSkippedFrames.store(clock.SkippedFrameCount(), std::memory_order_relaxed); } for (std::size_t i = 0; i < mConfig.pboDepth * 2; ++i) { readback.ConsumeCompleted( [this](SystemFrame& frame) { return mFrameExchange.AcquireForRender(frame); }, [this](const SystemFrame& frame) { return mFrameExchange.PublishCompleted(frame); }, [this]() { CountAcquireMiss(); }, [this]() { CountCompleted(); }); PublishReadbackMetrics(readback); } readback.Shutdown(); inputTexture.ShutdownGl(); runtimeRenderScene.ShutdownGl(); renderer.ShutdownGl(); window.ClearCurrent(); mRunning.store(false, std::memory_order_release); RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread stopped."); } void RenderThread::SignalStarted() { std::lock_guard lock(mStartupMutex); mStarted = true; mStartupCondition.notify_all(); } void RenderThread::SignalStartupFailure(const std::string& error) { RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Error, "render-thread", error); std::lock_guard lock(mStartupMutex); mStartupError = error; mStartupCondition.notify_all(); } void RenderThread::CountRendered() { mRenderedFrames.fetch_add(1, std::memory_order_relaxed); } void RenderThread::CountCompleted() { mCompletedReadbacks.fetch_add(1, std::memory_order_relaxed); } void RenderThread::CountAcquireMiss() { mAcquireMisses.fetch_add(1, std::memory_order_relaxed); } void RenderThread::PublishReadbackMetrics(const Bgra8ReadbackPipeline& readback) { const double renderMilliseconds = readback.LastRenderFrameMilliseconds(); mRenderFrameMilliseconds.store(renderMilliseconds, std::memory_order_relaxed); if (mConfig.frameDurationMilliseconds > 0.0) { mRenderFrameBudgetUsedPercent.store( (renderMilliseconds / mConfig.frameDurationMilliseconds) * 100.0, std::memory_order_relaxed); } else { mRenderFrameBudgetUsedPercent.store(0.0, std::memory_order_relaxed); } const double previousMax = mRenderFrameMaxMilliseconds.load(std::memory_order_relaxed); if (renderMilliseconds > previousMax) mRenderFrameMaxMilliseconds.store(renderMilliseconds, std::memory_order_relaxed); mReadbackQueueMilliseconds.store(readback.LastReadbackQueueMilliseconds(), std::memory_order_relaxed); mCompletedReadbackCopyMilliseconds.store(readback.LastCompletedReadbackCopyMilliseconds(), std::memory_order_relaxed); } void RenderThread::PublishInputMetrics(const InputFrameTexture& inputTexture) { if (mInputMailbox != nullptr) { const InputFrameMailboxMetrics mailboxMetrics = mInputMailbox->Metrics(); mInputFramesReceived.store(mailboxMetrics.submittedFrames, std::memory_order_relaxed); mInputFramesDropped.store(mailboxMetrics.droppedReadyFrames + mailboxMetrics.submitMisses, std::memory_order_relaxed); mInputConsumeMisses.store(mailboxMetrics.consumeMisses, std::memory_order_relaxed); mInputReadyFrames.store(mailboxMetrics.readyCount, std::memory_order_relaxed); mInputReadingFrames.store(mailboxMetrics.readingCount, std::memory_order_relaxed); mInputLatestAgeMilliseconds.store(mailboxMetrics.latestFrameAgeMilliseconds, std::memory_order_relaxed); mInputSignalPresent.store(mailboxMetrics.hasSubmittedFrame, std::memory_order_relaxed); } else { mInputFramesReceived.store(0, std::memory_order_relaxed); mInputFramesDropped.store(0, std::memory_order_relaxed); mInputConsumeMisses.store(0, std::memory_order_relaxed); mInputReadyFrames.store(0, std::memory_order_relaxed); mInputReadingFrames.store(0, std::memory_order_relaxed); mInputLatestAgeMilliseconds.store(0.0, std::memory_order_relaxed); mInputSignalPresent.store(false, std::memory_order_relaxed); } mInputUploadMisses.store(inputTexture.UploadMisses(), std::memory_order_relaxed); mInputUploadMilliseconds.store(inputTexture.LastUploadMilliseconds(), std::memory_order_relaxed); mInputFormatSupported.store(inputTexture.LastFrameFormatSupported(), std::memory_order_relaxed); } void RenderThread::SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact) { if (artifact.fragmentShaderSource.empty()) return; std::lock_guard lock(mShaderArtifactMutex); mPendingShaderArtifact = artifact; mHasPendingShaderArtifact = true; } void RenderThread::SubmitRuntimeRenderLayers(const std::vector& layers) { std::lock_guard lock(mRenderLayersMutex); mPendingRenderLayers = layers; mHasPendingRenderLayers = true; } bool RenderThread::TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact) { std::lock_guard lock(mShaderArtifactMutex); if (!mHasPendingShaderArtifact) return false; artifact = std::move(mPendingShaderArtifact); mPendingShaderArtifact = RuntimeShaderArtifact(); mHasPendingShaderArtifact = false; return true; } bool RenderThread::TryTakePendingRenderLayers(std::vector& layers) { std::lock_guard lock(mRenderLayersMutex); if (!mHasPendingRenderLayers) return false; layers = std::move(mPendingRenderLayers); mPendingRenderLayers.clear(); mHasPendingRenderLayers = false; return true; } void RenderThread::TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRenderScene) { std::vector layers; std::string commitError; if (TryTakePendingRenderLayers(layers)) { if (!runtimeRenderScene.CommitRenderLayers(layers, commitError)) { RenderCadenceCompositor::TryLog( RenderCadenceCompositor::LogLevel::Error, "render-thread", "Runtime render-layer commit failed: " + commitError); mShaderBuildFailures.fetch_add(1, std::memory_order_relaxed); return; } RenderCadenceCompositor::TryLog( RenderCadenceCompositor::LogLevel::Log, "render-thread", "Runtime render layer snapshot committed."); mShaderBuildsCommitted.fetch_add(1, std::memory_order_relaxed); return; } RuntimeShaderArtifact artifact; if (!TryTakePendingRuntimeShaderArtifact(artifact)) return; RenderCadenceCompositor::RuntimeRenderLayerModel layer; layer.id = artifact.layerId.empty() ? "runtime-layer-1" : artifact.layerId; layer.shaderId = artifact.shaderId; layer.artifact = artifact; layers.push_back(std::move(layer)); if (!runtimeRenderScene.CommitRenderLayers(layers, commitError)) { RenderCadenceCompositor::TryLog( RenderCadenceCompositor::LogLevel::Error, "render-thread", "Runtime shader GL commit failed: " + commitError); mShaderBuildFailures.fetch_add(1, std::memory_order_relaxed); return; } RenderCadenceCompositor::TryLog( RenderCadenceCompositor::LogLevel::Log, "render-thread", "Runtime shader committed: " + artifact.shaderId + ". " + artifact.message); mShaderBuildsCommitted.fetch_add(1, std::memory_order_relaxed); }