reload no longer can disrupt the renderer

This commit is contained in:
2026-05-21 17:30:09 +10:00
parent 5cf1a09e75
commit f9aac85e5f
6 changed files with 118 additions and 29 deletions

View File

@@ -47,7 +47,7 @@ private:
void RequestRuntimeStatePersistence(); void RequestRuntimeStatePersistence();
void RequestRuntimeStatePersistenceLocked(); void RequestRuntimeStatePersistenceLocked();
std::filesystem::path ResolveRuntimeStatePath() const; 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 RetireLayerShaderBuild(const std::string& layerId); void RetireLayerShaderBuild(const std::string& layerId);
void CleanupRetiredShaderBuilds(); void CleanupRetiredShaderBuilds();
void StopAllRuntimeShaderBuilds(); void StopAllRuntimeShaderBuilds();

View File

@@ -107,7 +107,7 @@ void RuntimeLayerController::RequestRuntimeStatePersistenceLocked()
mPersistenceWriter.RequestSave(mRuntimeLayerModel.Snapshot()); 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(); CleanupRetiredShaderBuilds();
RetireLayerShaderBuild(layerId); RetireLayerShaderBuild(layerId);
@@ -115,7 +115,7 @@ void RuntimeLayerController::StartLayerShaderBuild(const std::string& layerId, c
{ {
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex); std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
std::string error; 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);
} }
auto bridge = std::make_unique<RuntimeShaderBridge>(); auto bridge = std::make_unique<RuntimeShaderBridge>();

View File

@@ -154,9 +154,8 @@ ControlActionResult RuntimeLayerController::HandleControlCommand(const RuntimeCo
for (const auto& build : buildsToStart) for (const auto& build : buildsToStart)
{ {
Log("runtime-shader", "Reload queued shader rebuild: " + build.first + " shader=" + build.second); 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() }; return { true, std::string() };
} }
case RuntimeControlCommandType::Unsupported: case RuntimeControlCommandType::Unsupported:

View File

@@ -402,12 +402,6 @@ bool RuntimeLayerModel::ReloadFromCatalog(const SupportedShaderCatalog& shaderCa
layer.packageFingerprint = nextFingerprint; layer.packageFingerprint = nextFingerprint;
layer.parameterDefinitions = shaderPackage->parameters; layer.parameterDefinitions = shaderPackage->parameters;
layer.parameterValues = std::move(nextValues); 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 }); buildsToStart.push_back({ layer.id, layer.shaderId });
} }
@@ -420,7 +414,7 @@ void RuntimeLayerModel::Clear()
mLayers.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); Layer* layer = FindLayer(layerId);
if (!layer) if (!layer)
@@ -431,8 +425,12 @@ bool RuntimeLayerModel::MarkBuildStarted(const std::string& layerId, const std::
layer->buildState = RuntimeLayerBuildState::Pending; layer->buildState = RuntimeLayerBuildState::Pending;
layer->message = message; layer->message = message;
layer->renderReady = false; layer->preserveRenderDuringBuild = preserveExistingRenderArtifact && layer->renderReady;
layer->artifact = RuntimeShaderArtifact(); if (!layer->preserveRenderDuringBuild)
{
layer->renderReady = false;
layer->artifact = RuntimeShaderArtifact();
}
error.clear(); error.clear();
return true; return true;
} }
@@ -448,21 +446,28 @@ bool RuntimeLayerModel::MarkBuildReady(const RuntimeShaderArtifact& artifact, st
return false; 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->shaderName = artifact.displayName.empty() ? artifact.shaderId : artifact.displayName;
layer->packageFingerprint = artifact.packageFingerprint; layer->packageFingerprint = artifact.packageFingerprint;
layer->buildState = RuntimeLayerBuildState::Ready; layer->buildState = RuntimeLayerBuildState::Ready;
layer->message = artifact.message; layer->message = artifact.message;
layer->renderReady = true; layer->renderReady = true;
layer->artifact = artifact; layer->preserveRenderDuringBuild = false;
layer->artifact.parameterValues = layer->parameterValues; layer->artifact = std::move(nextArtifact);
if (!PrepareRuntimeTextTextures(layer->artifact, error))
{
layer->buildState = RuntimeLayerBuildState::Failed;
layer->message = error;
layer->renderReady = false;
layer->artifact = RuntimeShaderArtifact();
return false;
}
error.clear(); error.clear();
return true; return true;
} }
@@ -488,8 +493,12 @@ bool RuntimeLayerModel::MarkBuildFailed(const std::string& layerId, const std::s
layer->buildState = RuntimeLayerBuildState::Failed; layer->buildState = RuntimeLayerBuildState::Failed;
layer->message = message; layer->message = message;
layer->renderReady = false; if (!layer->preserveRenderDuringBuild)
layer->artifact = RuntimeShaderArtifact(); {
layer->renderReady = false;
layer->artifact = RuntimeShaderArtifact();
}
layer->preserveRenderDuringBuild = false;
error.clear(); error.clear();
return true; return true;
} }
@@ -515,10 +524,11 @@ RuntimeLayerModelSnapshot RuntimeLayerModel::Snapshot() const
{ {
RuntimeRenderLayerModel renderLayer; RuntimeRenderLayerModel renderLayer;
renderLayer.id = layer.id; renderLayer.id = layer.id;
renderLayer.shaderId = layer.shaderId;
renderLayer.bypass = layer.bypass; renderLayer.bypass = layer.bypass;
renderLayer.artifact = layer.artifact; 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(); renderLayer.artifact.fontAtlases.clear();
snapshot.renderLayers.push_back(std::move(renderLayer)); snapshot.renderLayers.push_back(std::move(renderLayer));
} }

