rendering: add keyword-aware shader variant selection

This commit is contained in:
2026-04-06 19:37:01 +08:00
parent a8b4da16a3
commit 261dd44fd5
26 changed files with 469 additions and 76 deletions

View File

@@ -13,7 +13,7 @@ namespace Resources {
constexpr Core::uint32 kTextureArtifactSchemaVersion = 1;
constexpr Core::uint32 kMaterialArtifactSchemaVersion = 3;
constexpr Core::uint32 kMeshArtifactSchemaVersion = 2;
constexpr Core::uint32 kShaderArtifactSchemaVersion = 2;
constexpr Core::uint32 kShaderArtifactSchemaVersion = 3;
constexpr Core::uint32 kUIDocumentArtifactSchemaVersion = 2;
struct TextureArtifactHeader {
@@ -73,7 +73,7 @@ struct MaterialPropertyArtifact {
};
struct ShaderArtifactFileHeader {
char magic[8] = { 'X', 'C', 'S', 'H', 'D', '0', '2', '\0' };
char magic[8] = { 'X', 'C', 'S', 'H', 'D', '0', '3', '\0' };
Core::uint32 schemaVersion = kShaderArtifactSchemaVersion;
};
@@ -105,10 +105,18 @@ struct ShaderResourceArtifact {
Core::uint32 binding = 0;
};
struct ShaderVariantArtifactHeaderV2 {
Core::uint32 stage = 0;
Core::uint32 language = 0;
Core::uint32 backend = 0;
Core::uint64 compiledBinarySize = 0;
};
struct ShaderVariantArtifactHeader {
Core::uint32 stage = 0;
Core::uint32 language = 0;
Core::uint32 backend = 0;
Core::uint32 keywordCount = 0;
Core::uint64 compiledBinarySize = 0;
};

View File

@@ -106,6 +106,7 @@ private:
Resources::MaterialRenderState renderState;
const Resources::Shader* shader = nullptr;
Containers::String passName;
Containers::String keywordSignature;
uint32_t renderTargetCount = 0;
uint32_t renderTargetFormat = 0;
uint32_t depthStencilFormat = 0;
@@ -114,6 +115,7 @@ private:
return renderState == other.renderState &&
shader == other.shader &&
passName == other.passName &&
keywordSignature == other.keywordSignature &&
renderTargetCount == other.renderTargetCount &&
renderTargetFormat == other.renderTargetFormat &&
depthStencilFormat == other.depthStencilFormat;
@@ -125,6 +127,7 @@ private:
size_t hash = MaterialRenderStateHash()(key.renderState);
hash ^= reinterpret_cast<size_t>(key.shader) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= std::hash<Containers::String>{}(key.passName) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= std::hash<Containers::String>{}(key.keywordSignature) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= std::hash<uint32_t>{}(key.renderTargetCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= std::hash<uint32_t>{}(key.renderTargetFormat) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= std::hash<uint32_t>{}(key.depthStencilFormat) + 0x9e3779b9u + (hash << 6) + (hash >> 2);

View File

@@ -192,11 +192,13 @@ private:
Resources::MaterialRenderState renderState;
const Resources::Shader* shader = nullptr;
Containers::String passName;
Containers::String keywordSignature;
bool operator==(const PipelineStateKey& other) const {
return renderState == other.renderState &&
shader == other.shader &&
passName == other.passName;
passName == other.passName &&
keywordSignature == other.keywordSignature;
}
};
@@ -209,6 +211,7 @@ private:
combine(reinterpret_cast<size_t>(key.shader));
combine(std::hash<Containers::String>{}(key.passName));
combine(std::hash<Containers::String>{}(key.keywordSignature));
return hash;
}
};

View File

@@ -88,6 +88,7 @@ struct ShaderStageVariant {
ShaderType stage = ShaderType::Fragment;
ShaderLanguage language = ShaderLanguage::GLSL;
ShaderBackend backend = ShaderBackend::Generic;
ShaderKeywordSet requiredKeywords;
Containers::String entryPoint;
Containers::String profile;
Containers::String sourceCode;
@@ -167,7 +168,8 @@ public:
const ShaderStageVariant* FindVariant(
const Containers::String& passName,
ShaderType stage,
ShaderBackend backend = ShaderBackend::Generic) const;
ShaderBackend backend = ShaderBackend::Generic,
const ShaderKeywordSet& enabledKeywords = ShaderKeywordSet()) const;
class IRHIShader* GetRHIResource() const { return m_rhiResource; }
void SetRHIResource(class IRHIShader* resource);

View File

@@ -4,6 +4,9 @@
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/Types.h>
#include <algorithm>
#include <cstring>
namespace XCEngine {
namespace Resources {
@@ -30,5 +33,84 @@ struct ShaderKeywordSet {
}
};
inline bool IsShaderKeywordPlaceholderToken(const Containers::String& keyword) {
return keyword == Containers::String("_") ||
keyword == Containers::String("__");
}
inline Containers::String NormalizeShaderKeywordToken(const Containers::String& keyword) {
const Containers::String normalized = keyword.Trim();
if (normalized.Empty() || IsShaderKeywordPlaceholderToken(normalized)) {
return Containers::String();
}
return normalized;
}
inline bool CompareShaderKeywordTokens(
const Containers::String& left,
const Containers::String& right) {
return std::strcmp(left.CStr(), right.CStr()) < 0;
}
inline void NormalizeShaderKeywordSetInPlace(ShaderKeywordSet& keywordSet) {
Containers::Array<Containers::String> normalizedKeywords;
normalizedKeywords.Reserve(keywordSet.enabledKeywords.Size());
for (const Containers::String& keyword : keywordSet.enabledKeywords) {
const Containers::String normalizedKeyword = NormalizeShaderKeywordToken(keyword);
if (normalizedKeyword.Empty()) {
continue;
}
bool alreadyExists = false;
for (const Containers::String& existingKeyword : normalizedKeywords) {
if (existingKeyword == normalizedKeyword) {
alreadyExists = true;
break;
}
}
if (!alreadyExists) {
normalizedKeywords.PushBack(normalizedKeyword);
}
}
std::sort(
normalizedKeywords.begin(),
normalizedKeywords.end(),
CompareShaderKeywordTokens);
keywordSet.enabledKeywords = normalizedKeywords;
}
inline bool ShaderKeywordSetContains(
const ShaderKeywordSet& keywordSet,
const Containers::String& keyword) {
const Containers::String normalizedKeyword = NormalizeShaderKeywordToken(keyword);
if (normalizedKeyword.Empty()) {
return false;
}
for (const Containers::String& enabledKeyword : keywordSet.enabledKeywords) {
if (enabledKeyword == normalizedKeyword) {
return true;
}
}
return false;
}
inline bool IsShaderKeywordSubset(
const ShaderKeywordSet& requiredKeywords,
const ShaderKeywordSet& enabledKeywords) {
for (const Containers::String& keyword : requiredKeywords.enabledKeywords) {
if (!ShaderKeywordSetContains(enabledKeywords, keyword)) {
return false;
}
}
return true;
}
} // namespace Resources
} // namespace XCEngine