From d07ea1f63a800acd56954dbaf79b309c4e2cd918 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Tue, 12 May 2026 14:36:36 +1000 Subject: [PATCH] Render changes --- CMakeLists.txt | 2 + apps/RenderCadenceCompositor/README.md | 6 +- .../app/RenderCadenceApp.h | 32 +++-- .../render/RenderThread.cpp | 64 ++++++++-- .../render/RenderThread.h | 11 +- .../render/RuntimeRenderScene.cpp | 114 ++++++++++++++++++ .../render/RuntimeRenderScene.h | 39 ++++++ 7 files changed, 248 insertions(+), 20 deletions(-) create mode 100644 apps/RenderCadenceCompositor/render/RuntimeRenderScene.cpp create mode 100644 apps/RenderCadenceCompositor/render/RuntimeRenderScene.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 950cdc2..6243efc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -331,6 +331,8 @@ set(RENDER_CADENCE_APP_SOURCES "${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderRenderer.h" "${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderParams.cpp" "${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderParams.h" + "${RENDER_CADENCE_APP_DIR}/render/RuntimeRenderScene.cpp" + "${RENDER_CADENCE_APP_DIR}/render/RuntimeRenderScene.h" "${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.cpp" "${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.h" "${RENDER_CADENCE_APP_DIR}/runtime/RuntimeLayerModel.cpp" diff --git a/apps/RenderCadenceCompositor/README.md b/apps/RenderCadenceCompositor/README.md index 5410c07..3ba2718 100644 --- a/apps/RenderCadenceCompositor/README.md +++ b/apps/RenderCadenceCompositor/README.md @@ -43,6 +43,7 @@ Included now: - background Slang compile of `shaders/happy-accident` - app-owned display/render layer model for shader build readiness - app-owned submission of a completed shader artifact +- render-thread-owned runtime render scene for ready shader layers - render-thread-only GL commit once the artifact is ready - manifest-driven stateless single-pass shader packages - HTTP shader list populated from supported stateless single-pass shader packages @@ -210,7 +211,9 @@ Current runtime shader support is deliberately limited to stateless single-pass The `/api/state` shader list uses the same support rules as runtime shader compilation and reports only packages this app can run today. Unsupported manifest feature sets such as multipass, temporal, feedback, texture-backed, font-backed, or text-parameter shaders are hidden from the control UI for now. -Runtime shaders are exposed through `RuntimeLayerModel` as display layers with manifest parameter defaults. The model also records whether each layer has a render-ready artifact. Stage 1 add/remove POST controls mutate this app-owned model and may start background shader builds, but multi-layer render-scene handoff is not ported yet. +Runtime shaders are exposed through `RuntimeLayerModel` as display layers with manifest parameter defaults. The model also records whether each layer has a render-ready artifact. Add/remove POST controls mutate this app-owned model and may start background shader builds. + +When a layer becomes render-ready, the app publishes the ready render-layer snapshot to the render thread. The render thread owns the GL-side `RuntimeRenderScene`, diffs that snapshot at a frame boundary, commits/removes GL programs, and renders the ready layers in order. Current layer rendering is still deliberately simple: each stateless full-frame shader draws to the output target using fallback source textures until proper layer-input texture handoff is designed. Successful handoff signs: @@ -260,6 +263,7 @@ This app keeps the same core behavior but splits it into modules that can grow: - `frames/`: system-memory handoff - `platform/`: COM/Win32/hidden GL context support - `render/`: cadence, simple rendering, PBO readback +- `render/RuntimeRenderScene`: render-thread-owned GL scene for ready runtime shader layers - `runtime/`: app-owned shader layer readiness model, runtime Slang build bridge, and completed artifact handoff - `control/`: local HTTP API edge and runtime-state JSON presentation - `json/`: compact JSON serialization helpers diff --git a/apps/RenderCadenceCompositor/app/RenderCadenceApp.h b/apps/RenderCadenceCompositor/app/RenderCadenceApp.h index 527f798..249cbe8 100644 --- a/apps/RenderCadenceCompositor/app/RenderCadenceApp.h +++ b/apps/RenderCadenceCompositor/app/RenderCadenceApp.h @@ -237,7 +237,7 @@ private: Log("runtime-shader", "Starting background Slang build for shader '" + mConfig.runtimeShaderId + "'."); const std::string layerId = FirstRuntimeLayerId(); if (!layerId.empty()) - StartLayerShaderBuild(layerId, mConfig.runtimeShaderId, true); + StartLayerShaderBuild(layerId, mConfig.runtimeShaderId); } void LoadSupportedShaderCatalog() @@ -279,12 +279,16 @@ private: mRuntimeLayerModel.MarkBuildStarted(layerId, message, error); } - void MarkRuntimeBuildReady(const RuntimeShaderArtifact& artifact) + bool MarkRuntimeBuildReady(const RuntimeShaderArtifact& artifact) { std::lock_guard lock(mRuntimeLayerMutex); std::string error; if (!mRuntimeLayerModel.MarkBuildReady(artifact, error)) + { LogWarning("runtime-shader", error); + return false; + } + return true; } void MarkRuntimeBuildFailed(const std::string& message) @@ -308,7 +312,7 @@ private: return mRuntimeLayerModel.FirstLayerId(); } - void StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId, bool submitToRender) + void StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId) { { std::lock_guard lock(mRuntimeLayerMutex); @@ -329,10 +333,9 @@ private: bridgePtr->Start( layerId, shaderId, - [this, submitToRender](const RuntimeShaderArtifact& artifact) { - MarkRuntimeBuildReady(artifact); - if (submitToRender) - mRenderThread.SubmitRuntimeShaderArtifact(artifact); + [this](const RuntimeShaderArtifact& artifact) { + if (MarkRuntimeBuildReady(artifact)) + PublishRuntimeRenderLayers(); }, [this, layerId](const std::string& message) { MarkRuntimeBuildFailedForLayer(layerId, message); @@ -379,7 +382,8 @@ private: return { false, error }; } - StartLayerShaderBuild(layerId, shaderId, false); + Log("runtime-shader", "Layer added: " + layerId + " shader=" + shaderId); + StartLayerShaderBuild(layerId, shaderId); return { true, std::string() }; } @@ -396,10 +400,22 @@ private: return { false, error }; } + Log("runtime-shader", "Layer removed: " + layerId); StopLayerShaderBuild(layerId); + PublishRuntimeRenderLayers(); return { true, std::string() }; } + void PublishRuntimeRenderLayers() + { + std::vector renderLayers; + { + std::lock_guard lock(mRuntimeLayerMutex); + renderLayers = mRuntimeLayerModel.Snapshot().renderLayers; + } + mRenderThread.SubmitRuntimeRenderLayers(renderLayers); + } + static bool ExtractStringField(const std::string& body, const char* fieldName, std::string& value, std::string& error) { JsonValue root; diff --git a/apps/RenderCadenceCompositor/render/RenderThread.cpp b/apps/RenderCadenceCompositor/render/RenderThread.cpp index 8b7e25d..2a5a195 100644 --- a/apps/RenderCadenceCompositor/render/RenderThread.cpp +++ b/apps/RenderCadenceCompositor/render/RenderThread.cpp @@ -6,6 +6,7 @@ #include "../platform/HiddenGlWindow.h" #include "Bgra8ReadbackPipeline.h" #include "GLExtensions.h" +#include "RuntimeRenderScene.h" #include "RuntimeShaderRenderer.h" #include "SimpleMotionRenderer.h" @@ -94,7 +95,7 @@ void RenderThread::ThreadMain() } SimpleMotionRenderer renderer; - RuntimeShaderRenderer runtimeShaderRenderer; + RuntimeRenderScene runtimeRenderScene; Bgra8ReadbackPipeline readback; if (!renderer.InitializeGl(mConfig.width, mConfig.height) || !readback.Initialize(mConfig.width, mConfig.height, mConfig.pboDepth)) { @@ -126,10 +127,10 @@ void RenderThread::ThreadMain() continue; } - TryCommitReadyRuntimeShader(runtimeShaderRenderer); - if (!readback.RenderAndQueue(frameIndex, [this, &renderer, &runtimeShaderRenderer](uint64_t index) { - if (runtimeShaderRenderer.HasProgram()) - runtimeShaderRenderer.RenderFrame(index, mConfig.width, mConfig.height); + 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); })) @@ -157,7 +158,7 @@ void RenderThread::ThreadMain() } readback.Shutdown(); - runtimeShaderRenderer.ShutdownGl(); + runtimeRenderScene.ShutdownGl(); renderer.ShutdownGl(); window.ClearCurrent(); mRunning.store(false, std::memory_order_release); @@ -204,6 +205,13 @@ void RenderThread::SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& arti 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); @@ -216,14 +224,52 @@ bool RenderThread::TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& ar return true; } -void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer) +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; - std::string commitError; - if (!runtimeShaderRenderer.CommitShaderArtifact(artifact, commitError)) + 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, diff --git a/apps/RenderCadenceCompositor/render/RenderThread.h b/apps/RenderCadenceCompositor/render/RenderThread.h index f8b6670..d24c9a0 100644 --- a/apps/RenderCadenceCompositor/render/RenderThread.h +++ b/apps/RenderCadenceCompositor/render/RenderThread.h @@ -1,8 +1,9 @@ #pragma once #include "RenderCadenceClock.h" +#include "../runtime/RuntimeLayerModel.h" #include "../runtime/RuntimeShaderArtifact.h" -#include "RuntimeShaderRenderer.h" +#include "RuntimeRenderScene.h" #include #include @@ -45,6 +46,7 @@ public: bool Start(std::string& error); void Stop(); void SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact); + void SubmitRuntimeRenderLayers(const std::vector& layers); Metrics GetMetrics() const; bool IsRunning() const { return mRunning.load(std::memory_order_acquire); } @@ -56,8 +58,9 @@ private: void CountRendered(); void CountCompleted(); void CountAcquireMiss(); - void TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer); + void TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRenderScene); bool TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact); + bool TryTakePendingRenderLayers(std::vector& layers); SystemFrameExchange& mFrameExchange; Config mConfig; @@ -82,4 +85,8 @@ private: std::mutex mShaderArtifactMutex; bool mHasPendingShaderArtifact = false; RuntimeShaderArtifact mPendingShaderArtifact; + + std::mutex mRenderLayersMutex; + bool mHasPendingRenderLayers = false; + std::vector mPendingRenderLayers; }; diff --git a/apps/RenderCadenceCompositor/render/RuntimeRenderScene.cpp b/apps/RenderCadenceCompositor/render/RuntimeRenderScene.cpp new file mode 100644 index 0000000..5471fba --- /dev/null +++ b/apps/RenderCadenceCompositor/render/RuntimeRenderScene.cpp @@ -0,0 +1,114 @@ +#include "RuntimeRenderScene.h" + +#include +#include +#include + +RuntimeRenderScene::~RuntimeRenderScene() +{ + ShutdownGl(); +} + +bool RuntimeRenderScene::CommitRenderLayers(const std::vector& layers, std::string& error) +{ + std::vector nextOrder; + nextOrder.reserve(layers.size()); + for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers) + nextOrder.push_back(layer.id); + + for (auto layerIt = mLayers.begin(); layerIt != mLayers.end();) + { + const bool stillPresent = std::find(nextOrder.begin(), nextOrder.end(), layerIt->layerId) != nextOrder.end(); + if (stillPresent) + { + ++layerIt; + continue; + } + + if (layerIt->renderer) + layerIt->renderer->ShutdownGl(); + layerIt = mLayers.erase(layerIt); + } + + for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers) + { + if (layer.artifact.fragmentShaderSource.empty()) + continue; + + const std::string fingerprint = Fingerprint(layer.artifact); + LayerProgram* program = FindLayer(layer.id); + if (!program) + { + LayerProgram next; + next.layerId = layer.id; + next.renderer = std::make_unique(); + mLayers.push_back(std::move(next)); + program = &mLayers.back(); + } + + if (program->shaderId == layer.shaderId && program->sourceFingerprint == fingerprint && program->renderer && program->renderer->HasProgram()) + continue; + + std::unique_ptr nextRenderer = std::make_unique(); + if (!nextRenderer->CommitShaderArtifact(layer.artifact, error)) + return false; + + if (program->renderer) + program->renderer->ShutdownGl(); + program->shaderId = layer.shaderId; + program->sourceFingerprint = fingerprint; + program->renderer = std::move(nextRenderer); + } + + mLayerOrder = std::move(nextOrder); + error.clear(); + return true; +} + +void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsigned height) +{ + for (const std::string& layerId : mLayerOrder) + { + LayerProgram* layer = FindLayer(layerId); + if (!layer || !layer->renderer || !layer->renderer->HasProgram()) + continue; + layer->renderer->RenderFrame(frameIndex, width, height); + } +} + +void RuntimeRenderScene::ShutdownGl() +{ + for (LayerProgram& layer : mLayers) + { + if (layer.renderer) + layer.renderer->ShutdownGl(); + } + mLayers.clear(); + mLayerOrder.clear(); +} + +RuntimeRenderScene::LayerProgram* RuntimeRenderScene::FindLayer(const std::string& layerId) +{ + for (LayerProgram& layer : mLayers) + { + if (layer.layerId == layerId) + return &layer; + } + return nullptr; +} + +const RuntimeRenderScene::LayerProgram* RuntimeRenderScene::FindLayer(const std::string& layerId) const +{ + for (const LayerProgram& layer : mLayers) + { + if (layer.layerId == layerId) + return &layer; + } + return nullptr; +} + +std::string RuntimeRenderScene::Fingerprint(const RuntimeShaderArtifact& artifact) +{ + const std::hash hasher; + return artifact.shaderId + ":" + std::to_string(artifact.fragmentShaderSource.size()) + ":" + std::to_string(hasher(artifact.fragmentShaderSource)); +} diff --git a/apps/RenderCadenceCompositor/render/RuntimeRenderScene.h b/apps/RenderCadenceCompositor/render/RuntimeRenderScene.h new file mode 100644 index 0000000..1895b04 --- /dev/null +++ b/apps/RenderCadenceCompositor/render/RuntimeRenderScene.h @@ -0,0 +1,39 @@ +#pragma once + +#include "../runtime/RuntimeLayerModel.h" +#include "RuntimeShaderRenderer.h" + +#include +#include +#include +#include + +class RuntimeRenderScene +{ +public: + RuntimeRenderScene() = default; + RuntimeRenderScene(const RuntimeRenderScene&) = delete; + RuntimeRenderScene& operator=(const RuntimeRenderScene&) = delete; + ~RuntimeRenderScene(); + + bool CommitRenderLayers(const std::vector& layers, std::string& error); + bool HasLayers() const { return !mLayerOrder.empty(); } + void RenderFrame(uint64_t frameIndex, unsigned width, unsigned height); + void ShutdownGl(); + +private: + struct LayerProgram + { + std::string layerId; + std::string shaderId; + std::string sourceFingerprint; + std::unique_ptr renderer; + }; + + LayerProgram* FindLayer(const std::string& layerId); + const LayerProgram* FindLayer(const std::string& layerId) const; + static std::string Fingerprint(const RuntimeShaderArtifact& artifact); + + std::vector mLayers; + std::vector mLayerOrder; +};