rendering: formalize shader keyword metadata contract

This commit is contained in:
2026-04-06 18:55:26 +08:00
parent 7acc397714
commit a8b4da16a3
16 changed files with 795 additions and 16 deletions

View File

@@ -11,9 +11,9 @@ namespace XCEngine {
namespace Resources {
constexpr Core::uint32 kTextureArtifactSchemaVersion = 1;
constexpr Core::uint32 kMaterialArtifactSchemaVersion = 2;
constexpr Core::uint32 kMaterialArtifactSchemaVersion = 3;
constexpr Core::uint32 kMeshArtifactSchemaVersion = 2;
constexpr Core::uint32 kShaderArtifactSchemaVersion = 1;
constexpr Core::uint32 kShaderArtifactSchemaVersion = 2;
constexpr Core::uint32 kUIDocumentArtifactSchemaVersion = 2;
struct TextureArtifactHeader {
@@ -46,14 +46,23 @@ struct MeshArtifactHeader {
};
struct MaterialArtifactFileHeader {
char magic[8] = { 'X', 'C', 'M', 'A', 'T', '0', '2', '\0' };
char magic[8] = { 'X', 'C', 'M', 'A', 'T', '0', '3', '\0' };
Core::uint32 schemaVersion = kMaterialArtifactSchemaVersion;
};
struct MaterialArtifactHeaderV2 {
Core::int32 renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
MaterialRenderState renderState = {};
Core::uint32 tagCount = 0;
Core::uint32 propertyCount = 0;
Core::uint32 textureBindingCount = 0;
};
struct MaterialArtifactHeader {
Core::int32 renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
MaterialRenderState renderState = {};
Core::uint32 tagCount = 0;
Core::uint32 keywordCount = 0;
Core::uint32 propertyCount = 0;
Core::uint32 textureBindingCount = 0;
};
@@ -64,7 +73,7 @@ struct MaterialPropertyArtifact {
};
struct ShaderArtifactFileHeader {
char magic[8] = { 'X', 'C', 'S', 'H', 'D', '0', '1', '\0' };
char magic[8] = { 'X', 'C', 'S', 'H', 'D', '0', '2', '\0' };
Core::uint32 schemaVersion = kShaderArtifactSchemaVersion;
};
@@ -73,9 +82,16 @@ struct ShaderArtifactHeader {
Core::uint32 passCount = 0;
};
struct ShaderPassArtifactHeaderV1 {
Core::uint32 tagCount = 0;
Core::uint32 resourceCount = 0;
Core::uint32 variantCount = 0;
};
struct ShaderPassArtifactHeader {
Core::uint32 tagCount = 0;
Core::uint32 resourceCount = 0;
Core::uint32 keywordDeclarationCount = 0;
Core::uint32 variantCount = 0;
};
@@ -96,6 +112,11 @@ struct ShaderVariantArtifactHeader {
Core::uint64 compiledBinarySize = 0;
};
struct ShaderKeywordDeclarationArtifactHeader {
Core::uint32 declarationType = 0;
Core::uint32 optionCount = 0;
};
struct UIDocumentArtifactFileHeader {
char magic[8] = { 'X', 'C', 'U', 'I', 'D', '0', '1', '\0' };
Core::uint32 schemaVersion = kUIDocumentArtifactSchemaVersion;

View File

@@ -192,6 +192,15 @@ public:
Containers::String GetTagName(Core::uint32 index) const;
Containers::String GetTagValue(Core::uint32 index) const;
const Containers::Array<MaterialTagEntry>& GetTags() const { return m_tags; }
void EnableKeyword(const Containers::String& keyword);
void DisableKeyword(const Containers::String& keyword);
void SetKeywordEnabled(const Containers::String& keyword, bool enabled);
bool IsKeywordEnabled(const Containers::String& keyword) const;
void ClearKeywords();
Core::uint32 GetKeywordCount() const { return static_cast<Core::uint32>(m_keywordSet.enabledKeywords.Size()); }
Containers::String GetKeyword(Core::uint32 index) const;
const ShaderKeywordSet& GetKeywordSet() const { return m_keywordSet; }
void SetFloat(const Containers::String& name, float value);
void SetFloat2(const Containers::String& name, const Math::Vector2& value);
@@ -242,12 +251,14 @@ private:
void ResolvePendingTextureBindings();
void MarkChanged(bool updateConstantBuffer);
void UpdateMemorySize();
void SyncShaderSchemaKeywords(bool removeUnknownKeywords);
ResourceHandle<class Shader> m_shader;
Core::int32 m_renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
MaterialRenderState m_renderState;
Containers::String m_shaderPass;
Containers::Array<MaterialTagEntry> m_tags;
ShaderKeywordSet m_keywordSet;
Containers::HashMap<Containers::String, MaterialProperty> m_properties;
Containers::Array<MaterialConstantFieldDesc> m_constantLayout;
Containers::Array<Core::uint8> m_constantBufferData;

View File

@@ -3,6 +3,7 @@
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Containers/Array.h>
#include <XCEngine/Core/Types.h>
#include <XCEngine/Resources/Shader/ShaderKeywordTypes.h>
namespace XCEngine {
namespace Resources {
@@ -97,6 +98,7 @@ struct ShaderPass {
Containers::String name;
Containers::Array<ShaderPassTagEntry> tags;
Containers::Array<ShaderResourceBindingDesc> resources;
Containers::Array<ShaderKeywordDeclaration> keywordDeclarations;
Containers::Array<ShaderStageVariant> variants;
};
@@ -149,9 +151,16 @@ public:
void AddPassResourceBinding(
const Containers::String& passName,
const ShaderResourceBindingDesc& binding);
void AddPassKeywordDeclaration(
const Containers::String& passName,
const ShaderKeywordDeclaration& declaration);
bool HasPass(const Containers::String& passName) const;
const ShaderPass* FindPass(const Containers::String& passName) const;
ShaderPass* FindPass(const Containers::String& passName);
bool PassDeclaresKeyword(
const Containers::String& passName,
const Containers::String& keyword) const;
bool DeclaresKeyword(const Containers::String& keyword) const;
const ShaderResourceBindingDesc* FindPassResourceBinding(
const Containers::String& passName,
const Containers::String& resourceName) const;

View File

@@ -0,0 +1,34 @@
#pragma once
#include <XCEngine/Core/Containers/Array.h>
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/Types.h>
namespace XCEngine {
namespace Resources {
enum class ShaderKeywordDeclarationType : Core::uint8 {
MultiCompile = 0,
ShaderFeature,
ShaderFeatureLocal
};
struct ShaderKeywordDeclaration {
ShaderKeywordDeclarationType type = ShaderKeywordDeclarationType::MultiCompile;
Containers::Array<Containers::String> options;
bool IsLocal() const {
return type == ShaderKeywordDeclarationType::ShaderFeatureLocal;
}
};
struct ShaderKeywordSet {
Containers::Array<Containers::String> enabledKeywords;
bool Empty() const {
return enabledKeywords.Empty();
}
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -451,6 +451,7 @@ bool WriteMaterialArtifactFile(
header.renderQueue = material.GetRenderQueue();
header.renderState = material.GetRenderState();
header.tagCount = material.GetTagCount();
header.keywordCount = material.GetKeywordCount();
const std::vector<MaterialProperty> properties = GatherMaterialProperties(material);
std::vector<MaterialProperty> nonTextureProperties;
@@ -472,6 +473,10 @@ bool WriteMaterialArtifactFile(
WriteString(output, material.GetTagValue(tagIndex));
}
for (Core::uint32 keywordIndex = 0; keywordIndex < material.GetKeywordCount(); ++keywordIndex) {
WriteString(output, material.GetKeyword(keywordIndex));
}
for (const MaterialProperty& property : nonTextureProperties) {
WriteString(output, property.name);
@@ -528,6 +533,7 @@ bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader)
ShaderPassArtifactHeader passHeader;
passHeader.tagCount = static_cast<Core::uint32>(pass.tags.Size());
passHeader.resourceCount = static_cast<Core::uint32>(pass.resources.Size());
passHeader.keywordDeclarationCount = static_cast<Core::uint32>(pass.keywordDeclarations.Size());
passHeader.variantCount = static_cast<Core::uint32>(pass.variants.Size());
output.write(reinterpret_cast<const char*>(&passHeader), sizeof(passHeader));
@@ -547,6 +553,17 @@ bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader)
output.write(reinterpret_cast<const char*>(&resourceArtifact), sizeof(resourceArtifact));
}
for (const ShaderKeywordDeclaration& declaration : pass.keywordDeclarations) {
ShaderKeywordDeclarationArtifactHeader declarationHeader;
declarationHeader.declarationType = static_cast<Core::uint32>(declaration.type);
declarationHeader.optionCount = static_cast<Core::uint32>(declaration.options.Size());
output.write(reinterpret_cast<const char*>(&declarationHeader), sizeof(declarationHeader));
for (const Containers::String& option : declaration.options) {
WriteString(output, option);
}
}
for (const ShaderStageVariant& variant : pass.variants) {
ShaderVariantArtifactHeader variantHeader;
variantHeader.stage = static_cast<Core::uint32>(variant.stage);

View File

@@ -658,6 +658,11 @@ size_t CalculateBuiltinShaderMemorySize(const Shader& shader) {
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();

View File

@@ -95,6 +95,21 @@ bool IsTextureMaterialPropertyType(MaterialPropertyType type) {
return type == MaterialPropertyType::Texture || type == MaterialPropertyType::Cubemap;
}
Containers::String NormalizeShaderKeyword(const Containers::String& keyword) {
const Containers::String normalized = keyword.Trim();
if (normalized.Empty() ||
normalized == Containers::String("_") ||
normalized == Containers::String("__")) {
return Containers::String();
}
return normalized;
}
bool CompareShaderKeywords(const Containers::String& left, const Containers::String& right) {
return std::strcmp(left.CStr(), right.CStr()) < 0;
}
MaterialPropertyType GetMaterialPropertyTypeForShaderProperty(ShaderPropertyType type) {
switch (type) {
case ShaderPropertyType::Float:
@@ -312,6 +327,7 @@ Material::~Material() {
// resetting them here avoids teardown-order issues during destruction.
m_shader.Reset();
m_tags = Containers::Array<MaterialTagEntry>();
m_keywordSet = ShaderKeywordSet();
m_properties = Containers::HashMap<Containers::String, MaterialProperty>();
m_constantLayout = Containers::Array<MaterialConstantFieldDesc>();
m_constantBufferData = Containers::Array<Core::uint8>();
@@ -324,6 +340,7 @@ void Material::Release() {
m_renderState = MaterialRenderState();
m_shaderPass.Clear();
m_tags.Clear();
m_keywordSet.enabledKeywords.Clear();
m_properties.Clear();
m_constantLayout.Clear();
m_textureBindings.Clear();
@@ -336,6 +353,7 @@ void Material::Release() {
void Material::SetShader(const ResourceHandle<Shader>& shader) {
m_shader = shader;
SyncShaderSchemaProperties(true);
SyncShaderSchemaKeywords(true);
MarkChanged(true);
}
@@ -420,6 +438,90 @@ Containers::String Material::GetTagValue(Core::uint32 index) const {
return index < m_tags.Size() ? m_tags[index].value : Containers::String();
}
void Material::EnableKeyword(const Containers::String& keyword) {
const Containers::String normalizedKeyword = NormalizeShaderKeyword(keyword);
if (normalizedKeyword.Empty()) {
return;
}
if (m_shader.Get() != nullptr && !m_shader->DeclaresKeyword(normalizedKeyword)) {
return;
}
for (const Containers::String& existingKeyword : m_keywordSet.enabledKeywords) {
if (existingKeyword == normalizedKeyword) {
return;
}
}
m_keywordSet.enabledKeywords.PushBack(normalizedKeyword);
std::sort(
m_keywordSet.enabledKeywords.begin(),
m_keywordSet.enabledKeywords.end(),
CompareShaderKeywords);
MarkChanged(false);
}
void Material::DisableKeyword(const Containers::String& keyword) {
const Containers::String normalizedKeyword = NormalizeShaderKeyword(keyword);
if (normalizedKeyword.Empty()) {
return;
}
for (size_t keywordIndex = 0; keywordIndex < m_keywordSet.enabledKeywords.Size(); ++keywordIndex) {
if (m_keywordSet.enabledKeywords[keywordIndex] == normalizedKeyword) {
if (keywordIndex != m_keywordSet.enabledKeywords.Size() - 1) {
m_keywordSet.enabledKeywords[keywordIndex] = std::move(m_keywordSet.enabledKeywords.Back());
}
m_keywordSet.enabledKeywords.PopBack();
std::sort(
m_keywordSet.enabledKeywords.begin(),
m_keywordSet.enabledKeywords.end(),
CompareShaderKeywords);
MarkChanged(false);
return;
}
}
}
void Material::SetKeywordEnabled(const Containers::String& keyword, bool enabled) {
if (enabled) {
EnableKeyword(keyword);
} else {
DisableKeyword(keyword);
}
}
bool Material::IsKeywordEnabled(const Containers::String& keyword) const {
const Containers::String normalizedKeyword = NormalizeShaderKeyword(keyword);
if (normalizedKeyword.Empty()) {
return false;
}
for (const Containers::String& existingKeyword : m_keywordSet.enabledKeywords) {
if (existingKeyword == normalizedKeyword) {
return true;
}
}
return false;
}
void Material::ClearKeywords() {
if (m_keywordSet.enabledKeywords.Empty()) {
return;
}
m_keywordSet.enabledKeywords.Clear();
MarkChanged(false);
}
Containers::String Material::GetKeyword(Core::uint32 index) const {
return index < m_keywordSet.enabledKeywords.Size()
? m_keywordSet.enabledKeywords[index]
: Containers::String();
}
void Material::SetFloat(const Containers::String& name, float value) {
if (!CanAssignPropertyType(name, MaterialPropertyType::Float)) {
return;
@@ -976,6 +1078,29 @@ void Material::SyncShaderSchemaProperties(bool removeUnknownProperties) {
}
}
void Material::SyncShaderSchemaKeywords(bool removeUnknownKeywords) {
if (m_shader.Get() == nullptr || !removeUnknownKeywords) {
return;
}
for (size_t keywordIndex = 0; keywordIndex < m_keywordSet.enabledKeywords.Size();) {
if (m_shader->DeclaresKeyword(m_keywordSet.enabledKeywords[keywordIndex])) {
++keywordIndex;
continue;
}
if (keywordIndex != m_keywordSet.enabledKeywords.Size() - 1) {
m_keywordSet.enabledKeywords[keywordIndex] = std::move(m_keywordSet.enabledKeywords.Back());
}
m_keywordSet.enabledKeywords.PopBack();
}
std::sort(
m_keywordSet.enabledKeywords.begin(),
m_keywordSet.enabledKeywords.end(),
CompareShaderKeywords);
}
void Material::MarkChanged(bool updateConstantBuffer) {
if (updateConstantBuffer) {
UpdateConstantBuffer();
@@ -992,6 +1117,7 @@ void Material::UpdateMemorySize() {
sizeof(MaterialRenderState) +
m_shaderPass.Length() +
m_tags.Size() * sizeof(MaterialTagEntry) +
m_keywordSet.enabledKeywords.Size() * sizeof(Containers::String) +
m_textureBindings.Size() * sizeof(MaterialTextureBinding) +
m_properties.Size() * sizeof(MaterialProperty) +
m_name.Length() +
@@ -1002,6 +1128,10 @@ void Material::UpdateMemorySize() {
m_memorySize += tag.value.Length();
}
for (const Containers::String& keyword : m_keywordSet.enabledKeywords) {
m_memorySize += keyword.Length();
}
for (const MaterialConstantFieldDesc& field : m_constantLayout) {
m_memorySize += field.name.Length();
}

View File

@@ -300,6 +300,13 @@ bool TryDecodeAssetRef(const Containers::String& value, AssetRef& outRef) {
return outRef.IsValid();
}
bool TryExtractDelimitedText(const std::string& text,
size_t valuePos,
char openChar,
char closeChar,
std::string& outValue,
size_t* nextPos);
bool TryExtractObject(const std::string& json, const char* key, std::string& outObject) {
size_t valuePos = 0;
if (!FindValueStart(json, key, valuePos) || valuePos >= json.size() || json[valuePos] != '{') {
@@ -345,6 +352,15 @@ bool TryExtractObject(const std::string& json, const char* key, std::string& out
return false;
}
bool TryExtractArray(const std::string& json, const char* key, std::string& outArray) {
size_t valuePos = 0;
if (!FindValueStart(json, key, valuePos) || valuePos >= json.size() || json[valuePos] != '[') {
return false;
}
return TryExtractDelimitedText(json, valuePos, '[', ']', outArray, nullptr);
}
std::string TrimCopy(const std::string& text) {
const size_t first = SkipWhitespace(text, 0);
if (first >= text.size()) {
@@ -359,6 +375,17 @@ std::string TrimCopy(const std::string& text) {
return text.substr(first, last - first);
}
Containers::String NormalizeMaterialKeywordToken(const Containers::String& keyword) {
const Containers::String normalized = keyword.Trim();
if (normalized.Empty() ||
normalized == Containers::String("_") ||
normalized == Containers::String("__")) {
return Containers::String();
}
return normalized;
}
bool IsJsonValueTerminator(char ch) {
return std::isspace(static_cast<unsigned char>(ch)) != 0 ||
ch == ',' ||
@@ -887,6 +914,58 @@ bool TryParseMaterialPropertiesObject(const std::string& objectText, Material* m
return false;
}
bool TryParseMaterialKeywordsArray(const std::string& arrayText, Material* material) {
if (material == nullptr || arrayText.empty() || arrayText.front() != '[' || arrayText.back() != ']') {
return false;
}
size_t pos = 1;
while (pos < arrayText.size()) {
pos = SkipWhitespace(arrayText, pos);
if (pos >= arrayText.size()) {
return false;
}
if (arrayText[pos] == ']') {
return true;
}
std::string rawValue;
JsonRawValueType rawType = JsonRawValueType::Invalid;
if (!TryExtractRawValue(arrayText, pos, rawValue, rawType, &pos) ||
rawType != JsonRawValueType::String) {
return false;
}
const Containers::String normalizedKeyword =
NormalizeMaterialKeywordToken(Containers::String(rawValue.c_str()));
if (!normalizedKeyword.Empty()) {
material->EnableKeyword(normalizedKeyword);
if (!material->IsKeywordEnabled(normalizedKeyword)) {
return false;
}
}
pos = SkipWhitespace(arrayText, pos);
if (pos >= arrayText.size()) {
return false;
}
if (arrayText[pos] == ',') {
++pos;
continue;
}
if (arrayText[pos] == ']') {
return true;
}
return false;
}
return false;
}
bool TryParseRenderQueueName(const Containers::String& queueName, Core::int32& outQueue) {
const Containers::String normalized = queueName.ToLower();
if (normalized == "background") {
@@ -1487,7 +1566,10 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
}
const std::string magic(fileHeader.magic, fileHeader.magic + 7);
if (magic != "XCMAT02") {
const bool isLegacySchema = magic == "XCMAT02" && fileHeader.schemaVersion == 2u;
const bool isCurrentSchema =
magic == "XCMAT03" && fileHeader.schemaVersion == kMaterialArtifactSchemaVersion;
if (!isLegacySchema && !isCurrentSchema) {
return LoadResult("Invalid material artifact magic: " + path);
}
@@ -1523,9 +1605,22 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
material->SetShaderPass(shaderPass);
}
MaterialArtifactHeader header;
if (!ReadMaterialArtifactValue(data, offset, header)) {
return LoadResult("Failed to parse material artifact body: " + path);
MaterialArtifactHeader header = {};
if (isLegacySchema) {
MaterialArtifactHeaderV2 legacyHeader = {};
if (!ReadMaterialArtifactValue(data, offset, legacyHeader)) {
return LoadResult("Failed to parse material artifact body: " + path);
}
header.renderQueue = legacyHeader.renderQueue;
header.renderState = legacyHeader.renderState;
header.tagCount = legacyHeader.tagCount;
header.propertyCount = legacyHeader.propertyCount;
header.textureBindingCount = legacyHeader.textureBindingCount;
} else {
if (!ReadMaterialArtifactValue(data, offset, header)) {
return LoadResult("Failed to parse material artifact body: " + path);
}
}
material->SetRenderQueue(header.renderQueue);
@@ -1542,6 +1637,21 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
material->SetTag(tagName, tagValue);
}
for (Core::uint32 keywordIndex = 0; keywordIndex < header.keywordCount; ++keywordIndex) {
Containers::String keyword;
if (!ReadMaterialArtifactString(data, offset, keyword)) {
return LoadResult("Failed to read material artifact keywords: " + path);
}
const Containers::String normalizedKeyword = NormalizeMaterialKeywordToken(keyword);
if (!normalizedKeyword.Empty()) {
material->EnableKeyword(normalizedKeyword);
if (!material->IsKeywordEnabled(normalizedKeyword)) {
return LoadResult("Material artifact references undeclared shader keyword: " + path);
}
}
}
for (Core::uint32 propertyIndex = 0; propertyIndex < header.propertyCount; ++propertyIndex) {
Containers::String propertyName;
MaterialPropertyArtifact propertyArtifact;
@@ -1692,6 +1802,14 @@ bool MaterialLoader::ParseMaterialData(const Containers::Array<Core::uint8>& dat
}
}
if (HasKey(jsonText, "keywords")) {
std::string keywordsArray;
if (!TryExtractArray(jsonText, "keywords", keywordsArray) ||
!TryParseMaterialKeywordsArray(keywordsArray, material)) {
return false;
}
}
if (HasKey(jsonText, "tags")) {
std::string tagObject;
if (!TryExtractObject(jsonText, "tags", tagObject) || !TryParseTagMap(tagObject, material)) {

View File

@@ -7,6 +7,27 @@ namespace {
const char* kLegacyShaderPassName = "Default";
bool IsShaderKeywordPlaceholder(const Containers::String& keyword) {
return keyword == Containers::String("_") ||
keyword == Containers::String("__");
}
bool PassDeclaresKeywordInternal(const ShaderPass& pass, const Containers::String& keyword) {
if (keyword.Empty() || IsShaderKeywordPlaceholder(keyword)) {
return false;
}
for (const ShaderKeywordDeclaration& declaration : pass.keywordDeclarations) {
for (const Containers::String& option : declaration.options) {
if (!IsShaderKeywordPlaceholder(option) && option == keyword) {
return true;
}
}
}
return false;
}
} // namespace
Shader::Shader() = default;
@@ -125,6 +146,13 @@ void Shader::AddPassResourceBinding(
pass.resources.PushBack(binding);
}
void Shader::AddPassKeywordDeclaration(
const Containers::String& passName,
const ShaderKeywordDeclaration& declaration) {
ShaderPass& pass = GetOrCreatePass(passName);
pass.keywordDeclarations.PushBack(declaration);
}
bool Shader::HasPass(const Containers::String& passName) const {
return FindPass(passName) != nullptr;
}
@@ -149,6 +177,23 @@ ShaderPass* Shader::FindPass(const Containers::String& passName) {
return nullptr;
}
bool Shader::PassDeclaresKeyword(
const Containers::String& passName,
const Containers::String& keyword) const {
const ShaderPass* pass = FindPass(passName);
return pass != nullptr && PassDeclaresKeywordInternal(*pass, keyword);
}
bool Shader::DeclaresKeyword(const Containers::String& keyword) const {
for (const ShaderPass& pass : m_passes) {
if (PassDeclaresKeywordInternal(pass, keyword)) {
return true;
}
}
return false;
}
const ShaderResourceBindingDesc* Shader::FindPassResourceBinding(
const Containers::String& passName,
const Containers::String& resourceName) const {

View File

@@ -611,6 +611,7 @@ struct AuthoringPassEntry {
Containers::String name;
std::vector<AuthoringTagEntry> tags;
Containers::Array<ShaderResourceBindingDesc> resources;
Containers::Array<ShaderKeywordDeclaration> keywordDeclarations;
Containers::String vertexEntryPoint;
Containers::String fragmentEntryPoint;
Containers::String sharedProgramSource;
@@ -924,6 +925,31 @@ Containers::String StripUnityStyleAuthoringPragmas(const Containers::String& sou
return strippedSource.c_str();
}
bool TryParseShaderKeywordDeclarationPragma(
const std::vector<std::string>& 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,
@@ -1703,6 +1729,12 @@ bool ParseUnityStyleSingleSourceShaderAuthoring(
(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;
}
@@ -1932,6 +1964,9 @@ LoadResult BuildShaderFromAuthoringDesc(
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) {
@@ -2182,6 +2217,11 @@ size_t CalculateShaderMemorySize(const Shader& shader) {
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();
@@ -2439,7 +2479,10 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
}
const std::string magic(fileHeader.magic, fileHeader.magic + 7);
if (magic != "XCSHD01" || fileHeader.schemaVersion != kShaderArtifactSchemaVersion) {
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);
}
@@ -2478,17 +2521,40 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
for (Core::uint32 passIndex = 0; passIndex < shaderHeader.passCount; ++passIndex) {
Containers::String passName;
ShaderPassArtifactHeader passHeader;
if (!ReadShaderArtifactString(data, offset, passName) ||
!ReadShaderArtifactValue(data, offset, passHeader)) {
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 < passHeader.tagCount; ++tagIndex) {
for (Core::uint32 tagIndex = 0; tagIndex < tagCount; ++tagIndex) {
Containers::String tagName;
Containers::String tagValue;
if (!ReadShaderArtifactString(data, offset, tagName) ||
@@ -2499,7 +2565,7 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
shader->SetPassTag(passName, tagName, tagValue);
}
for (Core::uint32 resourceIndex = 0; resourceIndex < passHeader.resourceCount; ++resourceIndex) {
for (Core::uint32 resourceIndex = 0; resourceIndex < resourceCount; ++resourceIndex) {
ShaderResourceBindingDesc binding = {};
ShaderResourceArtifact resourceArtifact;
if (!ReadShaderArtifactString(data, offset, binding.name) ||
@@ -2514,7 +2580,29 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
shader->AddPassResourceBinding(passName, binding);
}
for (Core::uint32 variantIndex = 0; variantIndex < passHeader.variantCount; ++variantIndex) {
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<ShaderKeywordDeclarationType>(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) ||