#include "RenderThread.h" #include "../frames/SystemFrameExchange.h" #include "../frames/SystemFrameTypes.h" #include "../platform/HiddenGlWindow.h" #include "Bgra8ReadbackPipeline.h" #include "GLExtensions.h" #include "RuntimeShaderRenderer.h" #include "SimpleMotionRenderer.h" #include #include #include RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) : mFrameExchange(frameExchange), 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 { std::lock_guard lock(mMetricsMutex); return mMetrics; } void RenderThread::ThreadMain() { HiddenGlWindow window; std::string error; if (!window.Create(mConfig.width, mConfig.height, error) || !window.MakeCurrent()) { SignalStartupFailure(error.empty() ? "OpenGL context creation failed." : error); return; } if (!ResolveGLExtensions()) { SignalStartupFailure("OpenGL extension resolution failed."); return; } SimpleMotionRenderer renderer; RuntimeShaderRenderer runtimeShaderRenderer; Bgra8ReadbackPipeline readback; 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(); }); 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(runtimeShaderRenderer); if (!readback.RenderAndQueue(frameIndex, [this, &renderer, &runtimeShaderRenderer](uint64_t index) { if (runtimeShaderRenderer.HasProgram()) runtimeShaderRenderer.RenderFrame(index, mConfig.width, mConfig.height); else renderer.RenderFrame(index); })) { std::lock_guard lock(mMetricsMutex); ++mMetrics.pboQueueMisses; } CountRendered(); ++frameIndex; clock.MarkRendered(RenderCadenceClock::Clock::now()); { std::lock_guard lock(mMetricsMutex); mMetrics.clockOverruns = clock.OverrunCount(); mMetrics.skippedFrames = clock.SkippedFrameCount(); } } 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(); }); } readback.Shutdown(); runtimeShaderRenderer.ShutdownGl(); renderer.ShutdownGl(); window.ClearCurrent(); mRunning.store(false, std::memory_order_release); } void RenderThread::SignalStarted() { std::lock_guard lock(mStartupMutex); mStarted = true; mStartupCondition.notify_all(); } void RenderThread::SignalStartupFailure(const std::string& error) { std::lock_guard lock(mStartupMutex); mStartupError = error; mStartupCondition.notify_all(); } void RenderThread::CountRendered() { std::lock_guard lock(mMetricsMutex); ++mMetrics.renderedFrames; } void RenderThread::CountCompleted() { std::lock_guard lock(mMetricsMutex); ++mMetrics.completedReadbacks; } void RenderThread::CountAcquireMiss() { std::lock_guard lock(mMetricsMutex); ++mMetrics.acquireMisses; } void RenderThread::SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact) { if (artifact.fragmentShaderSource.empty()) return; std::lock_guard lock(mShaderArtifactMutex); mPendingShaderArtifact = artifact; mHasPendingShaderArtifact = 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; } void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer) { RuntimeShaderArtifact artifact; if (!TryTakePendingRuntimeShaderArtifact(artifact)) return; std::string commitError; if (!runtimeShaderRenderer.CommitShaderArtifact(artifact, commitError)) { OutputDebugStringA(("Runtime shader GL commit failed: " + commitError + "\n").c_str()); std::lock_guard lock(mMetricsMutex); ++mMetrics.shaderBuildFailures; return; } OutputDebugStringA(("Runtime shader committed: " + artifact.shaderId + ". " + artifact.message + "\n").c_str()); std::lock_guard lock(mMetricsMutex); ++mMetrics.shaderBuildsCommitted; }