305 lines
8.9 KiB
C++
305 lines
8.9 KiB
C++
#include "RenderThread.h"
|
|
|
|
#include "../frames/SystemFrameExchange.h"
|
|
#include "../frames/SystemFrameTypes.h"
|
|
#include "../logging/Logger.h"
|
|
#include "../platform/HiddenGlWindow.h"
|
|
#include "readback/Bgra8ReadbackPipeline.h"
|
|
#include "GLExtensions.h"
|
|
#include "runtime/RuntimeRenderScene.h"
|
|
#include "runtime/RuntimeShaderRenderer.h"
|
|
#include "SimpleMotionRenderer.h"
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <thread>
|
|
|
|
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<std::mutex> lock(mStartupMutex);
|
|
mStarted = false;
|
|
mStartupError.clear();
|
|
}
|
|
|
|
mStopping.store(false, std::memory_order_release);
|
|
mThread = std::thread([this]() { ThreadMain(); });
|
|
|
|
std::unique_lock<std::mutex> 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);
|
|
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<HiddenGlWindow> prepareWindow = std::make_unique<HiddenGlWindow>();
|
|
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;
|
|
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(); });
|
|
|
|
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);
|
|
if (!readback.RenderAndQueue(frameIndex, [this, &renderer, &runtimeRenderScene](uint64_t index) {
|
|
if (runtimeRenderScene.HasLayers())
|
|
runtimeRenderScene.RenderFrame(index, mConfig.width, mConfig.height);
|
|
else
|
|
renderer.RenderFrame(index);
|
|
}))
|
|
{
|
|
mPboQueueMisses.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
|
|
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(); });
|
|
}
|
|
|
|
readback.Shutdown();
|
|
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<std::mutex> 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<std::mutex> 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::SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact)
|
|
{
|
|
if (artifact.fragmentShaderSource.empty())
|
|
return;
|
|
|
|
std::lock_guard<std::mutex> lock(mShaderArtifactMutex);
|
|
mPendingShaderArtifact = artifact;
|
|
mHasPendingShaderArtifact = true;
|
|
}
|
|
|
|
void RenderThread::SubmitRuntimeRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers)
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderLayersMutex);
|
|
mPendingRenderLayers = layers;
|
|
mHasPendingRenderLayers = true;
|
|
}
|
|
|
|
bool RenderThread::TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact)
|
|
{
|
|
std::lock_guard<std::mutex> lock(mShaderArtifactMutex);
|
|
if (!mHasPendingShaderArtifact)
|
|
return false;
|
|
|
|
artifact = std::move(mPendingShaderArtifact);
|
|
mPendingShaderArtifact = RuntimeShaderArtifact();
|
|
mHasPendingShaderArtifact = false;
|
|
return true;
|
|
}
|
|
|
|
bool RenderThread::TryTakePendingRenderLayers(std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers)
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRenderLayersMutex);
|
|
if (!mHasPendingRenderLayers)
|
|
return false;
|
|
|
|
layers = std::move(mPendingRenderLayers);
|
|
mPendingRenderLayers.clear();
|
|
mHasPendingRenderLayers = false;
|
|
return true;
|
|
}
|
|
|
|
void RenderThread::TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRenderScene)
|
|
{
|
|
std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> 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);
|
|
}
|