Add shader artifact import pipeline
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user