#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; } 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 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); if (magic != "XCSHD01" || fileHeader.schemaVersion != kShaderArtifactSchemaVersion) { 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; ShaderPassArtifactHeader passHeader; if (!ReadShaderArtifactString(data, offset, passName) || !ReadShaderArtifactValue(data, offset, passHeader)) { return LoadResult("Failed to read shader artifact passes: " + path); } ShaderPass pass = {}; pass.name = passName; shader->AddPass(pass); for (Core::uint32 tagIndex = 0; tagIndex < passHeader.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 < passHeader.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 variantIndex = 0; variantIndex < passHeader.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); } 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)) { 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