Render changes
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m59s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
Aiden
2026-05-12 14:36:36 +10:00
parent 1ddcf5d621
commit d07ea1f63a
7 changed files with 248 additions and 20 deletions

View File

@@ -331,6 +331,8 @@ set(RENDER_CADENCE_APP_SOURCES
"${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderRenderer.h" "${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderRenderer.h"
"${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderParams.cpp" "${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderParams.cpp"
"${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderParams.h" "${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.cpp"
"${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.h" "${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.h"
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeLayerModel.cpp" "${RENDER_CADENCE_APP_DIR}/runtime/RuntimeLayerModel.cpp"

View File

@@ -43,6 +43,7 @@ Included now:
- background Slang compile of `shaders/happy-accident` - background Slang compile of `shaders/happy-accident`
- app-owned display/render layer model for shader build readiness - app-owned display/render layer model for shader build readiness
- app-owned submission of a completed shader artifact - 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 - render-thread-only GL commit once the artifact is ready
- manifest-driven stateless single-pass shader packages - manifest-driven stateless single-pass shader packages
- HTTP shader list populated from supported 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. 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: 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 - `frames/`: system-memory handoff
- `platform/`: COM/Win32/hidden GL context support - `platform/`: COM/Win32/hidden GL context support
- `render/`: cadence, simple rendering, PBO readback - `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 - `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 - `control/`: local HTTP API edge and runtime-state JSON presentation
- `json/`: compact JSON serialization helpers - `json/`: compact JSON serialization helpers

View File

@@ -237,7 +237,7 @@ private:
Log("runtime-shader", "Starting background Slang build for shader '" + mConfig.runtimeShaderId + "'."); Log("runtime-shader", "Starting background Slang build for shader '" + mConfig.runtimeShaderId + "'.");
const std::string layerId = FirstRuntimeLayerId(); const std::string layerId = FirstRuntimeLayerId();
if (!layerId.empty()) if (!layerId.empty())
StartLayerShaderBuild(layerId, mConfig.runtimeShaderId, true); StartLayerShaderBuild(layerId, mConfig.runtimeShaderId);
} }
void LoadSupportedShaderCatalog() void LoadSupportedShaderCatalog()
@@ -279,12 +279,16 @@ private:
mRuntimeLayerModel.MarkBuildStarted(layerId, message, error); mRuntimeLayerModel.MarkBuildStarted(layerId, message, error);
} }
void MarkRuntimeBuildReady(const RuntimeShaderArtifact& artifact) bool MarkRuntimeBuildReady(const RuntimeShaderArtifact& artifact)
{ {
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex); std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
std::string error; std::string error;
if (!mRuntimeLayerModel.MarkBuildReady(artifact, error)) if (!mRuntimeLayerModel.MarkBuildReady(artifact, error))
{
LogWarning("runtime-shader", error); LogWarning("runtime-shader", error);
return false;
}
return true;
} }
void MarkRuntimeBuildFailed(const std::string& message) void MarkRuntimeBuildFailed(const std::string& message)
@@ -308,7 +312,7 @@ private:
return mRuntimeLayerModel.FirstLayerId(); 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<std::mutex> lock(mRuntimeLayerMutex); std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
@@ -329,10 +333,9 @@ private:
bridgePtr->Start( bridgePtr->Start(
layerId, layerId,
shaderId, shaderId,
[this, submitToRender](const RuntimeShaderArtifact& artifact) { [this](const RuntimeShaderArtifact& artifact) {
MarkRuntimeBuildReady(artifact); if (MarkRuntimeBuildReady(artifact))
if (submitToRender) PublishRuntimeRenderLayers();
mRenderThread.SubmitRuntimeShaderArtifact(artifact);
}, },
[this, layerId](const std::string& message) { [this, layerId](const std::string& message) {
MarkRuntimeBuildFailedForLayer(layerId, message); MarkRuntimeBuildFailedForLayer(layerId, message);
@@ -379,7 +382,8 @@ private:
return { false, error }; return { false, error };
} }
StartLayerShaderBuild(layerId, shaderId, false); Log("runtime-shader", "Layer added: " + layerId + " shader=" + shaderId);
StartLayerShaderBuild(layerId, shaderId);
return { true, std::string() }; return { true, std::string() };
} }
@@ -396,10 +400,22 @@ private:
return { false, error }; return { false, error };
} }
Log("runtime-shader", "Layer removed: " + layerId);
StopLayerShaderBuild(layerId); StopLayerShaderBuild(layerId);
PublishRuntimeRenderLayers();
return { true, std::string() }; return { true, std::string() };
} }
void PublishRuntimeRenderLayers()
{
std::vector<RuntimeRenderLayerModel> renderLayers;
{
std::lock_guard<std::mutex> 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) static bool ExtractStringField(const std::string& body, const char* fieldName, std::string& value, std::string& error)
{ {
JsonValue root; JsonValue root;

View File

@@ -6,6 +6,7 @@
#include "../platform/HiddenGlWindow.h" #include "../platform/HiddenGlWindow.h"
#include "Bgra8ReadbackPipeline.h" #include "Bgra8ReadbackPipeline.h"
#include "GLExtensions.h" #include "GLExtensions.h"
#include "RuntimeRenderScene.h"
#include "RuntimeShaderRenderer.h" #include "RuntimeShaderRenderer.h"
#include "SimpleMotionRenderer.h" #include "SimpleMotionRenderer.h"
@@ -94,7 +95,7 @@ void RenderThread::ThreadMain()
} }
SimpleMotionRenderer renderer; SimpleMotionRenderer renderer;
RuntimeShaderRenderer runtimeShaderRenderer; RuntimeRenderScene runtimeRenderScene;
Bgra8ReadbackPipeline readback; Bgra8ReadbackPipeline readback;
if (!renderer.InitializeGl(mConfig.width, mConfig.height) || !readback.Initialize(mConfig.width, mConfig.height, mConfig.pboDepth)) if (!renderer.InitializeGl(mConfig.width, mConfig.height) || !readback.Initialize(mConfig.width, mConfig.height, mConfig.pboDepth))
{ {
@@ -126,10 +127,10 @@ void RenderThread::ThreadMain()
continue; continue;
} }
TryCommitReadyRuntimeShader(runtimeShaderRenderer); TryCommitReadyRuntimeShader(runtimeRenderScene);
if (!readback.RenderAndQueue(frameIndex, [this, &renderer, &runtimeShaderRenderer](uint64_t index) { if (!readback.RenderAndQueue(frameIndex, [this, &renderer, &runtimeRenderScene](uint64_t index) {
if (runtimeShaderRenderer.HasProgram()) if (runtimeRenderScene.HasLayers())
runtimeShaderRenderer.RenderFrame(index, mConfig.width, mConfig.height); runtimeRenderScene.RenderFrame(index, mConfig.width, mConfig.height);
else else
renderer.RenderFrame(index); renderer.RenderFrame(index);
})) }))
@@ -157,7 +158,7 @@ void RenderThread::ThreadMain()
} }
readback.Shutdown(); readback.Shutdown();
runtimeShaderRenderer.ShutdownGl(); runtimeRenderScene.ShutdownGl();
renderer.ShutdownGl(); renderer.ShutdownGl();
window.ClearCurrent(); window.ClearCurrent();
mRunning.store(false, std::memory_order_release); mRunning.store(false, std::memory_order_release);
@@ -204,6 +205,13 @@ void RenderThread::SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& arti
mHasPendingShaderArtifact = true; 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) bool RenderThread::TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact)
{ {
std::lock_guard<std::mutex> lock(mShaderArtifactMutex); std::lock_guard<std::mutex> lock(mShaderArtifactMutex);
@@ -216,14 +224,52 @@ bool RenderThread::TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& ar
return true; return true;
} }
void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer) 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; RuntimeShaderArtifact artifact;
if (!TryTakePendingRuntimeShaderArtifact(artifact)) if (!TryTakePendingRuntimeShaderArtifact(artifact))
return; return;
std::string commitError; RenderCadenceCompositor::RuntimeRenderLayerModel layer;
if (!runtimeShaderRenderer.CommitShaderArtifact(artifact, commitError)) 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::TryLog(
RenderCadenceCompositor::LogLevel::Error, RenderCadenceCompositor::LogLevel::Error,

View File

@@ -1,8 +1,9 @@
#pragma once #pragma once
#include "RenderCadenceClock.h" #include "RenderCadenceClock.h"
#include "../runtime/RuntimeLayerModel.h"
#include "../runtime/RuntimeShaderArtifact.h" #include "../runtime/RuntimeShaderArtifact.h"
#include "RuntimeShaderRenderer.h" #include "RuntimeRenderScene.h"
#include <atomic> #include <atomic>
#include <condition_variable> #include <condition_variable>
@@ -45,6 +46,7 @@ public:
bool Start(std::string& error); bool Start(std::string& error);
void Stop(); void Stop();
void SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact); void SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact);
void SubmitRuntimeRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers);
Metrics GetMetrics() const; Metrics GetMetrics() const;
bool IsRunning() const { return mRunning.load(std::memory_order_acquire); } bool IsRunning() const { return mRunning.load(std::memory_order_acquire); }
@@ -56,8 +58,9 @@ private:
void CountRendered(); void CountRendered();
void CountCompleted(); void CountCompleted();
void CountAcquireMiss(); void CountAcquireMiss();
void TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer); void TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRenderScene);
bool TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact); bool TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact);
bool TryTakePendingRenderLayers(std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers);
SystemFrameExchange& mFrameExchange; SystemFrameExchange& mFrameExchange;
Config mConfig; Config mConfig;
@@ -82,4 +85,8 @@ private:
std::mutex mShaderArtifactMutex; std::mutex mShaderArtifactMutex;
bool mHasPendingShaderArtifact = false; bool mHasPendingShaderArtifact = false;
RuntimeShaderArtifact mPendingShaderArtifact; RuntimeShaderArtifact mPendingShaderArtifact;
std::mutex mRenderLayersMutex;
bool mHasPendingRenderLayers = false;
std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> mPendingRenderLayers;
}; };

