Files
video-shader-toys/src/runtime/catalog/SupportedShaderCatalog.cpp
Aiden 27690c3afa
All checks were successful
CI / React UI Build (push) Successful in 12s
CI / Native Windows Build And Tests (push) Successful in 2m20s
CI / Windows Release Package (push) Successful in 2m56s
added optional web component UI control
2026-05-30 22:57:59 +10:00

211 lines
7.4 KiB
C++

#include "SupportedShaderCatalog.h"
#include "ShaderPackageRegistry.h"
#include <map>
#include <sstream>
#include <utility>
namespace RenderCadenceCompositor
{
namespace
{
const ShaderParameterDefinition* FindParameterDefinition(const ShaderPackage& shaderPackage, const std::string& parameterId)
{
for (const ShaderParameterDefinition& parameter : shaderPackage.parameters)
{
if (parameter.id == parameterId)
return &parameter;
}
return nullptr;
}
bool HasFontAsset(const ShaderPackage& shaderPackage, const std::string& fontId)
{
for (const ShaderFontAsset& fontAsset : shaderPackage.fontAssets)
{
if (fontAsset.id == fontId)
return true;
}
return false;
}
}
ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& shaderPackage)
{
if (shaderPackage.passes.empty())
return { false, "Shader package has no render passes." };
if (shaderPackage.temporal.enabled)
return { false, "RenderCadenceCompositor currently supports only stateless shaders; temporal history is not enabled in this app." };
if (shaderPackage.feedback.enabled)
return { false, "RenderCadenceCompositor currently supports only stateless shaders; feedback storage is not enabled in this app." };
if (!shaderPackage.textureAssets.empty())
return { false, "RenderCadenceCompositor does not load shader texture assets yet; texture-backed shaders need a CPU-prepared asset handoff first." };
for (const ShaderParameterDefinition& parameter : shaderPackage.parameters)
{
if (parameter.type != ShaderParameterType::Text)
continue;
if (parameter.fontId.empty() && parameter.fontParameterId.empty())
return { false, "Text parameter '" + parameter.id + "' must reference a declared font asset." };
if (!parameter.fontId.empty() && !HasFontAsset(shaderPackage, parameter.fontId))
return { false, "Text parameter '" + parameter.id + "' references unknown font asset '" + parameter.fontId + "'." };
if (!parameter.fontParameterId.empty())
{
const ShaderParameterDefinition* fontParameter = FindParameterDefinition(shaderPackage, parameter.fontParameterId);
if (fontParameter == nullptr || fontParameter->type != ShaderParameterType::Enum)
return { false, "Text parameter '" + parameter.id + "' references unknown font enum parameter '" + parameter.fontParameterId + "'." };
for (const ShaderParameterOption& option : fontParameter->enumOptions)
{
if (!HasFontAsset(shaderPackage, option.value))
return { false, "Font enum parameter '" + fontParameter->id + "' references unknown font asset '" + option.value + "'." };
}
}
}
bool writesLayerOutput = false;
for (const ShaderPassDefinition& pass : shaderPackage.passes)
{
if (pass.sourcePath.empty())
{
return { false, "Shader pass '" + pass.id + "' has no source." };
}
if (pass.outputName == "layerOutput")
writesLayerOutput = true;
for (const std::string& inputName : pass.inputNames)
{
if (inputName == "videoInput" || inputName == "layerInput")
continue;
bool matchesNamedOutput = false;
for (const ShaderPassDefinition& outputPass : shaderPackage.passes)
{
if (outputPass.outputName == inputName)
{
matchesNamedOutput = true;
break;
}
}
if (!matchesNamedOutput)
return { false, "Shader pass '" + pass.id + "' references unknown input '" + inputName + "'." };
}
}
if (!writesLayerOutput)
return { false, "Shader package must write a pass output named 'layerOutput'." };
return { true, std::string() };
}
std::string ShaderPackageFingerprint(const ShaderPackage& shaderPackage)
{
const auto fileTimeText = [](std::filesystem::file_time_type value) {
return std::to_string(value.time_since_epoch().count());
};
std::ostringstream source;
source << shaderPackage.id << "\n"
<< shaderPackage.displayName << "\n"
<< shaderPackage.description << "\n"
<< shaderPackage.category << "\n"
<< shaderPackage.entryPoint << "\n"
<< fileTimeText(shaderPackage.manifestWriteTime) << "\n"
<< fileTimeText(shaderPackage.shaderWriteTime) << "\n";
for (const ShaderPassDefinition& pass : shaderPackage.passes)
{
source << "pass:" << pass.id << ":" << pass.entryPoint << ":" << pass.outputName << ":"
<< fileTimeText(pass.sourceWriteTime) << "\n";
for (const std::string& inputName : pass.inputNames)
source << "input:" << inputName << "\n";
}
for (const ShaderFontAsset& font : shaderPackage.fontAssets)
source << "font:" << font.id << ":" << font.path.string() << ":" << fileTimeText(font.writeTime) << "\n";
for (const ShaderParameterDefinition& parameter : shaderPackage.parameters)
{
source << "param:" << parameter.id << ":" << static_cast<int>(parameter.type) << ":"
<< parameter.label << ":" << parameter.description << ":" << parameter.fontId << ":"
<< parameter.fontParameterId << ":"
<< parameter.defaultTextValue << ":" << parameter.defaultBoolean << ":"
<< parameter.defaultEnumValue << ":" << parameter.maxLength << "\n";
for (double value : parameter.defaultNumbers)
source << "default:" << value << "\n";
for (double value : parameter.minNumbers)
source << "min:" << value << "\n";
for (double value : parameter.maxNumbers)
source << "max:" << value << "\n";
for (const ShaderParameterOption& option : parameter.enumOptions)
source << "option:" << option.value << ":" << option.label << "\n";
}
const std::string text = source.str();
return std::to_string(text.size()) + ":" + std::to_string(std::hash<std::string>{}(text));
}
bool SupportedShaderCatalog::Load(const std::filesystem::path& shaderRoot, unsigned maxTemporalHistoryFrames, std::string& error)
{
mShaders.clear();
mPackagesById.clear();
mFontAtlasesByShaderId.clear();
if (shaderRoot.empty())
{
error = "Shader library path is empty.";
return false;
}
ShaderPackageRegistry registry(maxTemporalHistoryFrames);
std::map<std::string, ShaderPackage> packagesById;
std::vector<std::string> packageOrder;
std::vector<ShaderPackageStatus> packageStatuses;
if (!registry.Scan(shaderRoot, packagesById, packageOrder, packageStatuses, error))
return false;
FontAtlasBuildConfig fontConfig;
fontConfig.repoRoot = shaderRoot.parent_path();
FontAtlasBuilder fontAtlasBuilder(fontConfig);
for (const std::string& packageId : packageOrder)
{
const auto packageIt = packagesById.find(packageId);
if (packageIt == packagesById.end())
continue;
const ShaderPackage& shaderPackage = packageIt->second;
if (!shaderPackage.fontAssets.empty())
{
std::vector<FontAtlasBuildOutput> fontAtlasOutputs;
std::string fontAtlasError;
if (fontAtlasBuilder.BuildPackageFontAtlases(shaderPackage, fontAtlasOutputs, fontAtlasError))
mFontAtlasesByShaderId[shaderPackage.id] = std::move(fontAtlasOutputs);
}
const ShaderSupportResult support = CheckStatelessSinglePassShaderSupport(shaderPackage);
if (!support.supported)
continue;
SupportedShaderSummary summary;
summary.id = shaderPackage.id;
summary.name = shaderPackage.displayName.empty() ? shaderPackage.id : shaderPackage.displayName;
summary.description = shaderPackage.description;
summary.category = shaderPackage.category;
summary.ui = shaderPackage.ui;
mShaders.push_back(std::move(summary));
mPackagesById[shaderPackage.id] = shaderPackage;
}
error.clear();
return true;
}
const ShaderPackage* SupportedShaderCatalog::FindPackage(const std::string& shaderId) const
{
const auto packageIt = mPackagesById.find(shaderId);
return packageIt == mPackagesById.end() ? nullptr : &packageIt->second;
}
}