218 lines
9.2 KiB
C++
218 lines
9.2 KiB
C++
#include "RuntimeLayerModel.h"
|
|
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
namespace
|
|
{
|
|
int gFailures = 0;
|
|
|
|
void Expect(bool condition, const std::string& message)
|
|
{
|
|
if (condition)
|
|
return;
|
|
|
|
++gFailures;
|
|
std::cerr << "FAIL: " << message << "\n";
|
|
}
|
|
|
|
std::filesystem::path MakeTestRoot()
|
|
{
|
|
const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count();
|
|
const std::filesystem::path root = std::filesystem::temp_directory_path() / ("render-cadence-layer-model-tests-" + std::to_string(stamp));
|
|
std::filesystem::create_directories(root);
|
|
return root;
|
|
}
|
|
|
|
void WriteFile(const std::filesystem::path& path, const std::string& contents)
|
|
{
|
|
std::filesystem::create_directories(path.parent_path());
|
|
std::ofstream output(path, std::ios::binary);
|
|
output << contents;
|
|
}
|
|
|
|
RenderCadenceCompositor::SupportedShaderCatalog MakeCatalog(std::filesystem::path& root)
|
|
{
|
|
root = MakeTestRoot();
|
|
WriteFile(root / "solid" / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 0.0, 1.0); }\n");
|
|
WriteFile(root / "solid" / "shader.json", R"({
|
|
"id": "solid",
|
|
"name": "Solid",
|
|
"description": "Solid test shader",
|
|
"category": "Tests",
|
|
"entryPoint": "shadeVideo",
|
|
"parameters": [
|
|
{ "id": "gain", "label": "Gain", "type": "float", "default": 0.5 },
|
|
{ "id": "drop", "label": "Drop", "type": "trigger" }
|
|
]
|
|
})");
|
|
|
|
RenderCadenceCompositor::SupportedShaderCatalog catalog;
|
|
std::string error;
|
|
Expect(catalog.Load(root, 4, error), error.empty() ? "catalog loads test shader" : error);
|
|
return catalog;
|
|
}
|
|
|
|
void TestSingleLayerLifecycle()
|
|
{
|
|
std::filesystem::path root;
|
|
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
|
|
|
RenderCadenceCompositor::RuntimeLayerModel model;
|
|
std::string error;
|
|
Expect(model.InitializeSingleLayer(catalog, "solid", error), "model initializes a supported startup shader");
|
|
Expect(model.FirstLayerId() == "runtime-layer-1", "startup layer id is stable");
|
|
|
|
Expect(model.MarkBuildStarted(model.FirstLayerId(), "build started", error), "build start updates the layer");
|
|
RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot();
|
|
Expect(snapshot.displayLayers.size() == 1, "snapshot exposes the display layer");
|
|
Expect(snapshot.displayLayers[0].buildState == RenderCadenceCompositor::RuntimeLayerBuildState::Pending, "started layer is pending");
|
|
Expect(!snapshot.displayLayers[0].renderReady, "started layer is not render-ready yet");
|
|
|
|
RuntimeShaderArtifact artifact;
|
|
artifact.shaderId = "solid";
|
|
artifact.displayName = "Solid";
|
|
artifact.fragmentShaderSource = "void main(){}";
|
|
artifact.message = "build ready";
|
|
Expect(model.MarkBuildReady(artifact, error), "ready artifact updates the matching layer");
|
|
|
|
snapshot = model.Snapshot();
|
|
Expect(snapshot.compileSucceeded, "ready layer reports compile success");
|
|
Expect(snapshot.displayLayers[0].buildState == RenderCadenceCompositor::RuntimeLayerBuildState::Ready, "ready layer is marked ready");
|
|
Expect(snapshot.displayLayers[0].renderReady, "ready layer exposes render readiness");
|
|
Expect(snapshot.renderLayers.size() == 1, "ready layer produces one render layer artifact");
|
|
Expect(snapshot.renderLayers[0].artifact.shaderId == "solid", "render layer carries the artifact");
|
|
|
|
std::filesystem::remove_all(root);
|
|
}
|
|
|
|
void TestRejectsUnsupportedStartupShader()
|
|
{
|
|
std::filesystem::path root;
|
|
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
|
|
|
RenderCadenceCompositor::RuntimeLayerModel model;
|
|
std::string error;
|
|
Expect(!model.InitializeSingleLayer(catalog, "missing", error), "model rejects unsupported shader ids");
|
|
Expect(!error.empty(), "unsupported shader rejection explains the problem");
|
|
Expect(model.Snapshot().displayLayers.empty(), "rejected startup shader leaves no display layer");
|
|
|
|
std::filesystem::remove_all(root);
|
|
}
|
|
|
|
void TestBuildFailureStaysDisplaySide()
|
|
{
|
|
std::filesystem::path root;
|
|
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
|
|
|
RenderCadenceCompositor::RuntimeLayerModel model;
|
|
std::string error;
|
|
Expect(model.InitializeSingleLayer(catalog, "solid", error), "model initializes for failure test");
|
|
Expect(model.MarkBuildFailed(model.FirstLayerId(), "compile failed", error), "build failure updates the layer");
|
|
|
|
const RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot();
|
|
Expect(!snapshot.compileSucceeded, "failed layer reports compile failure");
|
|
Expect(snapshot.displayLayers[0].buildState == RenderCadenceCompositor::RuntimeLayerBuildState::Failed, "failed layer is marked failed");
|
|
Expect(!snapshot.displayLayers[0].renderReady, "failed layer is not render-ready");
|
|
Expect(snapshot.renderLayers.empty(), "failed layer does not produce a render artifact");
|
|
|
|
std::filesystem::remove_all(root);
|
|
}
|
|
|
|
void TestAddAndRemoveLayers()
|
|
{
|
|
std::filesystem::path root;
|
|
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
|
|
|
RenderCadenceCompositor::RuntimeLayerModel model;
|
|
std::string error;
|
|
std::string firstLayerId;
|
|
std::string secondLayerId;
|
|
Expect(model.AddLayer(catalog, "solid", firstLayerId, error), "first layer can be added");
|
|
Expect(model.AddLayer(catalog, "solid", secondLayerId, error), "second layer can be added");
|
|
Expect(firstLayerId != secondLayerId, "added layers receive distinct ids");
|
|
Expect(model.Snapshot().displayLayers.size() == 2, "added layers appear in display snapshot");
|
|
|
|
Expect(model.RemoveLayer(firstLayerId, error), "existing layer can be removed");
|
|
RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot();
|
|
Expect(snapshot.displayLayers.size() == 1, "removed layer leaves snapshot");
|
|
Expect(snapshot.displayLayers[0].id == secondLayerId, "remaining layer identity is preserved");
|
|
Expect(!model.RemoveLayer(firstLayerId, error), "removed layer cannot be removed twice");
|
|
|
|
std::filesystem::remove_all(root);
|
|
}
|
|
|
|
void TestLayerControlsUpdateDisplayAndRenderModels()
|
|
{
|
|
std::filesystem::path root;
|
|
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
|
|
|
RenderCadenceCompositor::RuntimeLayerModel model;
|
|
std::string error;
|
|
std::string firstLayerId;
|
|
std::string secondLayerId;
|
|
Expect(model.AddLayer(catalog, "solid", firstLayerId, error), "first control layer can be added");
|
|
Expect(model.AddLayer(catalog, "solid", secondLayerId, error), "second control layer can be added");
|
|
|
|
Expect(model.SetLayerBypass(firstLayerId, true, error), "bypass can be set");
|
|
Expect(model.ReorderLayer(firstLayerId, 1, error), "layer can be reordered");
|
|
RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot();
|
|
Expect(snapshot.displayLayers[1].id == firstLayerId, "reordered layer moves to requested index");
|
|
Expect(snapshot.displayLayers[1].bypass, "bypass state is visible in read model");
|
|
|
|
JsonValue gainValue(0.75);
|
|
Expect(model.UpdateParameter(firstLayerId, "gain", gainValue, error), "parameter value can be updated");
|
|
snapshot = model.Snapshot();
|
|
Expect(snapshot.displayLayers[1].parameterValues.at("gain").numberValues.front() == 0.75, "updated parameter value is visible");
|
|
JsonValue dropPulse(true);
|
|
Expect(model.UpdateParameter(firstLayerId, "drop", dropPulse, error), "trigger parameter can be pulsed");
|
|
snapshot = model.Snapshot();
|
|
const std::vector<double> firstTrigger = snapshot.displayLayers[1].parameterValues.at("drop").numberValues;
|
|
Expect(firstTrigger.size() == 2 && firstTrigger[0] == 1.0 && firstTrigger[1] >= 0.0, "trigger pulse increments count and records runtime time");
|
|
Expect(model.UpdateParameter(firstLayerId, "drop", dropPulse, error), "trigger parameter can be pulsed again");
|
|
snapshot = model.Snapshot();
|
|
const std::vector<double> secondTrigger = snapshot.displayLayers[1].parameterValues.at("drop").numberValues;
|
|
Expect(secondTrigger.size() == 2 && secondTrigger[0] == 2.0 && secondTrigger[1] >= firstTrigger[1], "second trigger pulse increments count again");
|
|
|
|
RuntimeShaderArtifact artifact;
|
|
artifact.layerId = firstLayerId;
|
|
artifact.shaderId = "solid";
|
|
artifact.displayName = "Solid";
|
|
artifact.fragmentShaderSource = "void main(){}";
|
|
artifact.parameterDefinitions = snapshot.displayLayers[1].parameterDefinitions;
|
|
artifact.message = "build ready";
|
|
Expect(model.MarkBuildReady(artifact, error), "ready artifact keeps layer parameter state");
|
|
snapshot = model.Snapshot();
|
|
Expect(snapshot.renderLayers.size() == 1, "ready layer produces render model");
|
|
Expect(snapshot.renderLayers[0].bypass, "render model carries bypass state");
|
|
Expect(snapshot.renderLayers[0].artifact.parameterValues.at("gain").numberValues.front() == 0.75, "render artifact carries updated parameter value");
|
|
|
|
Expect(model.ResetParameters(firstLayerId, error), "parameters can reset to defaults");
|
|
snapshot = model.Snapshot();
|
|
Expect(snapshot.displayLayers[1].parameterValues.at("gain").numberValues.front() == 0.5, "reset restores default value");
|
|
|
|
std::filesystem::remove_all(root);
|
|
}
|
|
}
|
|
|
|
int main()
|
|
{
|
|
TestSingleLayerLifecycle();
|
|
TestRejectsUnsupportedStartupShader();
|
|
TestBuildFailureStaysDisplaySide();
|
|
TestAddAndRemoveLayers();
|
|
TestLayerControlsUpdateDisplayAndRenderModels();
|
|
|
|
if (gFailures != 0)
|
|
{
|
|
std::cerr << gFailures << " RenderCadenceCompositorRuntimeLayerModel test failure(s).\n";
|
|
return 1;
|
|
}
|
|
|
|
std::cout << "RenderCadenceCompositorRuntimeLayerModel tests passed.\n";
|
|
return 0;
|
|
}
|