Add shader artifact import pipeline

This commit is contained in:
2026-04-03 14:56:51 +08:00
parent 0f51f553c8
commit d4afa022c1
8 changed files with 1095 additions and 4 deletions

View File

@@ -1,15 +1,18 @@
#include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <XCEngine/Core/Asset/ArtifactFormats.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <cctype>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <functional>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
namespace XCEngine {
@@ -21,6 +24,10 @@ std::string ToStdString(const Containers::Array<Core::uint8>& data) {
return std::string(reinterpret_cast<const char*>(data.Data()), data.Size());
}
std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
Containers::Array<Core::uint8> TryReadFileData(
const std::filesystem::path& filePath,
bool& opened) {
@@ -576,6 +583,40 @@ bool ReadTextFile(const Containers::String& path, Containers::String& outText) {
return true;
}
template<typename T>
bool ReadShaderArtifactValue(const Containers::Array<Core::uint8>& 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<Core::uint8>& 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<const char*>(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)) {
@@ -645,6 +686,51 @@ bool LooksLikeShaderManifest(const std::string& sourceText) {
sourceText.find("\"passes\"") != std::string::npos;
}
bool CollectShaderManifestDependencyPaths(const Containers::String& path,
const std::string& jsonText,
Containers::Array<Containers::String>& outDependencies) {
outDependencies.Clear();
std::string passesArray;
if (!TryExtractArray(jsonText, "passes", passesArray)) {
return false;
}
std::vector<std::string> passObjects;
if (!SplitTopLevelArrayElements(passesArray, passObjects)) {
return false;
}
std::unordered_set<std::string> seenPaths;
for (const std::string& passObject : passObjects) {
std::string variantsArray;
if (!TryExtractArray(passObject, "variants", variantsArray)) {
return false;
}
std::vector<std::string> 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)) {
@@ -816,6 +902,143 @@ LoadResult LoadShaderManifest(const Containers::String& path, const std::string&
return LoadResult(shader.release());
}
LoadResult LoadShaderArtifact(const Containers::String& path) {
const Containers::Array<Core::uint8> 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<Shader>();
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<ShaderPropertyType>(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<ShaderResourceType>(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<ShaderType>(variantHeader.stage);
variant.language = static_cast<ShaderLanguage>(variantHeader.language);
variant.backend = static_cast<ShaderBackend>(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<size_t>(variantHeader.compiledBinarySize));
std::memcpy(
variant.compiledBinary.Data(),
data.Data() + offset,
static_cast<size_t>(variantHeader.compiledBinarySize));
offset += static_cast<size_t>(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>();
shader->m_path = path;
@@ -856,6 +1079,7 @@ Containers::Array<Containers::String> ShaderLoader::GetSupportedExtensions() con
extensions.PushBack("glsl");
extensions.PushBack("hlsl");
extensions.PushBack("shader");
extensions.PushBack("xcshader");
return extensions;
}
@@ -866,7 +1090,8 @@ bool ShaderLoader::CanLoad(const Containers::String& path) const {
const Containers::String ext = GetExtension(path).ToLower();
return ext == "vert" || ext == "frag" || ext == "geom" ||
ext == "comp" || ext == "glsl" || ext == "hlsl" || ext == "shader";
ext == "comp" || ext == "glsl" || ext == "hlsl" ||
ext == "shader" || ext == "xcshader";
}
LoadResult ShaderLoader::Load(const Containers::String& path, const ImportSettings* settings) {
@@ -876,13 +1101,17 @@ LoadResult ShaderLoader::Load(const Containers::String& path, const ImportSettin
return CreateBuiltinShaderResource(path);
}
const Containers::String ext = GetPathExtension(path).ToLower();
if (ext == "xcshader") {
return LoadShaderArtifact(path);
}
const Containers::Array<Core::uint8> data = ReadShaderFileData(path);
if (data.Empty()) {
return LoadResult("Failed to read shader file: " + path);
}
const std::string sourceText = ToStdString(data);
const Containers::String ext = GetPathExtension(path).ToLower();
if (ext == "shader" && LooksLikeShaderManifest(sourceText)) {
return LoadShaderManifest(path, sourceText);
}
@@ -894,6 +1123,32 @@ ImportSettings* ShaderLoader::GetDefaultSettings() const {
return nullptr;
}
bool ShaderLoader::CollectSourceDependencies(const Containers::String& path,
Containers::Array<Containers::String>& outDependencies) const {
outDependencies.Clear();
if (IsBuiltinShaderPath(path)) {
return true;
}
const Containers::String ext = GetPathExtension(path).ToLower();
if (ext != "shader") {
return true;
}
const Containers::Array<Core::uint8> 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);