View File

@@ -0,0 +1,114 @@
#include "RuntimeRenderScene.h"
#include <algorithm>
#include <functional>
#include <utility>
RuntimeRenderScene::~RuntimeRenderScene()
{
ShutdownGl();
}
bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers, std::string& error)
{
std::vector<std::string> 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<RuntimeShaderRenderer>();
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<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
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<std::string> hasher;
return artifact.shaderId + ":" + std::to_string(artifact.fragmentShaderSource.size()) + ":" + std::to_string(hasher(artifact.fragmentShaderSource));
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include "../runtime/RuntimeLayerModel.h"
#include "RuntimeShaderRenderer.h"
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
class RuntimeRenderScene
{
public:
RuntimeRenderScene() = default;
RuntimeRenderScene(const RuntimeRenderScene&) = delete;
RuntimeRenderScene& operator=(const RuntimeRenderScene&) = delete;
~RuntimeRenderScene();
bool CommitRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& 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<RuntimeShaderRenderer> renderer;
};
LayerProgram* FindLayer(const std::string& layerId);
const LayerProgram* FindLayer(const std::string& layerId) const;
static std::string Fingerprint(const RuntimeShaderArtifact& artifact);
std::vector<LayerProgram> mLayers;
std::vector<std::string> mLayerOrder;
};