View File

@@ -64,7 +64,7 @@ public:
bool UpdateParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& value, std::string& error); 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 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 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 MarkBuildReady(const RuntimeShaderArtifact& artifact, std::string& error);
bool MarkBuildFailedForShader(const std::string& shaderId, const std::string& message); bool MarkBuildFailedForShader(const std::string& shaderId, const std::string& message);
bool MarkBuildFailed(const std::string& layerId, const std::string& message, std::string& error); bool MarkBuildFailed(const std::string& layerId, const std::string& message, std::string& error);
@@ -85,6 +85,7 @@ private:
RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending; RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending;
std::string message; std::string message;
bool renderReady = false; bool renderReady = false;
bool preserveRenderDuringBuild = false;
std::vector<ShaderParameterDefinition> parameterDefinitions; std::vector<ShaderParameterDefinition> parameterDefinitions;
std::map<std::string, ShaderParameterValue> parameterValues; std::map<std::string, ShaderParameterValue> parameterValues;
RuntimeShaderArtifact artifact; RuntimeShaderArtifact artifact;

View File

@@ -405,6 +405,84 @@ void TestReloadRefreshesChangedShaderMetadataAndPreservesValues()
std::filesystem::remove_all(root); 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() void TestTextTexturesArePreparedInRuntimeModel()
{ {
std::filesystem::path root = MakeTestRoot(); std::filesystem::path root = MakeTestRoot();
@@ -456,6 +534,7 @@ int main()
TestInvalidRuntimeStateCanFallBackToConfiguredShader(); TestInvalidRuntimeStateCanFallBackToConfiguredShader();
TestLayerControlsUpdateDisplayAndRenderModels(); TestLayerControlsUpdateDisplayAndRenderModels();
TestReloadRefreshesChangedShaderMetadataAndPreservesValues(); TestReloadRefreshesChangedShaderMetadataAndPreservesValues();
TestReloadRebuildKeepsLastGoodRenderArtifacts();
TestTextTexturesArePreparedInRuntimeModel(); TestTextTexturesArePreparedInRuntimeModel();
if (gFailures != 0) if (gFailures != 0)