#include "SupportedShaderCatalog.h" #include "ShaderPackageRegistry.h" #include #include #include namespace RenderCadenceCompositor { namespace { const ShaderParameterDefinition* FindParameterDefinition(const ShaderPackage& shaderPackage, const std::string& parameterId) { for (const ShaderParameterDefinition& parameter : shaderPackage.parameters) { if (parameter.id == parameterId) return ¶meter; } 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(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{}(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 packagesById; std::vector packageOrder; std::vector 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 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; } }