rendering: formalize shader keyword metadata contract
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) ||
|
||||
|
||||
Reference in New Issue
Block a user