Compare commits
2 Commits
5cf1a09e75
...
4096e9c26a
| Author | SHA1 | Date | |
|---|---|---|---|
| 4096e9c26a | |||
| f9aac85e5f |
@@ -8,6 +8,7 @@
|
||||
#include "../runtime/SupportedShaderCatalog.h"
|
||||
#include "../telemetry/CadenceTelemetry.h"
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
@@ -47,7 +48,10 @@ private:
|
||||
void RequestRuntimeStatePersistence();
|
||||
void RequestRuntimeStatePersistenceLocked();
|
||||
std::filesystem::path ResolveRuntimeStatePath() const;
|
||||
void StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId);
|
||||
void StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId, bool preserveExistingRenderArtifact = false);
|
||||
void PumpShaderBuildQueue();
|
||||
void StartQueuedShaderBuild(const std::string& layerId, const std::string& shaderId);
|
||||
void CompleteLayerShaderBuild(const std::string& layerId);
|
||||
void RetireLayerShaderBuild(const std::string& layerId);
|
||||
void CleanupRetiredShaderBuilds();
|
||||
void StopAllRuntimeShaderBuilds();
|
||||
@@ -67,6 +71,12 @@ private:
|
||||
mutable std::mutex mRuntimeLayerMutex;
|
||||
RuntimeLayerModel mRuntimeLayerModel;
|
||||
std::mutex mShaderBuildMutex;
|
||||
struct QueuedShaderBuild
|
||||
{
|
||||
std::string layerId;
|
||||
std::string shaderId;
|
||||
};
|
||||
std::deque<QueuedShaderBuild> mQueuedShaderBuilds;
|
||||
std::map<std::string, std::unique_ptr<RuntimeShaderBridge>> mShaderBuilds;
|
||||
std::vector<std::unique_ptr<RuntimeShaderBridge>> mRetiredShaderBuilds;
|
||||
};
|
||||
|
||||
@@ -7,9 +7,15 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::size_t kMaxConcurrentRuntimeShaderBuilds = 2;
|
||||
}
|
||||
|
||||
bool RuntimeLayerController::LoadSupportedShaderCatalog(const std::string& shaderLibrary, unsigned maxTemporalHistoryFrames)
|
||||
{
|
||||
const std::filesystem::path shaderRoot = FindRepoPath(shaderLibrary);
|
||||
@@ -107,7 +113,7 @@ void RuntimeLayerController::RequestRuntimeStatePersistenceLocked()
|
||||
mPersistenceWriter.RequestSave(mRuntimeLayerModel.Snapshot());
|
||||
}
|
||||
|
||||
void RuntimeLayerController::StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId)
|
||||
void RuntimeLayerController::StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId, bool preserveExistingRenderArtifact)
|
||||
{
|
||||
CleanupRetiredShaderBuilds();
|
||||
RetireLayerShaderBuild(layerId);
|
||||
@@ -115,34 +121,94 @@ void RuntimeLayerController::StartLayerShaderBuild(const std::string& layerId, c
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||
std::string error;
|
||||
mRuntimeLayerModel.MarkBuildStarted(layerId, "Runtime Slang build started for shader '" + shaderId + "'.", error);
|
||||
mRuntimeLayerModel.MarkBuildStarted(layerId, "Runtime Slang build started for shader '" + shaderId + "'.", error, preserveExistingRenderArtifact);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
|
||||
mQueuedShaderBuilds.push_back(QueuedShaderBuild{ layerId, shaderId });
|
||||
}
|
||||
PumpShaderBuildQueue();
|
||||
}
|
||||
|
||||
void RuntimeLayerController::PumpShaderBuildQueue()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
QueuedShaderBuild build;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
|
||||
if (mShaderBuilds.size() >= kMaxConcurrentRuntimeShaderBuilds || mQueuedShaderBuilds.empty())
|
||||
return;
|
||||
build = mQueuedShaderBuilds.front();
|
||||
mQueuedShaderBuilds.pop_front();
|
||||
if (mShaderBuilds.find(build.layerId) != mShaderBuilds.end())
|
||||
continue;
|
||||
}
|
||||
|
||||
StartQueuedShaderBuild(build.layerId, build.shaderId);
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeLayerController::StartQueuedShaderBuild(const std::string& layerId, const std::string& shaderId)
|
||||
{
|
||||
auto bridge = std::make_unique<RuntimeShaderBridge>();
|
||||
RuntimeShaderBridge* bridgePtr = bridge.get();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
|
||||
if (mShaderBuilds.size() >= kMaxConcurrentRuntimeShaderBuilds || mShaderBuilds.find(layerId) != mShaderBuilds.end())
|
||||
{
|
||||
mQueuedShaderBuilds.push_front(QueuedShaderBuild{ layerId, shaderId });
|
||||
return;
|
||||
}
|
||||
mShaderBuilds[layerId] = std::move(bridge);
|
||||
}
|
||||
|
||||
bridgePtr->Start(
|
||||
layerId,
|
||||
shaderId,
|
||||
[this](const RuntimeShaderArtifact& artifact) {
|
||||
[this, layerId](const RuntimeShaderArtifact& artifact) {
|
||||
if (MarkRuntimeBuildReady(artifact))
|
||||
PublishRuntimeRenderLayers();
|
||||
CompleteLayerShaderBuild(layerId);
|
||||
},
|
||||
[this, layerId](const std::string& message) {
|
||||
MarkRuntimeBuildFailedForLayer(layerId, message);
|
||||
LogError("runtime-shader", "Runtime Slang build failed: " + message);
|
||||
CompleteLayerShaderBuild(layerId);
|
||||
});
|
||||
}
|
||||
|
||||
void RuntimeLayerController::CompleteLayerShaderBuild(const std::string& layerId)
|
||||
{
|
||||
std::unique_ptr<RuntimeShaderBridge> bridge;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
|
||||
auto bridgeIt = mShaderBuilds.find(layerId);
|
||||
if (bridgeIt != mShaderBuilds.end())
|
||||
{
|
||||
bridge = std::move(bridgeIt->second);
|
||||
mShaderBuilds.erase(bridgeIt);
|
||||
mRetiredShaderBuilds.push_back(std::move(bridge));
|
||||
}
|
||||
}
|
||||
PumpShaderBuildQueue();
|
||||
}
|
||||
|
||||
void RuntimeLayerController::RetireLayerShaderBuild(const std::string& layerId)
|
||||
{
|
||||
std::unique_ptr<RuntimeShaderBridge> bridge;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
|
||||
mQueuedShaderBuilds.erase(
|
||||
std::remove_if(
|
||||
mQueuedShaderBuilds.begin(),
|
||||
mQueuedShaderBuilds.end(),
|
||||
[&layerId](const QueuedShaderBuild& build) {
|
||||
return build.layerId == layerId;
|
||||
}),
|
||||
mQueuedShaderBuilds.end());
|
||||
|
||||
auto bridgeIt = mShaderBuilds.find(layerId);
|
||||
if (bridgeIt == mShaderBuilds.end())
|
||||
return;
|
||||
@@ -180,6 +246,7 @@ void RuntimeLayerController::StopAllRuntimeShaderBuilds()
|
||||
std::vector<std::unique_ptr<RuntimeShaderBridge>> retiredBuilds;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
|
||||
mQueuedShaderBuilds.clear();
|
||||
builds.swap(mShaderBuilds);
|
||||
retiredBuilds.swap(mRetiredShaderBuilds);
|
||||
}
|
||||
|
||||
@@ -154,9 +154,8 @@ ControlActionResult RuntimeLayerController::HandleControlCommand(const RuntimeCo
|
||||
for (const auto& build : buildsToStart)
|
||||
{
|
||||
Log("runtime-shader", "Reload queued shader rebuild: " + build.first + " shader=" + build.second);
|
||||
StartLayerShaderBuild(build.first, build.second);
|
||||
StartLayerShaderBuild(build.first, build.second, true);
|
||||
}
|
||||
PublishRuntimeRenderLayers();
|
||||
return { true, std::string() };
|
||||
}
|
||||
case RuntimeControlCommandType::Unsupported:
|
||||
|
||||
@@ -402,12 +402,6 @@ bool RuntimeLayerModel::ReloadFromCatalog(const SupportedShaderCatalog& shaderCa
|
||||
layer.packageFingerprint = nextFingerprint;
|
||||
layer.parameterDefinitions = shaderPackage->parameters;
|
||||
layer.parameterValues = std::move(nextValues);
|
||||
if (layer.renderReady)
|
||||
{
|
||||
layer.artifact.parameterValues = layer.parameterValues;
|
||||
std::string prepareError;
|
||||
PrepareRuntimeTextTextures(layer.artifact, prepareError);
|
||||
}
|
||||
buildsToStart.push_back({ layer.id, layer.shaderId });
|
||||
}
|
||||
|
||||
@@ -420,7 +414,7 @@ void RuntimeLayerModel::Clear()
|
||||
mLayers.clear();
|
||||
}
|
||||
|
||||
bool RuntimeLayerModel::MarkBuildStarted(const std::string& layerId, const std::string& message, std::string& error)
|
||||
bool RuntimeLayerModel::MarkBuildStarted(const std::string& layerId, const std::string& message, std::string& error, bool preserveExistingRenderArtifact)
|
||||
{
|
||||
Layer* layer = FindLayer(layerId);
|
||||
if (!layer)
|
||||
@@ -431,8 +425,12 @@ bool RuntimeLayerModel::MarkBuildStarted(const std::string& layerId, const std::
|
||||
|
||||
layer->buildState = RuntimeLayerBuildState::Pending;
|
||||
layer->message = message;
|
||||
layer->renderReady = false;
|
||||
layer->artifact = RuntimeShaderArtifact();
|
||||
layer->preserveRenderDuringBuild = preserveExistingRenderArtifact && layer->renderReady;
|
||||
if (!layer->preserveRenderDuringBuild)
|
||||
{
|
||||
layer->renderReady = false;
|
||||
layer->artifact = RuntimeShaderArtifact();
|
||||
}
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
@@ -448,21 +446,28 @@ bool RuntimeLayerModel::MarkBuildReady(const RuntimeShaderArtifact& artifact, st
|
||||
return false;
|
||||
}
|
||||
|
||||
RuntimeShaderArtifact nextArtifact = artifact;
|
||||
nextArtifact.parameterValues = layer->parameterValues;
|
||||
if (!PrepareRuntimeTextTextures(nextArtifact, error))
|
||||
{
|
||||
layer->buildState = RuntimeLayerBuildState::Failed;
|
||||
layer->message = error;
|
||||
if (!layer->preserveRenderDuringBuild)
|
||||
{
|
||||
layer->renderReady = false;
|
||||
layer->artifact = RuntimeShaderArtifact();
|
||||
}
|
||||
layer->preserveRenderDuringBuild = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
layer->shaderName = artifact.displayName.empty() ? artifact.shaderId : artifact.displayName;
|
||||
layer->packageFingerprint = artifact.packageFingerprint;
|
||||
layer->buildState = RuntimeLayerBuildState::Ready;
|
||||
layer->message = artifact.message;
|
||||
layer->renderReady = true;
|
||||
layer->artifact = artifact;
|
||||
layer->artifact.parameterValues = layer->parameterValues;
|
||||
if (!PrepareRuntimeTextTextures(layer->artifact, error))
|
||||
{
|
||||
layer->buildState = RuntimeLayerBuildState::Failed;
|
||||
layer->message = error;
|
||||
layer->renderReady = false;
|
||||
layer->artifact = RuntimeShaderArtifact();
|
||||
return false;
|
||||
}
|
||||
layer->preserveRenderDuringBuild = false;
|
||||
layer->artifact = std::move(nextArtifact);
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
@@ -488,8 +493,12 @@ bool RuntimeLayerModel::MarkBuildFailed(const std::string& layerId, const std::s
|
||||
|
||||
layer->buildState = RuntimeLayerBuildState::Failed;
|
||||
layer->message = message;
|
||||
layer->renderReady = false;
|
||||
layer->artifact = RuntimeShaderArtifact();
|
||||
if (!layer->preserveRenderDuringBuild)
|
||||
{
|
||||
layer->renderReady = false;
|
||||
layer->artifact = RuntimeShaderArtifact();
|
||||
}
|
||||
layer->preserveRenderDuringBuild = false;
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
@@ -515,10 +524,11 @@ RuntimeLayerModelSnapshot RuntimeLayerModel::Snapshot() const
|
||||
{
|
||||
RuntimeRenderLayerModel renderLayer;
|
||||
renderLayer.id = layer.id;
|
||||
renderLayer.shaderId = layer.shaderId;
|
||||
renderLayer.bypass = layer.bypass;
|
||||
renderLayer.artifact = layer.artifact;
|
||||
renderLayer.artifact.parameterValues = layer.parameterValues;
|
||||
renderLayer.shaderId = renderLayer.artifact.shaderId.empty() ? layer.shaderId : renderLayer.artifact.shaderId;
|
||||
if (layer.buildState == RuntimeLayerBuildState::Ready)
|
||||
renderLayer.artifact.parameterValues = layer.parameterValues;
|
||||
renderLayer.artifact.fontAtlases.clear();
|
||||
snapshot.renderLayers.push_back(std::move(renderLayer));
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
bool UpdateParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& value, std::string& error);
|
||||
bool ResetParameters(const std::string& layerId, std::string& error);
|
||||
bool ReloadFromCatalog(const SupportedShaderCatalog& shaderCatalog, std::vector<std::pair<std::string, std::string>>& buildsToStart, std::string& error);
|
||||
bool MarkBuildStarted(const std::string& layerId, const std::string& message, std::string& error);
|
||||
bool MarkBuildStarted(const std::string& layerId, const std::string& message, std::string& error, bool preserveExistingRenderArtifact = false);
|
||||
bool MarkBuildReady(const RuntimeShaderArtifact& artifact, std::string& error);
|
||||
bool MarkBuildFailedForShader(const std::string& shaderId, const std::string& message);
|
||||
bool MarkBuildFailed(const std::string& layerId, const std::string& message, std::string& error);
|
||||
@@ -85,6 +85,7 @@ private:
|
||||
RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending;
|
||||
std::string message;
|
||||
bool renderReady = false;
|
||||
bool preserveRenderDuringBuild = false;
|
||||
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
||||
RuntimeShaderArtifact artifact;
|
||||
|
||||
@@ -405,6 +405,84 @@ void TestReloadRefreshesChangedShaderMetadataAndPreservesValues()
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
|
||||
RuntimeShaderArtifact MakeReadyArtifact(
|
||||
const RenderCadenceCompositor::SupportedShaderCatalog& catalog,
|
||||
const std::string& layerId,
|
||||
const std::string& shaderId,
|
||||
const std::string& sourceToken)
|
||||
{
|
||||
const ShaderPackage* shaderPackage = catalog.FindPackage(shaderId);
|
||||
RuntimeShaderArtifact artifact;
|
||||
artifact.layerId = layerId;
|
||||
artifact.shaderId = shaderId;
|
||||
artifact.displayName = shaderPackage ? shaderPackage->displayName : shaderId;
|
||||
artifact.packageFingerprint = shaderPackage ? RenderCadenceCompositor::ShaderPackageFingerprint(*shaderPackage) : sourceToken;
|
||||
artifact.fragmentShaderSource = "void main(){/*" + sourceToken + "*/}";
|
||||
if (shaderPackage)
|
||||
artifact.parameterDefinitions = shaderPackage->parameters;
|
||||
artifact.message = "build ready";
|
||||
return artifact;
|
||||
}
|
||||
|
||||
void TestReloadRebuildKeepsLastGoodRenderArtifacts()
|
||||
{
|
||||
std::filesystem::path root;
|
||||
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
||||
WriteFile(root / "passthrough" / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 1.0, 1.0); }\n");
|
||||
WriteFile(root / "passthrough" / "shader.json", R"({
|
||||
"id": "passthrough",
|
||||
"name": "Passthrough",
|
||||
"description": "Passthrough test shader",
|
||||
"category": "Tests",
|
||||
"entryPoint": "shadeVideo",
|
||||
"parameters": [
|
||||
{ "id": "opacity", "label": "Opacity", "type": "float", "default": 1.0 }
|
||||
]
|
||||
})");
|
||||
catalog = LoadCatalog(root);
|
||||
|
||||
RenderCadenceCompositor::RuntimeLayerModel model;
|
||||
std::string error;
|
||||
std::string solidLayerId;
|
||||
std::string passthroughLayerId;
|
||||
Expect(model.AddLayer(catalog, "solid", solidLayerId, error), "reload preserve solid layer can be added");
|
||||
Expect(model.AddLayer(catalog, "passthrough", passthroughLayerId, error), "reload preserve passthrough layer can be added");
|
||||
Expect(model.MarkBuildReady(MakeReadyArtifact(catalog, solidLayerId, "solid", "solid-old"), error), "solid layer starts render-ready");
|
||||
Expect(model.MarkBuildReady(MakeReadyArtifact(catalog, passthroughLayerId, "passthrough", "passthrough-old"), error), "passthrough layer starts render-ready");
|
||||
|
||||
RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot();
|
||||
Expect(snapshot.renderLayers.size() == 2, "ready stack exposes both render layers before reload");
|
||||
const std::string oldSolidSource = snapshot.renderLayers[0].artifact.fragmentShaderSource;
|
||||
const std::string oldPassthroughSource = snapshot.renderLayers[1].artifact.fragmentShaderSource;
|
||||
|
||||
WriteFile(root / "solid" / "shader.json", SolidShaderManifest(0.25, true));
|
||||
RenderCadenceCompositor::SupportedShaderCatalog reloadedCatalog = LoadCatalog(root);
|
||||
std::vector<std::pair<std::string, std::string>> buildsToStart;
|
||||
Expect(model.ReloadFromCatalog(reloadedCatalog, buildsToStart, error), "reload preserve refreshes catalog");
|
||||
for (const auto& build : buildsToStart)
|
||||
Expect(model.MarkBuildStarted(build.first, "reload build started", error, true), "reload build preserves last good artifact");
|
||||
|
||||
snapshot = model.Snapshot();
|
||||
Expect(snapshot.displayLayers[0].buildState == RenderCadenceCompositor::RuntimeLayerBuildState::Pending, "reload marks first layer pending");
|
||||
Expect(snapshot.renderLayers.size() == 2, "pending reload keeps full render stack");
|
||||
Expect(snapshot.renderLayers[0].artifact.fragmentShaderSource == oldSolidSource, "pending reload keeps old first layer artifact");
|
||||
Expect(snapshot.renderLayers[1].artifact.fragmentShaderSource == oldPassthroughSource, "pending reload keeps old second layer artifact");
|
||||
|
||||
Expect(model.MarkBuildFailed(solidLayerId, "reload compile failed", error), "failed reload marks layer failed");
|
||||
snapshot = model.Snapshot();
|
||||
Expect(!snapshot.compileSucceeded, "failed reload reports compile failure");
|
||||
Expect(snapshot.renderLayers.size() == 2, "failed reload keeps last good render stack");
|
||||
Expect(snapshot.renderLayers[0].artifact.fragmentShaderSource == oldSolidSource, "failed reload keeps old failed layer artifact");
|
||||
|
||||
Expect(model.MarkBuildReady(MakeReadyArtifact(reloadedCatalog, passthroughLayerId, "passthrough", "passthrough-new"), error), "other reload layer can commit");
|
||||
snapshot = model.Snapshot();
|
||||
Expect(snapshot.renderLayers.size() == 2, "partial reload commit still keeps complete render stack");
|
||||
Expect(snapshot.renderLayers[0].artifact.fragmentShaderSource == oldSolidSource, "partial reload commit keeps old failed layer artifact");
|
||||
Expect(snapshot.renderLayers[1].artifact.fragmentShaderSource.find("passthrough-new") != std::string::npos, "partial reload commit updates ready layer");
|
||||
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
|
||||
void TestTextTexturesArePreparedInRuntimeModel()
|
||||
{
|
||||
std::filesystem::path root = MakeTestRoot();
|
||||
@@ -456,6 +534,7 @@ int main()
|
||||
TestInvalidRuntimeStateCanFallBackToConfiguredShader();
|
||||
TestLayerControlsUpdateDisplayAndRenderModels();
|
||||
TestReloadRefreshesChangedShaderMetadataAndPreservesValues();
|
||||
TestReloadRebuildKeepsLastGoodRenderArtifacts();
|
||||
TestTextTexturesArePreparedInRuntimeModel();
|
||||
|
||||
if (gFailures != 0)
|
||||
|
||||
Reference in New Issue
Block a user