#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace XCEngine { namespace Resources { namespace { std::string ToStdString(const Containers::Array& data) { return std::string(reinterpret_cast(data.Data()), data.Size()); } std::string ToStdString(const Containers::String& value) { return std::string(value.CStr()); } Containers::Array TryReadFileData( const std::filesystem::path& filePath, bool& opened) { Containers::Array data; std::ifstream file(filePath, std::ios::binary | std::ios::ate); if (!file.is_open()) { opened = false; return data; } opened = true; const std::streamsize size = file.tellg(); if (size <= 0) { return data; } file.seekg(0, std::ios::beg); data.Resize(static_cast(size)); if (!file.read(reinterpret_cast(data.Data()), size)) { data.Clear(); } return data; } Containers::Array ReadShaderFileData(const Containers::String& path) { bool opened = false; const std::filesystem::path inputPath(path.CStr()); Containers::Array data = TryReadFileData(inputPath, opened); if (opened || path.Empty() || inputPath.is_absolute()) { return data; } const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot(); if (resourceRoot.Empty()) { return data; } return TryReadFileData(std::filesystem::path(resourceRoot.CStr()) / inputPath, opened); } Containers::String NormalizePathString(const std::filesystem::path& path) { return Containers::String(path.lexically_normal().generic_string().c_str()); } Containers::String GetPathExtension(const Containers::String& path) { size_t dotPos = Containers::String::npos; for (size_t i = path.Length(); i > 0; --i) { if (path[i - 1] == '.') { dotPos = i - 1; break; } } if (dotPos == Containers::String::npos) { return Containers::String(); } return path.Substring(dotPos + 1); } size_t SkipWhitespace(const std::string& text, size_t pos) { while (pos < text.size() && std::isspace(static_cast(text[pos])) != 0) { ++pos; } return pos; } std::string TrimCopy(const std::string& text) { const size_t first = SkipWhitespace(text, 0); if (first >= text.size()) { return std::string(); } size_t last = text.size(); while (last > first && std::isspace(static_cast(text[last - 1])) != 0) { --last; } return text.substr(first, last - first); } bool FindValueStart(const std::string& json, const char* key, size_t& valuePos) { const std::string token = std::string("\"") + key + "\""; const size_t keyPos = json.find(token); if (keyPos == std::string::npos) { return false; } const size_t colonPos = json.find(':', keyPos + token.length()); if (colonPos == std::string::npos) { return false; } valuePos = SkipWhitespace(json, colonPos + 1); return valuePos < json.size(); } bool ParseQuotedString( const std::string& text, size_t quotePos, Containers::String& outValue, size_t* nextPos = nullptr) { if (quotePos >= text.size() || text[quotePos] != '"') { return false; } std::string parsed; ++quotePos; while (quotePos < text.size()) { const char ch = text[quotePos]; if (ch == '\\') { if (quotePos + 1 >= text.size()) { return false; } parsed.push_back(text[quotePos + 1]); quotePos += 2; continue; } if (ch == '"') { outValue = parsed.c_str(); if (nextPos != nullptr) { *nextPos = quotePos + 1; } return true; } parsed.push_back(ch); ++quotePos; } return false; } bool TryParseStringValue(const std::string& json, const char* key, Containers::String& outValue) { size_t valuePos = 0; if (!FindValueStart(json, key, valuePos)) { return false; } return ParseQuotedString(json, valuePos, outValue); } bool TryExtractDelimitedValue( const std::string& json, const char* key, char openChar, char closeChar, std::string& outValue) { size_t valuePos = 0; if (!FindValueStart(json, key, valuePos) || valuePos >= json.size() || json[valuePos] != openChar) { return false; } bool inString = false; bool escaped = false; int depth = 0; for (size_t pos = valuePos; pos < json.size(); ++pos) { const char ch = json[pos]; if (escaped) { escaped = false; continue; } if (ch == '\\') { escaped = true; continue; } if (ch == '"') { inString = !inString; continue; } if (inString) { continue; } if (ch == openChar) { ++depth; } else if (ch == closeChar) { --depth; if (depth == 0) { outValue = json.substr(valuePos, pos - valuePos + 1); return true; } } } return false; } bool TryExtractObject(const std::string& json, const char* key, std::string& outObject) { return TryExtractDelimitedValue(json, key, '{', '}', outObject); } bool TryExtractArray(const std::string& json, const char* key, std::string& outArray) { return TryExtractDelimitedValue(json, key, '[', ']', outArray); } bool TryParseStringMapObject( const std::string& objectText, const std::function& onEntry) { if (!onEntry || objectText.empty() || objectText.front() != '{' || objectText.back() != '}') { return false; } size_t pos = 1; while (pos < objectText.size()) { pos = SkipWhitespace(objectText, pos); if (pos >= objectText.size()) { return false; } if (objectText[pos] == '}') { return true; } Containers::String key; if (!ParseQuotedString(objectText, pos, key, &pos)) { return false; } pos = SkipWhitespace(objectText, pos); if (pos >= objectText.size() || objectText[pos] != ':') { return false; } pos = SkipWhitespace(objectText, pos + 1); Containers::String value; if (!ParseQuotedString(objectText, pos, value, &pos)) { return false; } onEntry(key, value); pos = SkipWhitespace(objectText, pos); if (pos >= objectText.size()) { return false; } if (objectText[pos] == ',') { ++pos; continue; } if (objectText[pos] == '}') { return true; } return false; } return false; } bool SplitTopLevelArrayElements(const std::string& arrayText, std::vector& outElements) { outElements.clear(); if (arrayText.size() < 2 || arrayText.front() != '[' || arrayText.back() != ']') { return false; } bool inString = false; bool escaped = false; int objectDepth = 0; int arrayDepth = 0; size_t elementStart = std::string::npos; for (size_t pos = 1; pos + 1 < arrayText.size(); ++pos) { const char ch = arrayText[pos]; if (escaped) { escaped = false; continue; } if (ch == '\\') { escaped = true; continue; } if (ch == '"') { if (elementStart == std::string::npos) { elementStart = pos; } inString = !inString; continue; } if (inString) { continue; } if (std::isspace(static_cast(ch)) != 0) { continue; } if (elementStart == std::string::npos) { elementStart = pos; } if (ch == '{') { ++objectDepth; continue; } if (ch == '[') { ++arrayDepth; continue; } if (ch == '}') { --objectDepth; continue; } if (ch == ']') { --arrayDepth; continue; } if (ch == ',' && objectDepth == 0 && arrayDepth == 0) { if (elementStart != std::string::npos && pos > elementStart) { outElements.push_back(TrimCopy(arrayText.substr(elementStart, pos - elementStart))); } elementStart = std::string::npos; } } if (elementStart != std::string::npos) { const std::string tail = TrimCopy(arrayText.substr(elementStart, arrayText.size() - 1 - elementStart)); if (!tail.empty()) { outElements.push_back(tail); } } return true; } bool TryParseShaderType(const Containers::String& value, ShaderType& outType) { const Containers::String normalized = value.Trim().ToLower(); if (normalized == "vertex" || normalized == "vs") { outType = ShaderType::Vertex; return true; } if (normalized == "fragment" || normalized == "pixel" || normalized == "ps") { outType = ShaderType::Fragment; return true; } if (normalized == "geometry" || normalized == "gs") { outType = ShaderType::Geometry; return true; } if (normalized == "compute" || normalized == "cs") { outType = ShaderType::Compute; return true; } if (normalized == "hull" || normalized == "hs") { outType = ShaderType::Hull; return true; } if (normalized == "domain" || normalized == "ds") { outType = ShaderType::Domain; return true; } return false; } bool TryParseShaderLanguage(const Containers::String& value, ShaderLanguage& outLanguage) { const Containers::String normalized = value.Trim().ToLower(); if (normalized == "glsl") { outLanguage = ShaderLanguage::GLSL; return true; } if (normalized == "hlsl") { outLanguage = ShaderLanguage::HLSL; return true; } if (normalized == "spirv" || normalized == "spv") { outLanguage = ShaderLanguage::SPIRV; return true; } return false; } bool TryParseShaderBackend(const Containers::String& value, ShaderBackend& outBackend) { const Containers::String normalized = value.Trim().ToLower(); if (normalized == "generic") { outBackend = ShaderBackend::Generic; return true; } if (normalized == "d3d12" || normalized == "dx12") { outBackend = ShaderBackend::D3D12; return true; } if (normalized == "opengl" || normalized == "gl") { outBackend = ShaderBackend::OpenGL; return true; } if (normalized == "vulkan" || normalized == "vk") { outBackend = ShaderBackend::Vulkan; return true; } return false; } bool TryParseShaderPropertyType(const Containers::String& value, ShaderPropertyType& outType) { const Containers::String normalized = value.Trim().ToLower(); if (normalized == "float") { outType = ShaderPropertyType::Float; return true; } if (normalized == "range") { outType = ShaderPropertyType::Range; return true; } if (normalized == "int" || normalized == "integer") { outType = ShaderPropertyType::Int; return true; } if (normalized == "vector" || normalized == "float4") { outType = ShaderPropertyType::Vector; return true; } if (normalized == "color") { outType = ShaderPropertyType::Color; return true; } if (normalized == "2d" || normalized == "texture2d" || normalized == "texture") { outType = ShaderPropertyType::Texture2D; return true; } if (normalized == "cube" || normalized == "cubemap" || normalized == "texturecube") { outType = ShaderPropertyType::TextureCube; return true; } return false; } bool TryParseShaderResourceType(const Containers::String& value, ShaderResourceType& outType) { const Containers::String normalized = value.Trim().ToLower(); if (normalized == "constantbuffer" || normalized == "cbuffer" || normalized == "cbv") { outType = ShaderResourceType::ConstantBuffer; return true; } if (normalized == "texture2d" || normalized == "texture" || normalized == "srvtexture2d") { outType = ShaderResourceType::Texture2D; return true; } if (normalized == "texturecube" || normalized == "cubemap") { outType = ShaderResourceType::TextureCube; return true; } if (normalized == "sampler" || normalized == "samplerstate") { outType = ShaderResourceType::Sampler; return true; } return false; } Containers::String GetDefaultEntryPoint(ShaderLanguage language, ShaderType stage) { if (language != ShaderLanguage::HLSL) { return Containers::String("main"); } switch (stage) { case ShaderType::Vertex: return "MainVS"; case ShaderType::Fragment: return "MainPS"; case ShaderType::Geometry: return "MainGS"; case ShaderType::Compute: return "MainCS"; case ShaderType::Hull: return "MainHS"; case ShaderType::Domain: return "MainDS"; default: return Containers::String(); } } Containers::String GetDefaultProfile( ShaderLanguage language, ShaderBackend backend, ShaderType stage) { if (language == ShaderLanguage::HLSL) { switch (stage) { case ShaderType::Vertex: return "vs_5_0"; case ShaderType::Fragment: return "ps_5_0"; case ShaderType::Geometry: return "gs_5_0"; case ShaderType::Compute: return "cs_5_0"; case ShaderType::Hull: return "hs_5_0"; case ShaderType::Domain: return "ds_5_0"; default: return Containers::String(); } } const bool isVulkan = backend == ShaderBackend::Vulkan; switch (stage) { case ShaderType::Vertex: return isVulkan ? "vs_4_50" : "vs_4_30"; case ShaderType::Fragment: return isVulkan ? "fs_4_50" : "fs_4_30"; case ShaderType::Geometry: return isVulkan ? "gs_4_50" : "gs_4_30"; case ShaderType::Compute: return isVulkan ? "cs_4_50" : "cs_4_30"; case ShaderType::Hull: return isVulkan ? "hs_4_50" : "hs_4_30"; case ShaderType::Domain: return isVulkan ? "ds_4_50" : "ds_4_30"; default: return Containers::String(); } } Containers::String ResolveShaderDependencyPath( const Containers::String& dependencyPath, const Containers::String& sourcePath) { if (dependencyPath.Empty()) { return dependencyPath; } const std::filesystem::path dependencyFsPath(dependencyPath.CStr()); if (dependencyFsPath.is_absolute()) { return NormalizePathString(dependencyFsPath); } const std::filesystem::path sourceFsPath(sourcePath.CStr()); if (sourceFsPath.is_absolute()) { return NormalizePathString(sourceFsPath.parent_path() / dependencyFsPath); } const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot(); if (!resourceRoot.Empty()) { return NormalizePathString( std::filesystem::path(resourceRoot.CStr()) / sourceFsPath.parent_path() / dependencyFsPath); } return NormalizePathString(sourceFsPath.parent_path() / dependencyFsPath); } bool ReadTextFile(const Containers::String& path, Containers::String& outText) { const Containers::Array data = ReadShaderFileData(path); if (data.Empty()) { return false; } outText = ToStdString(data).c_str(); return true; } size_t CalculateShaderMemorySize(const Shader& shader); bool TryTokenizeQuotedArguments(const std::string& line, std::vector& outTokens); enum class ShaderAuthoringStyle { NotShaderAuthoring = 0, LegacyBackendSplit, UnityStyleSingleSource }; struct AuthoringTagEntry { Containers::String name; Containers::String value; }; struct AuthoringBackendVariantEntry { ShaderBackend backend = ShaderBackend::Generic; ShaderLanguage language = ShaderLanguage::GLSL; Containers::String vertexSourcePath; Containers::String fragmentSourcePath; Containers::String vertexProfile; Containers::String fragmentProfile; }; struct AuthoringPassEntry { Containers::String name; std::vector tags; Containers::Array resources; Containers::Array keywordDeclarations; Containers::String vertexEntryPoint; Containers::String fragmentEntryPoint; Containers::String sharedProgramSource; Containers::String programSource; Containers::String targetProfile; std::vector backendVariants; }; struct AuthoringSubShaderEntry { std::vector tags; Containers::String sharedProgramSource; std::vector passes; }; struct AuthoringShaderDesc { Containers::String name; Containers::String sharedProgramSource; Containers::Array properties; std::vector subShaders; }; struct ExtractedProgramBlock { enum class Kind { SharedInclude, PassProgram }; Kind kind = Kind::PassProgram; Containers::String sourceText; size_t markerLine = 0; }; std::string StripAuthoringLineComment(const std::string& line) { bool inString = false; bool escaped = false; for (size_t index = 0; index + 1 < line.size(); ++index) { const char ch = line[index]; if (escaped) { escaped = false; continue; } if (ch == '\\') { escaped = true; continue; } if (ch == '"') { inString = !inString; continue; } if (!inString && ch == '/' && line[index + 1] == '/') { return line.substr(0, index); } } return line; } bool StartsWithKeyword(const std::string& line, const char* keyword) { const std::string keywordString(keyword); if (line.size() < keywordString.size() || line.compare(0, keywordString.size(), keywordString) != 0) { return false; } return line.size() == keywordString.size() || std::isspace(static_cast(line[keywordString.size()])) != 0; } void SplitShaderAuthoringLines( const std::string& sourceText, std::vector& outLines) { outLines.clear(); std::istringstream stream(sourceText); std::string rawLine; while (std::getline(stream, rawLine)) { std::string line = TrimCopy(StripAuthoringLineComment(rawLine)); if (line.empty()) { continue; } if (!StartsWithKeyword(line, "Tags") && line.size() > 1 && line.back() == '{') { line.pop_back(); const std::string prefix = TrimCopy(line); if (!prefix.empty()) { outLines.push_back(prefix); } outLines.push_back("{"); continue; } if (line.size() > 1 && line.front() == '}') { outLines.push_back("}"); line = TrimCopy(line.substr(1)); if (!line.empty()) { outLines.push_back(line); } continue; } outLines.push_back(line); } } bool TryExtractProgramBlocks( const std::string& sourceText, std::vector& outBlocks, Containers::String* outError) { outBlocks.clear(); std::istringstream stream(sourceText); std::string rawLine; bool insideBlock = false; ExtractedProgramBlock currentBlock = {}; std::string blockSource; size_t lineNumber = 0; auto fail = [&outError](const std::string& message, size_t humanLine) -> bool { if (outError != nullptr) { *outError = Containers::String( ("Unity-style shader parse error at line " + std::to_string(humanLine) + ": " + message).c_str()); } return false; }; while (std::getline(stream, rawLine)) { ++lineNumber; const std::string normalizedLine = TrimCopy(StripAuthoringLineComment(rawLine)); if (!insideBlock) { if (normalizedLine == "HLSLINCLUDE" || normalizedLine == "CGINCLUDE") { insideBlock = true; currentBlock = {}; currentBlock.kind = ExtractedProgramBlock::Kind::SharedInclude; currentBlock.markerLine = lineNumber; blockSource.clear(); } else if (normalizedLine == "HLSLPROGRAM" || normalizedLine == "CGPROGRAM") { insideBlock = true; currentBlock = {}; currentBlock.kind = ExtractedProgramBlock::Kind::PassProgram; currentBlock.markerLine = lineNumber; blockSource.clear(); } continue; } if (normalizedLine == "ENDHLSL" || normalizedLine == "ENDCG") { currentBlock.sourceText = blockSource.c_str(); outBlocks.push_back(std::move(currentBlock)); insideBlock = false; blockSource.clear(); continue; } blockSource += rawLine; blockSource += '\n'; } if (insideBlock) { return fail("program block was not closed", lineNumber); } return true; } bool ContainsBackendPragma(const std::vector& lines) { for (const std::string& line : lines) { if (line.rfind("#pragma backend", 0) == 0) { return true; } } return false; } bool ContainsResourcesBlock(const std::vector& lines) { for (const std::string& line : lines) { if (line == "Resources" || StartsWithKeyword(line, "Resources")) { return true; } } return false; } bool ContainsSingleSourceAuthoringConstructs(const std::vector& lines) { for (const std::string& line : lines) { if (line == "HLSLINCLUDE" || line == "CGINCLUDE") { return true; } if (line.rfind("#pragma target", 0) == 0 || line.rfind("#pragma multi_compile", 0) == 0 || line.rfind("#pragma shader_feature", 0) == 0 || line.rfind("#pragma shader_feature_local", 0) == 0) { return true; } } return false; } ShaderAuthoringStyle DetectShaderAuthoringStyle(const std::string& sourceText) { std::vector lines; SplitShaderAuthoringLines(sourceText, lines); if (lines.empty() || !StartsWithKeyword(lines.front(), "Shader")) { return ShaderAuthoringStyle::NotShaderAuthoring; } const bool hasBackendPragma = ContainsBackendPragma(lines); const bool hasSingleSourceConstructs = ContainsSingleSourceAuthoringConstructs(lines); if (hasBackendPragma && !hasSingleSourceConstructs) { return ShaderAuthoringStyle::LegacyBackendSplit; } if (ContainsResourcesBlock(lines)) { return ShaderAuthoringStyle::UnityStyleSingleSource; } if (hasSingleSourceConstructs) { return ShaderAuthoringStyle::UnityStyleSingleSource; } return ShaderAuthoringStyle::UnityStyleSingleSource; } void AppendAuthoringSourceBlock( Containers::String& target, const Containers::String& sourceBlock) { if (sourceBlock.Empty()) { return; } if (!target.Empty()) { target += '\n'; } target += sourceBlock; } void CollectQuotedIncludeDependencyPaths( const Containers::String& sourcePath, const Containers::String& sourceText, std::unordered_set& seenPaths, Containers::Array& outDependencies) { std::istringstream stream(ToStdString(sourceText)); std::string rawLine; while (std::getline(stream, rawLine)) { const std::string line = TrimCopy(StripAuthoringLineComment(rawLine)); if (line.rfind("#include", 0) != 0) { continue; } const size_t firstQuote = line.find('"'); if (firstQuote == std::string::npos) { continue; } const size_t secondQuote = line.find('"', firstQuote + 1); if (secondQuote == std::string::npos || secondQuote <= firstQuote + 1) { continue; } const Containers::String includePath(line.substr(firstQuote + 1, secondQuote - firstQuote - 1).c_str()); const Containers::String resolvedPath = ResolveShaderDependencyPath(includePath, sourcePath); const std::string key = ToStdString(resolvedPath); if (!key.empty() && seenPaths.insert(key).second) { outDependencies.PushBack(resolvedPath); } } } bool IsUnityStyleAuthoringPragmaDirective(const std::string& line) { std::vector pragmaTokens; if (!TryTokenizeQuotedArguments(line, pragmaTokens) || pragmaTokens.empty() || pragmaTokens[0] != "#pragma" || pragmaTokens.size() < 2u) { return false; } return pragmaTokens[1] == "vertex" || pragmaTokens[1] == "fragment" || pragmaTokens[1] == "target" || pragmaTokens[1] == "multi_compile" || pragmaTokens[1] == "shader_feature" || pragmaTokens[1] == "shader_feature_local" || pragmaTokens[1] == "backend"; } Containers::String StripUnityStyleAuthoringPragmas(const Containers::String& sourceText) { std::istringstream stream(ToStdString(sourceText)); std::string rawLine; std::string strippedSource; while (std::getline(stream, rawLine)) { const std::string normalizedLine = TrimCopy(StripAuthoringLineComment(rawLine)); if (IsUnityStyleAuthoringPragmaDirective(normalizedLine)) { continue; } strippedSource += rawLine; strippedSource += '\n'; } return strippedSource.c_str(); } bool TryParseShaderKeywordDeclarationPragma( const std::vector& pragmaTokens, ShaderKeywordDeclaration& outDeclaration) { outDeclaration = {}; if (pragmaTokens.size() < 3u) { return false; } if (pragmaTokens[1] == "multi_compile") { outDeclaration.type = ShaderKeywordDeclarationType::MultiCompile; } else if (pragmaTokens[1] == "shader_feature") { outDeclaration.type = ShaderKeywordDeclarationType::ShaderFeature; } else if (pragmaTokens[1] == "shader_feature_local") { outDeclaration.type = ShaderKeywordDeclarationType::ShaderFeatureLocal; } else { return false; } for (size_t tokenIndex = 2; tokenIndex < pragmaTokens.size(); ++tokenIndex) { outDeclaration.options.PushBack(pragmaTokens[tokenIndex].c_str()); } return !outDeclaration.options.Empty(); } size_t FindMatchingDelimiter( const std::string& text, size_t openPos, char openChar, char closeChar) { if (openPos >= text.size() || text[openPos] != openChar) { return std::string::npos; } bool inString = false; bool escaped = false; int depth = 0; for (size_t pos = openPos; pos < text.size(); ++pos) { const char ch = text[pos]; if (escaped) { escaped = false; continue; } if (ch == '\\') { escaped = true; continue; } if (ch == '"') { inString = !inString; continue; } if (inString) { continue; } if (ch == openChar) { ++depth; } else if (ch == closeChar) { --depth; if (depth == 0) { return pos; } } } return std::string::npos; } size_t FindFirstTopLevelChar(const std::string& text, char target) { bool inString = false; bool escaped = false; int roundDepth = 0; int squareDepth = 0; for (size_t pos = 0; pos < text.size(); ++pos) { const char ch = text[pos]; if (escaped) { escaped = false; continue; } if (ch == '\\') { escaped = true; continue; } if (ch == '"') { inString = !inString; continue; } if (inString) { continue; } if (ch == target && roundDepth == 0 && squareDepth == 0) { return pos; } if (ch == '(') { ++roundDepth; continue; } if (ch == ')') { --roundDepth; continue; } if (ch == '[') { ++squareDepth; continue; } if (ch == ']') { --squareDepth; continue; } } return std::string::npos; } bool SplitCommaSeparatedAuthoring(const std::string& text, std::vector& outParts) { outParts.clear(); bool inString = false; bool escaped = false; int roundDepth = 0; int squareDepth = 0; size_t partStart = 0; for (size_t pos = 0; pos < text.size(); ++pos) { const char ch = text[pos]; if (escaped) { escaped = false; continue; } if (ch == '\\') { escaped = true; continue; } if (ch == '"') { inString = !inString; continue; } if (inString) { continue; } if (ch == '(') { ++roundDepth; continue; } if (ch == ')') { --roundDepth; continue; } if (ch == '[') { ++squareDepth; continue; } if (ch == ']') { --squareDepth; continue; } if (ch == ',' && roundDepth == 0 && squareDepth == 0) { outParts.push_back(TrimCopy(text.substr(partStart, pos - partStart))); partStart = pos + 1; } } outParts.push_back(TrimCopy(text.substr(partStart))); return true; } Containers::String UnquoteAuthoringValue(const std::string& text) { const std::string trimmed = TrimCopy(text); if (trimmed.size() >= 2 && trimmed.front() == '"' && trimmed.back() == '"') { Containers::String parsed; if (ParseQuotedString(trimmed, 0, parsed)) { return parsed; } } return Containers::String(trimmed.c_str()); } bool TryTokenizeQuotedArguments(const std::string& line, std::vector& outTokens) { outTokens.clear(); std::string current; bool inString = false; bool escaped = false; for (size_t pos = 0; pos < line.size(); ++pos) { const char ch = line[pos]; if (escaped) { current.push_back(ch); escaped = false; continue; } if (ch == '\\' && inString) { escaped = true; continue; } if (ch == '"') { inString = !inString; continue; } if (!inString && std::isspace(static_cast(ch)) != 0) { if (!current.empty()) { outTokens.push_back(current); current.clear(); } continue; } current.push_back(ch); } if (inString) { return false; } if (!current.empty()) { outTokens.push_back(current); } return !outTokens.empty(); } bool TryParseInlineTagAssignments( const std::string& line, std::vector& outTags) { const size_t openBrace = line.find('{'); const size_t closeBrace = line.rfind('}'); if (openBrace == std::string::npos || closeBrace == std::string::npos || closeBrace <= openBrace) { return false; } const std::string content = line.substr(openBrace + 1, closeBrace - openBrace - 1); size_t pos = 0; while (pos < content.size()) { pos = SkipWhitespace(content, pos); while (pos < content.size() && content[pos] == ',') { ++pos; pos = SkipWhitespace(content, pos); } if (pos >= content.size()) { break; } Containers::String key; if (!ParseQuotedString(content, pos, key, &pos)) { return false; } pos = SkipWhitespace(content, pos); if (pos >= content.size() || content[pos] != '=') { return false; } pos = SkipWhitespace(content, pos + 1); Containers::String value; if (!ParseQuotedString(content, pos, value, &pos)) { return false; } outTags.push_back({ key, value }); } return true; } bool TryParseSemanticAttributes( const std::string& attributesText, Containers::String& outSemantic) { outSemantic.Clear(); size_t pos = 0; while (pos < attributesText.size()) { pos = attributesText.find('[', pos); if (pos == std::string::npos) { break; } const size_t closePos = FindMatchingDelimiter(attributesText, pos, '[', ']'); if (closePos == std::string::npos) { return false; } const std::string attributeBody = TrimCopy(attributesText.substr(pos + 1, closePos - pos - 1)); if (attributeBody.size() > 10 && attributeBody.compare(0, 9, "Semantic(") == 0 && attributeBody.back() == ')') { outSemantic = UnquoteAuthoringValue(attributeBody.substr(9, attributeBody.size() - 10)); } pos = closePos + 1; } return true; } bool TryParseAuthoringPropertyLine( const std::string& line, ShaderPropertyDesc& outProperty) { outProperty = {}; const size_t openParen = line.find('('); if (openParen == std::string::npos) { return false; } const size_t closeParen = FindMatchingDelimiter(line, openParen, '(', ')'); if (closeParen == std::string::npos) { return false; } outProperty.name = Containers::String(TrimCopy(line.substr(0, openParen)).c_str()); if (outProperty.name.Empty()) { return false; } std::vector headerParts; if (!SplitCommaSeparatedAuthoring(line.substr(openParen + 1, closeParen - openParen - 1), headerParts) || headerParts.size() < 2u) { return false; } outProperty.displayName = UnquoteAuthoringValue(headerParts[0]); std::string propertyTypeName = headerParts[1]; for (size_t index = 2; index < headerParts.size(); ++index) { propertyTypeName += ","; propertyTypeName += headerParts[index]; } propertyTypeName = TrimCopy(propertyTypeName); const size_t rangePos = propertyTypeName.find('('); if (rangePos != std::string::npos && TrimCopy(propertyTypeName.substr(0, rangePos)) == "Range") { propertyTypeName = "Range"; } if (!TryParseShaderPropertyType(propertyTypeName.c_str(), outProperty.type)) { return false; } const size_t equalsPos = line.find('=', closeParen + 1); if (equalsPos == std::string::npos) { return false; } const std::string tail = TrimCopy(line.substr(equalsPos + 1)); const size_t attributePos = FindFirstTopLevelChar(tail, '['); const std::string defaultValueText = attributePos == std::string::npos ? tail : TrimCopy(tail.substr(0, attributePos)); if (defaultValueText.empty()) { return false; } outProperty.defaultValue = UnquoteAuthoringValue(defaultValueText); if (attributePos != std::string::npos && !TryParseSemanticAttributes(tail.substr(attributePos), outProperty.semantic)) { return false; } return true; } bool TryParseAuthoringResourceLine( const std::string& line, ShaderResourceBindingDesc& outBinding) { outBinding = {}; const size_t openParen = line.find('('); if (openParen == std::string::npos) { return false; } const size_t closeParen = FindMatchingDelimiter(line, openParen, '(', ')'); if (closeParen == std::string::npos) { return false; } outBinding.name = Containers::String(TrimCopy(line.substr(0, openParen)).c_str()); if (outBinding.name.Empty()) { return false; } std::vector parts; if (!SplitCommaSeparatedAuthoring(line.substr(openParen + 1, closeParen - openParen - 1), parts) || parts.size() != 3u) { return false; } if (!TryParseShaderResourceType(parts[0].c_str(), outBinding.type)) { return false; } try { outBinding.set = static_cast(std::stoul(parts[1])); outBinding.binding = static_cast(std::stoul(parts[2])); } catch (...) { return false; } const size_t attributePos = FindFirstTopLevelChar(line.substr(closeParen + 1), '['); if (attributePos != std::string::npos) { const std::string attributesText = line.substr(closeParen + 1 + attributePos); if (!TryParseSemanticAttributes(attributesText, outBinding.semantic)) { return false; } } return true; } bool ParseLegacyBackendSplitShaderAuthoring( const Containers::String& path, const std::string& sourceText, AuthoringShaderDesc& outDesc, Containers::String* outError) { (void)path; outDesc = {}; enum class BlockKind { None, Shader, Properties, SubShader, Pass, Resources }; auto fail = [&outError](const std::string& message, size_t lineNumber) -> bool { if (outError != nullptr) { *outError = Containers::String( ("Legacy shader parse error at line " + std::to_string(lineNumber) + ": " + message).c_str()); } return false; }; std::vector lines; SplitShaderAuthoringLines(sourceText, lines); if (lines.empty()) { return fail("shader file is empty", 0); } std::vector blockStack; BlockKind pendingBlock = BlockKind::None; AuthoringSubShaderEntry* currentSubShader = nullptr; AuthoringPassEntry* currentPass = nullptr; bool inProgram = false; auto currentBlock = [&blockStack]() -> BlockKind { return blockStack.empty() ? BlockKind::None : blockStack.back(); }; for (size_t lineIndex = 0; lineIndex < lines.size(); ++lineIndex) { const std::string& line = lines[lineIndex]; const size_t humanLine = lineIndex + 1; if (inProgram) { if (line == "ENDHLSL" || line == "ENDCG") { inProgram = false; continue; } std::vector pragmaTokens; if (!TryTokenizeQuotedArguments(line, pragmaTokens) || pragmaTokens.empty()) { continue; } if (pragmaTokens[0] != "#pragma") { continue; } if (pragmaTokens.size() >= 3u && pragmaTokens[1] == "vertex") { currentPass->vertexEntryPoint = pragmaTokens[2].c_str(); continue; } if (pragmaTokens.size() >= 3u && pragmaTokens[1] == "fragment") { currentPass->fragmentEntryPoint = pragmaTokens[2].c_str(); continue; } if (pragmaTokens.size() >= 6u && pragmaTokens[1] == "backend") { AuthoringBackendVariantEntry backendVariant = {}; if (!TryParseShaderBackend(pragmaTokens[2].c_str(), backendVariant.backend)) { return fail("invalid backend pragma backend name", humanLine); } if (!TryParseShaderLanguage(pragmaTokens[3].c_str(), backendVariant.language)) { return fail("invalid backend pragma language name", humanLine); } backendVariant.vertexSourcePath = pragmaTokens[4].c_str(); backendVariant.fragmentSourcePath = pragmaTokens[5].c_str(); if (pragmaTokens.size() >= 7u) { backendVariant.vertexProfile = pragmaTokens[6].c_str(); } if (pragmaTokens.size() >= 8u) { backendVariant.fragmentProfile = pragmaTokens[7].c_str(); } currentPass->backendVariants.push_back(std::move(backendVariant)); } continue; } if (line == "{") { switch (pendingBlock) { case BlockKind::Shader: blockStack.push_back(BlockKind::Shader); break; case BlockKind::Properties: blockStack.push_back(BlockKind::Properties); break; case BlockKind::SubShader: outDesc.subShaders.emplace_back(); currentSubShader = &outDesc.subShaders.back(); blockStack.push_back(BlockKind::SubShader); break; case BlockKind::Pass: if (currentSubShader == nullptr) { return fail("pass block must be inside a SubShader", humanLine); } currentSubShader->passes.emplace_back(); currentPass = ¤tSubShader->passes.back(); blockStack.push_back(BlockKind::Pass); break; case BlockKind::Resources: if (currentPass == nullptr) { return fail("resources block must be inside a Pass", humanLine); } blockStack.push_back(BlockKind::Resources); break; case BlockKind::None: default: return fail("unexpected opening brace", humanLine); } pendingBlock = BlockKind::None; continue; } if (line == "}") { if (blockStack.empty()) { return fail("unexpected closing brace", humanLine); } const BlockKind closingBlock = blockStack.back(); blockStack.pop_back(); if (closingBlock == BlockKind::Pass) { currentPass = nullptr; } else if (closingBlock == BlockKind::SubShader) { currentSubShader = nullptr; } continue; } if (StartsWithKeyword(line, "Shader")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) { return fail("Shader declaration is missing a name", humanLine); } outDesc.name = tokens[1].c_str(); pendingBlock = BlockKind::Shader; continue; } if (line == "Properties") { pendingBlock = BlockKind::Properties; continue; } if (StartsWithKeyword(line, "SubShader")) { pendingBlock = BlockKind::SubShader; continue; } if (StartsWithKeyword(line, "Pass")) { pendingBlock = BlockKind::Pass; continue; } if (line == "Resources") { pendingBlock = BlockKind::Resources; continue; } if (StartsWithKeyword(line, "Tags")) { std::vector parsedTags; if (!TryParseInlineTagAssignments(line, parsedTags)) { return fail("Tags block must use inline key/value pairs", humanLine); } if (currentPass != nullptr) { currentPass->tags.insert(currentPass->tags.end(), parsedTags.begin(), parsedTags.end()); } else if (currentSubShader != nullptr) { currentSubShader->tags.insert(currentSubShader->tags.end(), parsedTags.begin(), parsedTags.end()); } else { return fail("Tags block is only supported inside SubShader or Pass", humanLine); } continue; } if (currentBlock() == BlockKind::Properties) { ShaderPropertyDesc property = {}; if (!TryParseAuthoringPropertyLine(line, property)) { return fail("invalid Properties entry", humanLine); } outDesc.properties.PushBack(property); continue; } if (currentBlock() == BlockKind::Resources) { ShaderResourceBindingDesc resourceBinding = {}; if (!TryParseAuthoringResourceLine(line, resourceBinding)) { return fail("invalid Resources entry", humanLine); } currentPass->resources.PushBack(resourceBinding); continue; } if (currentBlock() == BlockKind::Pass && currentPass != nullptr) { if (StartsWithKeyword(line, "Name")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) { return fail("pass Name directive is missing a value", humanLine); } currentPass->name = tokens[1].c_str(); continue; } if (line == "HLSLPROGRAM" || line == "CGPROGRAM") { inProgram = true; continue; } } return fail("unsupported authoring statement: " + line, humanLine); } if (inProgram) { return fail("program block was not closed", lines.size()); } if (!blockStack.empty()) { return fail("one or more blocks were not closed", lines.size()); } if (outDesc.name.Empty()) { return fail("shader name is missing", 0); } if (outDesc.subShaders.empty()) { return fail("shader does not declare any SubShader blocks", 0); } for (const AuthoringSubShaderEntry& subShader : outDesc.subShaders) { if (subShader.passes.empty()) { continue; } for (const AuthoringPassEntry& pass : subShader.passes) { if (pass.name.Empty()) { return fail("a Pass is missing a Name directive", 0); } if (pass.backendVariants.empty()) { return fail("a Pass is missing backend variants", 0); } } } return true; } bool ParseUnityStyleSingleSourceShaderAuthoring( const Containers::String& path, const std::string& sourceText, AuthoringShaderDesc& outDesc, Containers::String* outError) { (void)path; outDesc = {}; enum class BlockKind { None, Shader, Properties, SubShader, Pass }; auto fail = [&outError](const std::string& message, size_t lineNumber) -> bool { if (outError != nullptr) { *outError = Containers::String( ("Unity-style shader parse error at line " + std::to_string(lineNumber) + ": " + message).c_str()); } return false; }; std::vector lines; SplitShaderAuthoringLines(sourceText, lines); if (lines.empty()) { return fail("shader file is empty", 0); } std::vector extractedBlocks; Containers::String extractionError; if (!TryExtractProgramBlocks(sourceText, extractedBlocks, &extractionError)) { if (outError != nullptr) { *outError = extractionError; } return false; } size_t nextExtractedBlock = 0; std::vector blockStack; BlockKind pendingBlock = BlockKind::None; AuthoringSubShaderEntry* currentSubShader = nullptr; AuthoringPassEntry* currentPass = nullptr; bool inProgramBlock = false; bool inSharedIncludeBlock = false; auto currentBlock = [&blockStack]() -> BlockKind { return blockStack.empty() ? BlockKind::None : blockStack.back(); }; auto consumeExtractedBlock = [&](ExtractedProgramBlock::Kind expectedKind, Containers::String& destination, bool append, size_t humanLine) -> bool { if (nextExtractedBlock >= extractedBlocks.size()) { return fail("program block source extraction is out of sync", humanLine); } const ExtractedProgramBlock& block = extractedBlocks[nextExtractedBlock++]; if (block.kind != expectedKind) { return fail("program block source extraction mismatched block kind", humanLine); } if (append) { AppendAuthoringSourceBlock(destination, block.sourceText); } else { destination = block.sourceText; } return true; }; for (size_t lineIndex = 0; lineIndex < lines.size(); ++lineIndex) { const std::string& line = lines[lineIndex]; const size_t humanLine = lineIndex + 1; if (inSharedIncludeBlock || inProgramBlock) { if (line == "ENDHLSL" || line == "ENDCG") { inSharedIncludeBlock = false; inProgramBlock = false; continue; } std::vector pragmaTokens; if (!TryTokenizeQuotedArguments(line, pragmaTokens) || pragmaTokens.empty()) { continue; } if (pragmaTokens[0] != "#pragma") { continue; } if (pragmaTokens.size() >= 2u && pragmaTokens[1] == "backend") { return fail("Unity-style single-source shaders must not use #pragma backend", humanLine); } if (inSharedIncludeBlock) { continue; } if (pragmaTokens.size() >= 3u && pragmaTokens[1] == "vertex") { currentPass->vertexEntryPoint = pragmaTokens[2].c_str(); continue; } if (pragmaTokens.size() >= 3u && pragmaTokens[1] == "fragment") { currentPass->fragmentEntryPoint = pragmaTokens[2].c_str(); continue; } if (pragmaTokens.size() >= 3u && pragmaTokens[1] == "target") { currentPass->targetProfile = pragmaTokens[2].c_str(); continue; } if (pragmaTokens.size() >= 2u && (pragmaTokens[1] == "multi_compile" || pragmaTokens[1] == "shader_feature" || pragmaTokens[1] == "shader_feature_local")) { ShaderKeywordDeclaration declaration = {}; if (!TryParseShaderKeywordDeclarationPragma(pragmaTokens, declaration)) { return fail("keyword pragma must declare at least one option", humanLine); } currentPass->keywordDeclarations.PushBack(declaration); continue; } return fail("unsupported pragma in Unity-style single-source shader", humanLine); } if (line == "{") { switch (pendingBlock) { case BlockKind::Shader: blockStack.push_back(BlockKind::Shader); break; case BlockKind::Properties: blockStack.push_back(BlockKind::Properties); break; case BlockKind::SubShader: outDesc.subShaders.emplace_back(); currentSubShader = &outDesc.subShaders.back(); blockStack.push_back(BlockKind::SubShader); break; case BlockKind::Pass: if (currentSubShader == nullptr) { return fail("pass block must be inside a SubShader", humanLine); } currentSubShader->passes.emplace_back(); currentPass = ¤tSubShader->passes.back(); blockStack.push_back(BlockKind::Pass); break; case BlockKind::None: default: return fail("unexpected opening brace", humanLine); } pendingBlock = BlockKind::None; continue; } if (line == "}") { if (blockStack.empty()) { return fail("unexpected closing brace", humanLine); } const BlockKind closingBlock = blockStack.back(); blockStack.pop_back(); if (closingBlock == BlockKind::Pass) { currentPass = nullptr; } else if (closingBlock == BlockKind::SubShader) { currentSubShader = nullptr; } continue; } if (StartsWithKeyword(line, "Shader")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) { return fail("Shader declaration is missing a name", humanLine); } outDesc.name = tokens[1].c_str(); pendingBlock = BlockKind::Shader; continue; } if (line == "Properties") { pendingBlock = BlockKind::Properties; continue; } if (StartsWithKeyword(line, "SubShader")) { pendingBlock = BlockKind::SubShader; continue; } if (StartsWithKeyword(line, "Pass")) { pendingBlock = BlockKind::Pass; continue; } if (line == "Resources" || StartsWithKeyword(line, "Resources")) { return fail("Unity-style single-source shaders must not declare Resources blocks", humanLine); } if (StartsWithKeyword(line, "Tags")) { std::vector parsedTags; if (!TryParseInlineTagAssignments(line, parsedTags)) { return fail("Tags block must use inline key/value pairs", humanLine); } if (currentPass != nullptr) { currentPass->tags.insert(currentPass->tags.end(), parsedTags.begin(), parsedTags.end()); } else if (currentSubShader != nullptr) { currentSubShader->tags.insert(currentSubShader->tags.end(), parsedTags.begin(), parsedTags.end()); } else { return fail("Tags block is only supported inside SubShader or Pass", humanLine); } continue; } if (currentBlock() == BlockKind::Properties) { ShaderPropertyDesc property = {}; if (!TryParseAuthoringPropertyLine(line, property)) { return fail("invalid Properties entry", humanLine); } outDesc.properties.PushBack(property); continue; } if (line == "HLSLINCLUDE" || line == "CGINCLUDE") { if (currentPass != nullptr) { return fail("HLSLINCLUDE is not supported inside a Pass block", humanLine); } Containers::String* destination = nullptr; if (currentBlock() == BlockKind::Shader) { destination = &outDesc.sharedProgramSource; } else if (currentBlock() == BlockKind::SubShader && currentSubShader != nullptr) { destination = ¤tSubShader->sharedProgramSource; } else { return fail( "HLSLINCLUDE is only supported directly inside Shader or SubShader in the new authoring mode", humanLine); } inSharedIncludeBlock = true; if (!consumeExtractedBlock( ExtractedProgramBlock::Kind::SharedInclude, *destination, true, humanLine)) { return false; } continue; } if (currentBlock() == BlockKind::SubShader && StartsWithKeyword(line, "LOD")) { continue; } if (currentBlock() == BlockKind::Pass && currentPass != nullptr) { if (StartsWithKeyword(line, "Name")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) { return fail("pass Name directive is missing a value", humanLine); } currentPass->name = tokens[1].c_str(); continue; } if (line == "HLSLPROGRAM" || line == "CGPROGRAM") { inProgramBlock = true; if (!consumeExtractedBlock( ExtractedProgramBlock::Kind::PassProgram, currentPass->programSource, false, humanLine)) { return false; } continue; } } return fail("unsupported authoring statement: " + line, humanLine); } if (inSharedIncludeBlock || inProgramBlock) { return fail("program block was not closed", lines.size()); } if (!blockStack.empty()) { return fail("one or more blocks were not closed", lines.size()); } if (outDesc.name.Empty()) { return fail("shader name is missing", 0); } if (outDesc.subShaders.empty()) { return fail("shader does not declare any SubShader blocks", 0); } for (AuthoringSubShaderEntry& subShader : outDesc.subShaders) { if (subShader.passes.empty()) { continue; } for (AuthoringPassEntry& pass : subShader.passes) { if (pass.name.Empty()) { return fail("a Pass is missing a Name directive", 0); } if (pass.programSource.Empty()) { return fail("a Pass is missing an HLSLPROGRAM block", 0); } if (pass.vertexEntryPoint.Empty()) { return fail("a Pass is missing a #pragma vertex directive", 0); } if (pass.fragmentEntryPoint.Empty()) { return fail("a Pass is missing a #pragma fragment directive", 0); } } } return true; } LoadResult BuildShaderFromAuthoringDesc( const Containers::String& path, const AuthoringShaderDesc& authoringDesc) { auto shader = std::make_unique(); IResource::ConstructParams params; params.path = path; params.guid = ResourceGUID::Generate(path); params.name = authoringDesc.name; shader->Initialize(params); for (const ShaderPropertyDesc& property : authoringDesc.properties) { shader->AddProperty(property); } for (const AuthoringSubShaderEntry& subShader : authoringDesc.subShaders) { for (const AuthoringPassEntry& pass : subShader.passes) { ShaderPass shaderPass = {}; shaderPass.name = pass.name; shader->AddPass(shaderPass); for (const AuthoringTagEntry& subShaderTag : subShader.tags) { shader->SetPassTag(pass.name, subShaderTag.name, subShaderTag.value); } for (const AuthoringTagEntry& passTag : pass.tags) { shader->SetPassTag(pass.name, passTag.name, passTag.value); } for (const ShaderResourceBindingDesc& resourceBinding : pass.resources) { shader->AddPassResourceBinding(pass.name, resourceBinding); } for (const ShaderKeywordDeclaration& keywordDeclaration : pass.keywordDeclarations) { shader->AddPassKeywordDeclaration(pass.name, keywordDeclaration); } if (!pass.backendVariants.empty()) { for (const AuthoringBackendVariantEntry& backendVariant : pass.backendVariants) { ShaderStageVariant vertexVariant = {}; vertexVariant.stage = ShaderType::Vertex; vertexVariant.backend = backendVariant.backend; vertexVariant.language = backendVariant.language; vertexVariant.entryPoint = backendVariant.language == ShaderLanguage::HLSL && !pass.vertexEntryPoint.Empty() ? pass.vertexEntryPoint : GetDefaultEntryPoint(backendVariant.language, ShaderType::Vertex); vertexVariant.profile = !backendVariant.vertexProfile.Empty() ? backendVariant.vertexProfile : GetDefaultProfile(backendVariant.language, backendVariant.backend, ShaderType::Vertex); const Containers::String resolvedVertexPath = ResolveShaderDependencyPath(backendVariant.vertexSourcePath, path); if (!ReadTextFile(resolvedVertexPath, vertexVariant.sourceCode)) { return LoadResult("Failed to read shader authoring vertex source: " + resolvedVertexPath); } shader->AddPassVariant(pass.name, vertexVariant); ShaderStageVariant fragmentVariant = {}; fragmentVariant.stage = ShaderType::Fragment; fragmentVariant.backend = backendVariant.backend; fragmentVariant.language = backendVariant.language; fragmentVariant.entryPoint = backendVariant.language == ShaderLanguage::HLSL && !pass.fragmentEntryPoint.Empty() ? pass.fragmentEntryPoint : GetDefaultEntryPoint(backendVariant.language, ShaderType::Fragment); fragmentVariant.profile = !backendVariant.fragmentProfile.Empty() ? backendVariant.fragmentProfile : GetDefaultProfile(backendVariant.language, backendVariant.backend, ShaderType::Fragment); const Containers::String resolvedFragmentPath = ResolveShaderDependencyPath(backendVariant.fragmentSourcePath, path); if (!ReadTextFile(resolvedFragmentPath, fragmentVariant.sourceCode)) { return LoadResult("Failed to read shader authoring fragment source: " + resolvedFragmentPath); } shader->AddPassVariant(pass.name, fragmentVariant); } } else if (!pass.programSource.Empty()) { Containers::String combinedSource; AppendAuthoringSourceBlock(combinedSource, authoringDesc.sharedProgramSource); AppendAuthoringSourceBlock(combinedSource, subShader.sharedProgramSource); AppendAuthoringSourceBlock(combinedSource, pass.sharedProgramSource); AppendAuthoringSourceBlock(combinedSource, pass.programSource); combinedSource = StripUnityStyleAuthoringPragmas(combinedSource); ShaderStageVariant vertexVariant = {}; vertexVariant.stage = ShaderType::Vertex; vertexVariant.backend = ShaderBackend::Generic; vertexVariant.language = ShaderLanguage::HLSL; vertexVariant.entryPoint = !pass.vertexEntryPoint.Empty() ? pass.vertexEntryPoint : GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Vertex); vertexVariant.profile = GetDefaultProfile( ShaderLanguage::HLSL, ShaderBackend::Generic, ShaderType::Vertex); vertexVariant.sourceCode = combinedSource; shader->AddPassVariant(pass.name, vertexVariant); ShaderStageVariant fragmentVariant = {}; fragmentVariant.stage = ShaderType::Fragment; fragmentVariant.backend = ShaderBackend::Generic; fragmentVariant.language = ShaderLanguage::HLSL; fragmentVariant.entryPoint = !pass.fragmentEntryPoint.Empty() ? pass.fragmentEntryPoint : GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Fragment); fragmentVariant.profile = GetDefaultProfile( ShaderLanguage::HLSL, ShaderBackend::Generic, ShaderType::Fragment); fragmentVariant.sourceCode = combinedSource; shader->AddPassVariant(pass.name, fragmentVariant); } } } shader->m_memorySize = CalculateShaderMemorySize(*shader); return LoadResult(shader.release()); } bool LooksLikeShaderAuthoring(const std::string& sourceText) { return DetectShaderAuthoringStyle(sourceText) != ShaderAuthoringStyle::NotShaderAuthoring; } bool CollectLegacyBackendSplitShaderDependencyPaths( const Containers::String& path, const std::string& sourceText, Containers::Array& outDependencies) { outDependencies.Clear(); AuthoringShaderDesc authoringDesc = {}; Containers::String parseError; if (!ParseLegacyBackendSplitShaderAuthoring(path, sourceText, authoringDesc, &parseError)) { return false; } std::unordered_set seenPaths; for (const AuthoringSubShaderEntry& subShader : authoringDesc.subShaders) { for (const AuthoringPassEntry& pass : subShader.passes) { for (const AuthoringBackendVariantEntry& backendVariant : pass.backendVariants) { const Containers::String resolvedVertexPath = ResolveShaderDependencyPath(backendVariant.vertexSourcePath, path); const std::string vertexKey = ToStdString(resolvedVertexPath); if (!vertexKey.empty() && seenPaths.insert(vertexKey).second) { outDependencies.PushBack(resolvedVertexPath); } const Containers::String resolvedFragmentPath = ResolveShaderDependencyPath(backendVariant.fragmentSourcePath, path); const std::string fragmentKey = ToStdString(resolvedFragmentPath); if (!fragmentKey.empty() && seenPaths.insert(fragmentKey).second) { outDependencies.PushBack(resolvedFragmentPath); } } } } return true; } bool CollectUnityStyleSingleSourceShaderDependencyPaths( const Containers::String& path, const std::string& sourceText, Containers::Array& outDependencies) { outDependencies.Clear(); AuthoringShaderDesc authoringDesc = {}; Containers::String parseError; if (!ParseUnityStyleSingleSourceShaderAuthoring(path, sourceText, authoringDesc, &parseError)) { return false; } std::unordered_set seenPaths; CollectQuotedIncludeDependencyPaths(path, authoringDesc.sharedProgramSource, seenPaths, outDependencies); for (const AuthoringSubShaderEntry& subShader : authoringDesc.subShaders) { CollectQuotedIncludeDependencyPaths(path, subShader.sharedProgramSource, seenPaths, outDependencies); for (const AuthoringPassEntry& pass : subShader.passes) { CollectQuotedIncludeDependencyPaths(path, pass.sharedProgramSource, seenPaths, outDependencies); CollectQuotedIncludeDependencyPaths(path, pass.programSource, seenPaths, outDependencies); } } return true; } LoadResult LoadLegacyBackendSplitShaderAuthoring( const Containers::String& path, const std::string& sourceText) { AuthoringShaderDesc authoringDesc = {}; Containers::String parseError; if (!ParseLegacyBackendSplitShaderAuthoring(path, sourceText, authoringDesc, &parseError)) { return LoadResult(parseError); } return BuildShaderFromAuthoringDesc(path, authoringDesc); } LoadResult LoadUnityStyleSingleSourceShaderAuthoring( const Containers::String& path, const std::string& sourceText) { AuthoringShaderDesc authoringDesc = {}; Containers::String parseError; if (!ParseUnityStyleSingleSourceShaderAuthoring(path, sourceText, authoringDesc, &parseError)) { return LoadResult(parseError); } return BuildShaderFromAuthoringDesc(path, authoringDesc); } template bool ReadShaderArtifactValue(const Containers::Array& data, size_t& offset, T& outValue) { if (offset + sizeof(T) > data.Size()) { return false; } std::memcpy(&outValue, data.Data() + offset, sizeof(T)); offset += sizeof(T); return true; } bool ReadShaderArtifactString(const Containers::Array& data, size_t& offset, Containers::String& outValue) { Core::uint32 length = 0; if (!ReadShaderArtifactValue(data, offset, length)) { return false; } if (length == 0) { outValue.Clear(); return true; } if (offset + length > data.Size()) { return false; } outValue = Containers::String( std::string(reinterpret_cast(data.Data() + offset), length).c_str()); offset += length; return true; } bool TryParseUnsignedValue(const std::string& json, const char* key, Core::uint32& outValue) { size_t valuePos = 0; if (!FindValueStart(json, key, valuePos)) { return false; } size_t endPos = valuePos; while (endPos < json.size() && std::isdigit(static_cast(json[endPos])) != 0) { ++endPos; } if (endPos == valuePos) { return false; } try { outValue = static_cast(std::stoul(json.substr(valuePos, endPos - valuePos))); return true; } catch (...) { return false; } } size_t CalculateShaderMemorySize(const Shader& shader) { size_t memorySize = sizeof(Shader) + shader.GetName().Length() + shader.GetPath().Length(); for (const ShaderPropertyDesc& property : shader.GetProperties()) { memorySize += property.name.Length(); memorySize += property.displayName.Length(); memorySize += property.defaultValue.Length(); memorySize += property.semantic.Length(); } for (const ShaderPass& pass : shader.GetPasses()) { memorySize += pass.name.Length(); for (const ShaderPassTagEntry& tag : pass.tags) { memorySize += tag.name.Length(); memorySize += tag.value.Length(); } for (const ShaderResourceBindingDesc& binding : pass.resources) { memorySize += binding.name.Length(); memorySize += binding.semantic.Length(); } for (const ShaderKeywordDeclaration& declaration : pass.keywordDeclarations) { for (const Containers::String& option : declaration.options) { memorySize += option.Length(); } } for (const ShaderStageVariant& variant : pass.variants) { memorySize += variant.entryPoint.Length(); memorySize += variant.profile.Length(); memorySize += variant.sourceCode.Length(); memorySize += variant.compiledBinary.Size(); } } return memorySize; } ShaderType DetectShaderTypeFromPath(const Containers::String& path) { const Containers::String ext = GetPathExtension(path).ToLower(); if (ext == "vert") return ShaderType::Vertex; if (ext == "frag") return ShaderType::Fragment; if (ext == "geom") return ShaderType::Geometry; if (ext == "comp") return ShaderType::Compute; return ShaderType::Fragment; } bool LooksLikeShaderManifest(const std::string& sourceText) { const size_t firstContentPos = SkipWhitespace(sourceText, 0); return firstContentPos < sourceText.size() && sourceText[firstContentPos] == '{' && sourceText.find("\"passes\"") != std::string::npos; } bool CollectShaderManifestDependencyPaths(const Containers::String& path, const std::string& jsonText, Containers::Array& outDependencies) { outDependencies.Clear(); std::string passesArray; if (!TryExtractArray(jsonText, "passes", passesArray)) { return false; } std::vector passObjects; if (!SplitTopLevelArrayElements(passesArray, passObjects)) { return false; } std::unordered_set seenPaths; for (const std::string& passObject : passObjects) { std::string variantsArray; if (!TryExtractArray(passObject, "variants", variantsArray)) { return false; } std::vector variantObjects; if (!SplitTopLevelArrayElements(variantsArray, variantObjects)) { return false; } for (const std::string& variantObject : variantObjects) { Containers::String sourcePath; if (!TryParseStringValue(variantObject, "source", sourcePath) && !TryParseStringValue(variantObject, "sourcePath", sourcePath)) { continue; } const Containers::String resolvedPath = ResolveShaderDependencyPath(sourcePath, path); const std::string key = ToStdString(resolvedPath); if (!key.empty() && seenPaths.insert(key).second) { outDependencies.PushBack(resolvedPath); } } } return true; } LoadResult LoadShaderManifest(const Containers::String& path, const std::string& jsonText) { std::string passesArray; if (!TryExtractArray(jsonText, "passes", passesArray)) { return LoadResult("Shader manifest is missing a valid passes array: " + path); } std::vector passObjects; if (!SplitTopLevelArrayElements(passesArray, passObjects) || passObjects.empty()) { return LoadResult("Shader manifest does not contain any pass objects: " + path); } auto shader = std::make_unique(); IResource::ConstructParams params; params.path = path; params.guid = ResourceGUID::Generate(path); Containers::String manifestName; if (TryParseStringValue(jsonText, "name", manifestName) && !manifestName.Empty()) { params.name = manifestName; } else { const std::filesystem::path shaderPath(path.CStr()); const std::string stem = shaderPath.stem().generic_string(); params.name = stem.empty() ? path : Containers::String(stem.c_str()); } shader->Initialize(params); std::string propertiesArray; if (TryExtractArray(jsonText, "properties", propertiesArray)) { std::vector propertyObjects; if (!SplitTopLevelArrayElements(propertiesArray, propertyObjects)) { return LoadResult("Shader manifest properties array could not be parsed: " + path); } for (const std::string& propertyObject : propertyObjects) { ShaderPropertyDesc property = {}; if (!TryParseStringValue(propertyObject, "name", property.name) || property.name.Empty()) { return LoadResult("Shader manifest property is missing a valid name: " + path); } Containers::String propertyTypeName; if (!TryParseStringValue(propertyObject, "type", propertyTypeName) || !TryParseShaderPropertyType(propertyTypeName, property.type)) { return LoadResult("Shader manifest property has an invalid type: " + path); } if (!TryParseStringValue(propertyObject, "displayName", property.displayName)) { property.displayName = property.name; } if (!TryParseStringValue(propertyObject, "defaultValue", property.defaultValue)) { TryParseStringValue(propertyObject, "default", property.defaultValue); } TryParseStringValue(propertyObject, "semantic", property.semantic); shader->AddProperty(property); } } for (const std::string& passObject : passObjects) { Containers::String passName; if (!TryParseStringValue(passObject, "name", passName) || passName.Empty()) { return LoadResult("Shader manifest pass is missing a valid name: " + path); } std::string tagsObject; if (TryExtractObject(passObject, "tags", tagsObject)) { if (!TryParseStringMapObject( tagsObject, [shaderPtr = shader.get(), &passName](const Containers::String& key, const Containers::String& value) { shaderPtr->SetPassTag(passName, key, value); })) { return LoadResult("Shader manifest pass tags could not be parsed: " + path); } } std::string resourcesArray; if (TryExtractArray(passObject, "resources", resourcesArray)) { std::vector resourceObjects; if (!SplitTopLevelArrayElements(resourcesArray, resourceObjects)) { return LoadResult("Shader manifest pass resources could not be parsed: " + path); } for (const std::string& resourceObject : resourceObjects) { ShaderResourceBindingDesc resourceBinding = {}; if (!TryParseStringValue(resourceObject, "name", resourceBinding.name) || resourceBinding.name.Empty()) { return LoadResult("Shader manifest pass resource is missing a valid name: " + path); } Containers::String resourceTypeName; if (!TryParseStringValue(resourceObject, "type", resourceTypeName) || !TryParseShaderResourceType(resourceTypeName, resourceBinding.type)) { return LoadResult("Shader manifest pass resource has an invalid type: " + path); } if (!TryParseUnsignedValue(resourceObject, "set", resourceBinding.set)) { return LoadResult("Shader manifest pass resource is missing a valid set: " + path); } if (!TryParseUnsignedValue(resourceObject, "binding", resourceBinding.binding)) { return LoadResult("Shader manifest pass resource is missing a valid binding: " + path); } TryParseStringValue(resourceObject, "semantic", resourceBinding.semantic); shader->AddPassResourceBinding(passName, resourceBinding); } } std::string variantsArray; if (!TryExtractArray(passObject, "variants", variantsArray)) { return LoadResult("Shader manifest pass is missing variants: " + path); } std::vector variantObjects; if (!SplitTopLevelArrayElements(variantsArray, variantObjects) || variantObjects.empty()) { return LoadResult("Shader manifest pass does not contain any variants: " + path); } for (const std::string& variantObject : variantObjects) { ShaderStageVariant variant = {}; Containers::String stageName; if (!TryParseStringValue(variantObject, "stage", stageName) || !TryParseShaderType(stageName, variant.stage)) { return LoadResult("Shader manifest variant has an invalid stage: " + path); } Containers::String backendName; if (!TryParseStringValue(variantObject, "backend", backendName) || !TryParseShaderBackend(backendName, variant.backend)) { return LoadResult("Shader manifest variant has an invalid backend: " + path); } Containers::String languageName; if (!TryParseStringValue(variantObject, "language", languageName) || !TryParseShaderLanguage(languageName, variant.language)) { return LoadResult("Shader manifest variant has an invalid language: " + path); } Containers::String sourceCode; if (TryParseStringValue(variantObject, "sourceCode", sourceCode)) { variant.sourceCode = sourceCode; } else { Containers::String sourcePath; if (!TryParseStringValue(variantObject, "source", sourcePath) && !TryParseStringValue(variantObject, "sourcePath", sourcePath)) { return LoadResult("Shader manifest variant is missing source/sourceCode: " + path); } const Containers::String resolvedSourcePath = ResolveShaderDependencyPath(sourcePath, path); if (!ReadTextFile(resolvedSourcePath, variant.sourceCode)) { return LoadResult("Failed to read shader variant source: " + resolvedSourcePath); } } if (!TryParseStringValue(variantObject, "entryPoint", variant.entryPoint)) { variant.entryPoint = GetDefaultEntryPoint(variant.language, variant.stage); } if (!TryParseStringValue(variantObject, "profile", variant.profile)) { variant.profile = GetDefaultProfile(variant.language, variant.backend, variant.stage); } shader->AddPassVariant(passName, variant); } } shader->m_memorySize = CalculateShaderMemorySize(*shader); return LoadResult(shader.release()); } LoadResult LoadShaderArtifact(const Containers::String& path) { const Containers::Array data = ReadShaderFileData(path); if (data.Empty()) { return LoadResult("Failed to read shader artifact: " + path); } size_t offset = 0; ShaderArtifactFileHeader fileHeader; if (!ReadShaderArtifactValue(data, offset, fileHeader)) { return LoadResult("Failed to parse shader artifact header: " + path); } const std::string magic(fileHeader.magic, fileHeader.magic + 7); const bool isLegacySchema = magic == "XCSHD01" && fileHeader.schemaVersion == 1u; const bool isCurrentSchema = magic == "XCSHD02" && fileHeader.schemaVersion == kShaderArtifactSchemaVersion; if (!isLegacySchema && !isCurrentSchema) { return LoadResult("Invalid shader artifact header: " + path); } auto shader = std::make_unique(); Containers::String shaderName; Containers::String shaderSourcePath; if (!ReadShaderArtifactString(data, offset, shaderName) || !ReadShaderArtifactString(data, offset, shaderSourcePath)) { return LoadResult("Failed to parse shader artifact strings: " + path); } shader->m_name = shaderName.Empty() ? path : shaderName; shader->m_path = shaderSourcePath.Empty() ? path : shaderSourcePath; shader->m_guid = ResourceGUID::Generate(shader->m_path); ShaderArtifactHeader shaderHeader; if (!ReadShaderArtifactValue(data, offset, shaderHeader)) { return LoadResult("Failed to parse shader artifact body: " + path); } for (Core::uint32 propertyIndex = 0; propertyIndex < shaderHeader.propertyCount; ++propertyIndex) { ShaderPropertyDesc property = {}; ShaderPropertyArtifact propertyArtifact; if (!ReadShaderArtifactString(data, offset, property.name) || !ReadShaderArtifactString(data, offset, property.displayName) || !ReadShaderArtifactString(data, offset, property.defaultValue) || !ReadShaderArtifactString(data, offset, property.semantic) || !ReadShaderArtifactValue(data, offset, propertyArtifact)) { return LoadResult("Failed to read shader artifact properties: " + path); } property.type = static_cast(propertyArtifact.propertyType); shader->AddProperty(property); } for (Core::uint32 passIndex = 0; passIndex < shaderHeader.passCount; ++passIndex) { Containers::String passName; Core::uint32 tagCount = 0; Core::uint32 resourceCount = 0; Core::uint32 keywordDeclarationCount = 0; Core::uint32 variantCount = 0; if (!ReadShaderArtifactString(data, offset, passName)) { return LoadResult("Failed to read shader artifact passes: " + path); } if (isLegacySchema) { ShaderPassArtifactHeaderV1 passHeader = {}; if (!ReadShaderArtifactValue(data, offset, passHeader)) { return LoadResult("Failed to read shader artifact passes: " + path); } tagCount = passHeader.tagCount; resourceCount = passHeader.resourceCount; variantCount = passHeader.variantCount; } else { ShaderPassArtifactHeader passHeader = {}; if (!ReadShaderArtifactValue(data, offset, passHeader)) { return LoadResult("Failed to read shader artifact passes: " + path); } tagCount = passHeader.tagCount; resourceCount = passHeader.resourceCount; keywordDeclarationCount = passHeader.keywordDeclarationCount; variantCount = passHeader.variantCount; } ShaderPass pass = {}; pass.name = passName; shader->AddPass(pass); for (Core::uint32 tagIndex = 0; tagIndex < tagCount; ++tagIndex) { Containers::String tagName; Containers::String tagValue; if (!ReadShaderArtifactString(data, offset, tagName) || !ReadShaderArtifactString(data, offset, tagValue)) { return LoadResult("Failed to read shader artifact pass tags: " + path); } shader->SetPassTag(passName, tagName, tagValue); } for (Core::uint32 resourceIndex = 0; resourceIndex < resourceCount; ++resourceIndex) { ShaderResourceBindingDesc binding = {}; ShaderResourceArtifact resourceArtifact; if (!ReadShaderArtifactString(data, offset, binding.name) || !ReadShaderArtifactString(data, offset, binding.semantic) || !ReadShaderArtifactValue(data, offset, resourceArtifact)) { return LoadResult("Failed to read shader artifact pass resources: " + path); } binding.type = static_cast(resourceArtifact.resourceType); binding.set = resourceArtifact.set; binding.binding = resourceArtifact.binding; shader->AddPassResourceBinding(passName, binding); } for (Core::uint32 declarationIndex = 0; declarationIndex < keywordDeclarationCount; ++declarationIndex) { ShaderKeywordDeclaration declaration = {}; ShaderKeywordDeclarationArtifactHeader declarationHeader = {}; if (!ReadShaderArtifactValue(data, offset, declarationHeader)) { return LoadResult("Failed to read shader artifact pass keywords: " + path); } declaration.type = static_cast(declarationHeader.declarationType); declaration.options.Reserve(declarationHeader.optionCount); for (Core::uint32 optionIndex = 0; optionIndex < declarationHeader.optionCount; ++optionIndex) { Containers::String option; if (!ReadShaderArtifactString(data, offset, option)) { return LoadResult("Failed to read shader artifact keyword options: " + path); } declaration.options.PushBack(option); } shader->AddPassKeywordDeclaration(passName, declaration); } for (Core::uint32 variantIndex = 0; variantIndex < variantCount; ++variantIndex) { ShaderStageVariant variant = {}; ShaderVariantArtifactHeader variantHeader; if (!ReadShaderArtifactValue(data, offset, variantHeader) || !ReadShaderArtifactString(data, offset, variant.entryPoint) || !ReadShaderArtifactString(data, offset, variant.profile) || !ReadShaderArtifactString(data, offset, variant.sourceCode)) { return LoadResult("Failed to read shader artifact variants: " + path); } variant.stage = static_cast(variantHeader.stage); variant.language = static_cast(variantHeader.language); variant.backend = static_cast(variantHeader.backend); if (variantHeader.compiledBinarySize > 0) { if (offset + variantHeader.compiledBinarySize > data.Size()) { return LoadResult("Shader artifact variant binary payload is truncated: " + path); } variant.compiledBinary.Resize(static_cast(variantHeader.compiledBinarySize)); std::memcpy( variant.compiledBinary.Data(), data.Data() + offset, static_cast(variantHeader.compiledBinarySize)); offset += static_cast(variantHeader.compiledBinarySize); } shader->AddPassVariant(passName, variant); } } if (shader->GetPassCount() == 1) { const ShaderPass* defaultPass = shader->FindPass("Default"); if (defaultPass != nullptr && defaultPass->variants.Size() == 1u) { const ShaderStageVariant& variant = defaultPass->variants[0]; if (variant.backend == ShaderBackend::Generic) { shader->SetShaderType(variant.stage); shader->SetShaderLanguage(variant.language); shader->SetSourceCode(variant.sourceCode); shader->SetCompiledBinary(variant.compiledBinary); } } } shader->m_isValid = true; shader->m_memorySize = CalculateShaderMemorySize(*shader); return LoadResult(shader.release()); } LoadResult LoadLegacySingleStageShader(const Containers::String& path, const std::string& sourceText) { auto shader = std::make_unique(); shader->m_path = path; shader->m_name = path; shader->m_guid = ResourceGUID::Generate(path); const Containers::String ext = GetPathExtension(path).ToLower(); if (ext == "hlsl") { shader->SetShaderLanguage(ShaderLanguage::HLSL); } else { shader->SetShaderLanguage(ShaderLanguage::GLSL); } shader->SetShaderType(DetectShaderTypeFromPath(path)); shader->SetSourceCode(sourceText.c_str()); shader->m_isValid = true; shader->m_memorySize = sizeof(Shader) + shader->m_name.Length() + shader->m_path.Length() + shader->GetSourceCode().Length(); return LoadResult(shader.release()); } } // namespace ShaderLoader::ShaderLoader() = default; ShaderLoader::~ShaderLoader() = default; Containers::Array ShaderLoader::GetSupportedExtensions() const { Containers::Array extensions; extensions.PushBack("vert"); extensions.PushBack("frag"); extensions.PushBack("geom"); extensions.PushBack("comp"); extensions.PushBack("glsl"); extensions.PushBack("hlsl"); extensions.PushBack("shader"); extensions.PushBack("xcshader"); return extensions; } bool ShaderLoader::CanLoad(const Containers::String& path) const { if (IsBuiltinShaderPath(path)) { return true; } const Containers::String ext = GetExtension(path).ToLower(); return ext == "vert" || ext == "frag" || ext == "geom" || ext == "comp" || ext == "glsl" || ext == "hlsl" || ext == "shader" || ext == "xcshader"; } LoadResult ShaderLoader::Load(const Containers::String& path, const ImportSettings* settings) { (void)settings; if (IsBuiltinShaderPath(path)) { return CreateBuiltinShaderResource(path); } const Containers::String ext = GetPathExtension(path).ToLower(); if (ext == "xcshader") { return LoadShaderArtifact(path); } const Containers::Array data = ReadShaderFileData(path); if (data.Empty()) { return LoadResult("Failed to read shader file: " + path); } const std::string sourceText = ToStdString(data); if (ext == "shader" && LooksLikeShaderManifest(sourceText)) { return LoadShaderManifest(path, sourceText); } if (ext == "shader") { switch (DetectShaderAuthoringStyle(sourceText)) { case ShaderAuthoringStyle::LegacyBackendSplit: return LoadLegacyBackendSplitShaderAuthoring(path, sourceText); case ShaderAuthoringStyle::UnityStyleSingleSource: return LoadUnityStyleSingleSourceShaderAuthoring(path, sourceText); case ShaderAuthoringStyle::NotShaderAuthoring: default: break; } } return LoadLegacySingleStageShader(path, sourceText); } ImportSettings* ShaderLoader::GetDefaultSettings() const { return nullptr; } bool ShaderLoader::CollectSourceDependencies(const Containers::String& path, Containers::Array& outDependencies) const { outDependencies.Clear(); if (IsBuiltinShaderPath(path)) { return true; } const Containers::String ext = GetPathExtension(path).ToLower(); if (ext != "shader") { return true; } const Containers::Array data = ReadShaderFileData(path); if (data.Empty()) { return false; } const std::string sourceText = ToStdString(data); if (!LooksLikeShaderManifest(sourceText)) { switch (DetectShaderAuthoringStyle(sourceText)) { case ShaderAuthoringStyle::LegacyBackendSplit: return CollectLegacyBackendSplitShaderDependencyPaths(path, sourceText, outDependencies); case ShaderAuthoringStyle::UnityStyleSingleSource: return CollectUnityStyleSingleSourceShaderDependencyPaths(path, sourceText, outDependencies); case ShaderAuthoringStyle::NotShaderAuthoring: default: return true; } } return CollectShaderManifestDependencyPaths(path, sourceText, outDependencies); } ShaderType ShaderLoader::DetectShaderType(const Containers::String& path, const Containers::String& source) { (void)source; return DetectShaderTypeFromPath(path); } bool ShaderLoader::ParseShaderSource(const Containers::String& source, Shader* shader) { (void)source; (void)shader; return true; } } // namespace Resources } // namespace XCEngine