rendering: formalize unity-style shader pass contracts

This commit is contained in:
2026-04-07 00:34:28 +08:00
parent 7216ad9138
commit 87533e08f6
13 changed files with 1133 additions and 164 deletions

View File

@@ -451,6 +451,7 @@ bool WriteMaterialArtifactFile(
header.renderQueue = material.GetRenderQueue();
header.renderState = material.GetRenderState();
header.tagCount = material.GetTagCount();
header.hasRenderStateOverride = material.HasRenderStateOverride() ? 1u : 0u;
header.keywordCount = material.GetKeywordCount();
const std::vector<MaterialProperty> properties = GatherMaterialProperties(material);
@@ -510,6 +511,7 @@ bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader)
WriteString(output, shader.GetName());
WriteString(output, NormalizeArtifactPathString(shader.GetPath()));
WriteString(output, shader.GetFallback());
ShaderArtifactHeader header;
header.propertyCount = static_cast<Core::uint32>(shader.GetProperties().Size());
@@ -530,11 +532,13 @@ bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader)
for (const ShaderPass& pass : shader.GetPasses()) {
WriteString(output, pass.name);
ShaderPassArtifactHeader passHeader;
ShaderPassArtifactHeaderV4 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());
passHeader.hasFixedFunctionState = pass.hasFixedFunctionState ? 1u : 0u;
passHeader.fixedFunctionState = pass.fixedFunctionState;
output.write(reinterpret_cast<const char*>(&passHeader), sizeof(passHeader));
for (const ShaderPassTagEntry& tag : pass.tags) {

View File

@@ -100,6 +100,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
RHI::RHIType backendType,
RHI::RHIPipelineLayout* pipelineLayout,
const Resources::Shader& shader,
const Resources::ShaderPass& shaderPass,
const Containers::String& passName,
const Resources::ShaderKeywordSet& keywordSet,
const Resources::Material* material,
@@ -117,13 +118,17 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
static_cast<uint32_t>(ResolveSurfaceDepthFormat(surface));
pipelineDesc.sampleCount = 1;
pipelineDesc.inputLayout = inputLayout;
ApplyMaterialRenderState(material, pipelineDesc);
ApplyResolvedRenderState(&shaderPass, material, pipelineDesc);
pipelineDesc.blendState.blendEnable = false;
pipelineDesc.blendState.colorWriteMask = pipelineDesc.renderTargetCount > 0 ? 0xF : 0;
pipelineDesc.depthStencilState.depthTestEnable = true;
pipelineDesc.depthStencilState.depthWriteEnable = true;
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::LessEqual);
if (!shaderPass.hasFixedFunctionState) {
pipelineDesc.blendState.blendEnable = false;
pipelineDesc.blendState.colorWriteMask = pipelineDesc.renderTargetCount > 0 ? 0xF : 0;
pipelineDesc.depthStencilState.depthTestEnable = true;
pipelineDesc.depthStencilState.depthWriteEnable = true;
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::LessEqual);
} else if (pipelineDesc.renderTargetCount == 0) {
pipelineDesc.blendState.colorWriteMask = 0;
}
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
if (const Resources::ShaderStageVariant* vertexVariant =
@@ -350,16 +355,7 @@ bool BuiltinDepthStylePassBase::TryBuildSupportedBindingPlan(
const Resources::ShaderPass& shaderPass,
BuiltinPassResourceBindingPlan& outPlan,
Containers::String* outError) const {
if (shaderPass.resources.Empty()) {
if (outError != nullptr) {
*outError =
Containers::String("Builtin depth-style pass requires explicit resource bindings on shader pass: ") +
shaderPass.name;
}
return false;
}
if (!TryBuildBuiltinPassResourceBindingPlan(shaderPass.resources, outPlan, outError)) {
if (!TryBuildBuiltinPassResourceBindingPlan(shaderPass, outPlan, outError)) {
return false;
}
@@ -470,8 +466,7 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
}
PipelineStateKey pipelineKey = {};
pipelineKey.renderState =
material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState();
pipelineKey.renderState = ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
pipelineKey.shader = resolvedShaderPass.shader;
pipelineKey.passName = resolvedShaderPass.passName;
pipelineKey.keywordSignature = ::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(keywordSet);
@@ -488,6 +483,7 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
context.backendType,
passLayout->pipelineLayout,
*resolvedShaderPass.shader,
*resolvedShaderPass.pass,
resolvedShaderPass.passName,
keywordSet,
material,

View File

@@ -132,19 +132,9 @@ bool BuiltinObjectIdPass::CreateResources(const RenderContext& context) {
return false;
}
const Containers::Array<Resources::ShaderResourceBindingDesc>& resourceBindings = objectIdPass->resources;
if (resourceBindings.Empty()) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
(Containers::String("BuiltinObjectIdPass requires explicit resource bindings on shader pass: ") +
objectIdPass->name).CStr());
DestroyResources();
return false;
}
BuiltinPassResourceBindingPlan bindingPlan = {};
Containers::String bindingPlanError;
if (!TryBuildBuiltinPassResourceBindingPlan(resourceBindings, bindingPlan, &bindingPlanError)) {
if (!TryBuildBuiltinPassResourceBindingPlan(*objectIdPass, bindingPlan, &bindingPlanError)) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
(Containers::String("BuiltinObjectIdPass failed to resolve pass resource bindings: ") + bindingPlanError).CStr());

View File

@@ -109,6 +109,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
RHI::RHIType backendType,
RHI::RHIPipelineLayout* pipelineLayout,
const Resources::Shader& shader,
const Resources::ShaderPass& shaderPass,
const Containers::String& passName,
const Resources::ShaderKeywordSet& keywordSet,
const Resources::Material* material) {
@@ -119,7 +120,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::D24_UNorm_S8_UInt);
pipelineDesc.sampleCount = 1;
ApplyMaterialRenderState(material, pipelineDesc);
ApplyResolvedRenderState(&shaderPass, material, pipelineDesc);
pipelineDesc.inputLayout = BuiltinForwardPipeline::BuildInputLayout();
@@ -216,20 +217,15 @@ BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreateP
return nullptr;
};
const Containers::Array<Resources::ShaderResourceBindingDesc>& resourceBindings = resolvedShaderPass.pass->resources;
if (resourceBindings.Empty()) {
return failLayout("BuiltinForwardPipeline requires explicit resource bindings on the resolved shader pass");
}
BuiltinPassResourceBindingPlan bindingPlan = {};
Containers::String bindingPlanError;
if (!TryBuildBuiltinPassResourceBindingPlan(resourceBindings, bindingPlan, &bindingPlanError)) {
if (!TryBuildBuiltinPassResourceBindingPlan(*resolvedShaderPass.pass, bindingPlan, &bindingPlanError)) {
const Containers::String contextualError =
Containers::String("BuiltinForwardPipeline failed to resolve pass resource bindings for shader='") +
resolvedShaderPass.shader->GetPath() +
"', pass='" + resolvedShaderPass.passName +
"': " + bindingPlanError +
". Bindings: " + DescribeShaderResourceBindings(resourceBindings);
". Bindings: " + DescribeShaderResourceBindings(resolvedShaderPass.pass->resources);
return failLayout(contextualError.CStr());
}
@@ -299,8 +295,7 @@ RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState(
}
PipelineStateKey pipelineKey = {};
pipelineKey.renderState =
material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState();
pipelineKey.renderState = ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
pipelineKey.shader = resolvedShaderPass.shader;
pipelineKey.passName = resolvedShaderPass.passName;
pipelineKey.keywordSignature = ::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(keywordSet);
@@ -315,6 +310,7 @@ RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState(
context.backendType,
passLayout->pipelineLayout,
*resolvedShaderPass.shader,
*resolvedShaderPass.pass,
resolvedShaderPass.passName,
keywordSet,
material);

View File

@@ -615,6 +615,10 @@ bool ReadTextFile(const Containers::String& path, Containers::String& outText) {
size_t CalculateShaderMemorySize(const Shader& shader);
bool TryTokenizeQuotedArguments(const std::string& line, std::vector<std::string>& outTokens);
MaterialRenderState BuildUnityDefaultFixedFunctionState();
void EnsureAuthoringFixedFunctionStateInitialized(
bool& hasFixedFunctionState,
MaterialRenderState& fixedFunctionState);
enum class ShaderAuthoringStyle {
NotShaderAuthoring = 0,
@@ -638,6 +642,8 @@ struct AuthoringBackendVariantEntry {
struct AuthoringPassEntry {
Containers::String name;
bool hasFixedFunctionState = false;
MaterialRenderState fixedFunctionState = {};
std::vector<AuthoringTagEntry> tags;
Containers::Array<ShaderResourceBindingDesc> resources;
Containers::Array<ShaderKeywordDeclaration> keywordDeclarations;
@@ -650,6 +656,8 @@ struct AuthoringPassEntry {
};
struct AuthoringSubShaderEntry {
bool hasFixedFunctionState = false;
MaterialRenderState fixedFunctionState = {};
std::vector<AuthoringTagEntry> tags;
Containers::String sharedProgramSource;
std::vector<AuthoringPassEntry> passes;
@@ -657,6 +665,7 @@ struct AuthoringSubShaderEntry {
struct AuthoringShaderDesc {
Containers::String name;
Containers::String fallback;
Containers::String sharedProgramSource;
Containers::Array<ShaderPropertyDesc> properties;
std::vector<AuthoringSubShaderEntry> subShaders;
@@ -845,6 +854,258 @@ bool ContainsSingleSourceAuthoringConstructs(const std::vector<std::string>& lin
return false;
}
MaterialRenderState BuildUnityDefaultFixedFunctionState() {
MaterialRenderState state = {};
state.blendEnable = false;
state.srcBlend = MaterialBlendFactor::One;
state.dstBlend = MaterialBlendFactor::Zero;
state.srcBlendAlpha = MaterialBlendFactor::One;
state.dstBlendAlpha = MaterialBlendFactor::Zero;
state.blendOp = MaterialBlendOp::Add;
state.blendOpAlpha = MaterialBlendOp::Add;
state.colorWriteMask = 0xF;
state.depthTestEnable = true;
state.depthWriteEnable = true;
state.depthFunc = MaterialComparisonFunc::LessEqual;
state.cullMode = MaterialCullMode::Back;
return state;
}
void EnsureAuthoringFixedFunctionStateInitialized(
bool& hasFixedFunctionState,
MaterialRenderState& fixedFunctionState) {
if (!hasFixedFunctionState) {
hasFixedFunctionState = true;
fixedFunctionState = BuildUnityDefaultFixedFunctionState();
}
}
bool TryParseUnityStyleBoolDirectiveToken(const std::string& token, bool& outValue) {
const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower();
if (normalized == "on") {
outValue = true;
return true;
}
if (normalized == "off") {
outValue = false;
return true;
}
return false;
}
bool TryParseUnityStyleCullMode(const std::string& token, MaterialCullMode& outMode) {
const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower();
if (normalized == "back") {
outMode = MaterialCullMode::Back;
return true;
}
if (normalized == "front") {
outMode = MaterialCullMode::Front;
return true;
}
if (normalized == "off") {
outMode = MaterialCullMode::None;
return true;
}
return false;
}
bool TryParseUnityStyleComparisonFunc(const std::string& token, MaterialComparisonFunc& outFunc) {
const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower();
if (normalized == "never") {
outFunc = MaterialComparisonFunc::Never;
return true;
}
if (normalized == "less") {
outFunc = MaterialComparisonFunc::Less;
return true;
}
if (normalized == "equal") {
outFunc = MaterialComparisonFunc::Equal;
return true;
}
if (normalized == "lequal" || normalized == "lessequal" || normalized == "less_equal") {
outFunc = MaterialComparisonFunc::LessEqual;
return true;
}
if (normalized == "greater") {
outFunc = MaterialComparisonFunc::Greater;
return true;
}
if (normalized == "notequal" || normalized == "not_equal") {
outFunc = MaterialComparisonFunc::NotEqual;
return true;
}
if (normalized == "gequal" || normalized == "greaterequal" || normalized == "greater_equal") {
outFunc = MaterialComparisonFunc::GreaterEqual;
return true;
}
if (normalized == "always") {
outFunc = MaterialComparisonFunc::Always;
return true;
}
return false;
}
bool TryParseUnityStyleBlendFactor(const std::string& token, MaterialBlendFactor& outFactor) {
const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower();
if (normalized == "zero") {
outFactor = MaterialBlendFactor::Zero;
return true;
}
if (normalized == "one") {
outFactor = MaterialBlendFactor::One;
return true;
}
if (normalized == "srccolor" || normalized == "src_color") {
outFactor = MaterialBlendFactor::SrcColor;
return true;
}
if (normalized == "oneminussrccolor" || normalized == "one_minus_src_color" || normalized == "invsrccolor") {
outFactor = MaterialBlendFactor::InvSrcColor;
return true;
}
if (normalized == "srcalpha" || normalized == "src_alpha") {
outFactor = MaterialBlendFactor::SrcAlpha;
return true;
}
if (normalized == "oneminussrcalpha" || normalized == "one_minus_src_alpha" || normalized == "invsrcalpha") {
outFactor = MaterialBlendFactor::InvSrcAlpha;
return true;
}
if (normalized == "dstalpha" || normalized == "dst_alpha") {
outFactor = MaterialBlendFactor::DstAlpha;
return true;
}
if (normalized == "oneminusdstalpha" || normalized == "one_minus_dst_alpha" || normalized == "invdstalpha") {
outFactor = MaterialBlendFactor::InvDstAlpha;
return true;
}
if (normalized == "dstcolor" || normalized == "dst_color") {
outFactor = MaterialBlendFactor::DstColor;
return true;
}
if (normalized == "oneminusdstcolor" || normalized == "one_minus_dst_color" || normalized == "invdstcolor") {
outFactor = MaterialBlendFactor::InvDstColor;
return true;
}
if (normalized == "srcalphasaturate" || normalized == "src_alpha_saturate" || normalized == "srcalphasat") {
outFactor = MaterialBlendFactor::SrcAlphaSat;
return true;
}
return false;
}
bool TryParseUnityStyleColorMask(const std::string& token, Core::uint8& outMask) {
const Containers::String normalized = Containers::String(token.c_str()).Trim().ToUpper();
if (normalized == "0") {
outMask = 0u;
return true;
}
Core::uint8 mask = 0u;
for (size_t index = 0; index < normalized.Length(); ++index) {
switch (normalized[index]) {
case 'R':
mask |= 0x1u;
break;
case 'G':
mask |= 0x2u;
break;
case 'B':
mask |= 0x4u;
break;
case 'A':
mask |= 0x8u;
break;
default:
return false;
}
}
outMask = mask;
return true;
}
bool TryParseUnityStyleBlendDirective(
const std::vector<std::string>& tokens,
MaterialRenderState& outState) {
std::vector<std::string> normalizedTokens;
normalizedTokens.reserve(tokens.size());
for (const std::string& token : tokens) {
if (token == ",") {
continue;
}
std::string normalizedToken = token;
while (!normalizedToken.empty() && normalizedToken.back() == ',') {
normalizedToken.pop_back();
}
if (!normalizedToken.empty()) {
normalizedTokens.push_back(std::move(normalizedToken));
}
}
if (normalizedTokens.size() != 2u &&
normalizedTokens.size() != 3u &&
normalizedTokens.size() != 5u) {
return false;
}
if (normalizedTokens.size() == 2u) {
bool enabled = false;
if (!TryParseUnityStyleBoolDirectiveToken(normalizedTokens[1], enabled)) {
return false;
}
outState.blendEnable = enabled;
if (!enabled) {
outState.srcBlend = MaterialBlendFactor::One;
outState.dstBlend = MaterialBlendFactor::Zero;
outState.srcBlendAlpha = MaterialBlendFactor::One;
outState.dstBlendAlpha = MaterialBlendFactor::Zero;
}
return true;
}
MaterialBlendFactor srcBlend = MaterialBlendFactor::One;
MaterialBlendFactor dstBlend = MaterialBlendFactor::Zero;
if (!TryParseUnityStyleBlendFactor(normalizedTokens[1], srcBlend) ||
!TryParseUnityStyleBlendFactor(normalizedTokens[2], dstBlend)) {
return false;
}
outState.blendEnable = true;
outState.srcBlend = srcBlend;
outState.dstBlend = dstBlend;
if (normalizedTokens.size() == 5u) {
if (!TryParseUnityStyleBlendFactor(normalizedTokens[3], outState.srcBlendAlpha) ||
!TryParseUnityStyleBlendFactor(normalizedTokens[4], outState.dstBlendAlpha)) {
return false;
}
} else {
outState.srcBlendAlpha = srcBlend;
outState.dstBlendAlpha = dstBlend;
}
return true;
}
void SetOrReplaceAuthoringTag(
std::vector<AuthoringTagEntry>& tags,
const Containers::String& name,
const Containers::String& value) {
for (AuthoringTagEntry& tag : tags) {
if (tag.name == name) {
tag.value = value;
return;
}
}
tags.push_back({ name, value });
}
ShaderAuthoringStyle DetectShaderAuthoringStyle(const std::string& sourceText) {
std::vector<std::string> lines;
SplitShaderAuthoringLines(sourceText, lines);
@@ -1684,6 +1945,15 @@ bool ParseLegacyBackendSplitShaderAuthoring(
continue;
}
if (currentBlock() == BlockKind::Shader && StartsWithKeyword(line, "Fallback")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) {
return fail("Fallback directive is missing a value", humanLine);
}
outDesc.fallback = tokens[1].c_str();
continue;
}
if (StartsWithKeyword(line, "SubShader")) {
pendingBlock = BlockKind::SubShader;
continue;
@@ -1931,6 +2201,11 @@ bool ParseUnityStyleSingleSourceShaderAuthoring(
}
currentSubShader->passes.emplace_back();
currentPass = &currentSubShader->passes.back();
currentPass->hasFixedFunctionState = true;
currentPass->fixedFunctionState = BuildUnityDefaultFixedFunctionState();
if (currentSubShader->hasFixedFunctionState) {
currentPass->fixedFunctionState = currentSubShader->fixedFunctionState;
}
blockStack.push_back(BlockKind::Pass);
break;
case BlockKind::None:
@@ -1972,6 +2247,15 @@ bool ParseUnityStyleSingleSourceShaderAuthoring(
continue;
}
if (currentBlock() == BlockKind::Shader && StartsWithKeyword(line, "Fallback")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) {
return fail("Fallback directive is missing a value", humanLine);
}
outDesc.fallback = tokens[1].c_str();
continue;
}
if (StartsWithKeyword(line, "SubShader")) {
pendingBlock = BlockKind::SubShader;
continue;
@@ -2039,9 +2323,106 @@ bool ParseUnityStyleSingleSourceShaderAuthoring(
}
if (currentBlock() == BlockKind::SubShader && StartsWithKeyword(line, "LOD")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
return fail("LOD directive must provide a numeric value", humanLine);
}
try {
const Core::uint32 lodValue = static_cast<Core::uint32>(std::stoul(tokens[1]));
SetOrReplaceAuthoringTag(currentSubShader->tags, "LOD", std::to_string(lodValue).c_str());
} catch (...) {
return fail("LOD directive must provide a numeric value", humanLine);
}
continue;
}
if (currentBlock() == BlockKind::SubShader && currentSubShader != nullptr) {
if (StartsWithKeyword(line, "Cull")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
return fail("Cull directive must use Front, Back, or Off", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentSubShader->hasFixedFunctionState,
currentSubShader->fixedFunctionState);
if (!TryParseUnityStyleCullMode(tokens[1], currentSubShader->fixedFunctionState.cullMode)) {
return fail("Cull directive must use Front, Back, or Off", humanLine);
}
continue;
}
if (StartsWithKeyword(line, "ZWrite")) {
std::vector<std::string> tokens;
bool enabled = false;
if (!TryTokenizeQuotedArguments(line, tokens) ||
tokens.size() != 2u ||
!TryParseUnityStyleBoolDirectiveToken(tokens[1], enabled)) {
return fail("ZWrite directive must use On or Off", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentSubShader->hasFixedFunctionState,
currentSubShader->fixedFunctionState);
currentSubShader->fixedFunctionState.depthWriteEnable = enabled;
continue;
}
if (StartsWithKeyword(line, "ZTest")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
return fail("ZTest directive uses an unsupported compare function", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentSubShader->hasFixedFunctionState,
currentSubShader->fixedFunctionState);
if (!TryParseUnityStyleComparisonFunc(tokens[1], currentSubShader->fixedFunctionState.depthFunc)) {
return fail("ZTest directive uses an unsupported compare function", humanLine);
}
currentSubShader->fixedFunctionState.depthTestEnable = true;
continue;
}
if (StartsWithKeyword(line, "Blend")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens)) {
return fail("Blend directive could not be tokenized", humanLine);
}
for (size_t tokenIndex = 1; tokenIndex < tokens.size(); ++tokenIndex) {
if (!tokens[tokenIndex].empty() && tokens[tokenIndex].back() == ',') {
tokens[tokenIndex].pop_back();
}
}
EnsureAuthoringFixedFunctionStateInitialized(
currentSubShader->hasFixedFunctionState,
currentSubShader->fixedFunctionState);
if (!TryParseUnityStyleBlendDirective(tokens, currentSubShader->fixedFunctionState)) {
return fail("Blend directive uses an unsupported factor combination", humanLine);
}
continue;
}
if (StartsWithKeyword(line, "ColorMask")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) ||
(tokens.size() != 2u && tokens.size() != 3u)) {
return fail("ColorMask directive uses an unsupported channel mask", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentSubShader->hasFixedFunctionState,
currentSubShader->fixedFunctionState);
if (!TryParseUnityStyleColorMask(tokens[1], currentSubShader->fixedFunctionState.colorWriteMask)) {
return fail("ColorMask directive uses an unsupported channel mask", humanLine);
}
continue;
}
}
if (currentBlock() == BlockKind::Pass && currentPass != nullptr) {
if (StartsWithKeyword(line, "Name")) {
std::vector<std::string> tokens;
@@ -2052,6 +2433,90 @@ bool ParseUnityStyleSingleSourceShaderAuthoring(
continue;
}
if (StartsWithKeyword(line, "Cull")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
return fail("Cull directive must use Front, Back, or Off", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentPass->hasFixedFunctionState,
currentPass->fixedFunctionState);
if (!TryParseUnityStyleCullMode(tokens[1], currentPass->fixedFunctionState.cullMode)) {
return fail("Cull directive must use Front, Back, or Off", humanLine);
}
continue;
}
if (StartsWithKeyword(line, "ZWrite")) {
std::vector<std::string> tokens;
bool enabled = false;
if (!TryTokenizeQuotedArguments(line, tokens) ||
tokens.size() != 2u ||
!TryParseUnityStyleBoolDirectiveToken(tokens[1], enabled)) {
return fail("ZWrite directive must use On or Off", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentPass->hasFixedFunctionState,
currentPass->fixedFunctionState);
currentPass->fixedFunctionState.depthWriteEnable = enabled;
continue;
}
if (StartsWithKeyword(line, "ZTest")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
return fail("ZTest directive uses an unsupported compare function", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentPass->hasFixedFunctionState,
currentPass->fixedFunctionState);
if (!TryParseUnityStyleComparisonFunc(tokens[1], currentPass->fixedFunctionState.depthFunc)) {
return fail("ZTest directive uses an unsupported compare function", humanLine);
}
currentPass->fixedFunctionState.depthTestEnable = true;
continue;
}
if (StartsWithKeyword(line, "Blend")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens)) {
return fail("Blend directive could not be tokenized", humanLine);
}
for (size_t tokenIndex = 1; tokenIndex < tokens.size(); ++tokenIndex) {
if (!tokens[tokenIndex].empty() && tokens[tokenIndex].back() == ',') {
tokens[tokenIndex].pop_back();
}
}
EnsureAuthoringFixedFunctionStateInitialized(
currentPass->hasFixedFunctionState,
currentPass->fixedFunctionState);
if (!TryParseUnityStyleBlendDirective(tokens, currentPass->fixedFunctionState)) {
return fail("Blend directive uses an unsupported factor combination", humanLine);
}
continue;
}
if (StartsWithKeyword(line, "ColorMask")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) ||
(tokens.size() != 2u && tokens.size() != 3u)) {
return fail("ColorMask directive uses an unsupported channel mask", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentPass->hasFixedFunctionState,
currentPass->fixedFunctionState);
if (!TryParseUnityStyleColorMask(tokens[1], currentPass->fixedFunctionState.colorWriteMask)) {
return fail("ColorMask directive uses an unsupported channel mask", humanLine);
}
continue;
}
if (line == "HLSLPROGRAM" || line == "CGPROGRAM") {
inProgramBlock = true;
if (!consumeExtractedBlock(
@@ -2114,6 +2579,7 @@ LoadResult BuildShaderFromAuthoringDesc(
params.guid = ResourceGUID::Generate(path);
params.name = authoringDesc.name;
shader->Initialize(params);
shader->SetFallback(authoringDesc.fallback);
for (const ShaderPropertyDesc& property : authoringDesc.properties) {
shader->AddProperty(property);
@@ -2123,6 +2589,8 @@ LoadResult BuildShaderFromAuthoringDesc(
for (const AuthoringPassEntry& pass : subShader.passes) {
ShaderPass shaderPass = {};
shaderPass.name = pass.name;
shaderPass.hasFixedFunctionState = pass.hasFixedFunctionState;
shaderPass.fixedFunctionState = pass.fixedFunctionState;
shader->AddPass(shaderPass);
for (const AuthoringTagEntry& subShaderTag : subShader.tags) {
@@ -2393,7 +2861,8 @@ bool TryParseUnsignedValue(const std::string& json, const char* key, Core::uint3
}
size_t CalculateShaderMemorySize(const Shader& shader) {
size_t memorySize = sizeof(Shader) + shader.GetName().Length() + shader.GetPath().Length();
size_t memorySize =
sizeof(Shader) + shader.GetName().Length() + shader.GetPath().Length() + shader.GetFallback().Length();
for (const ShaderPropertyDesc& property : shader.GetProperties()) {
memorySize += property.name.Length();
memorySize += property.displayName.Length();
@@ -2518,6 +2987,11 @@ LoadResult LoadShaderManifest(const Containers::String& path, const std::string&
shader->Initialize(params);
Containers::String fallback;
if (TryParseStringValue(jsonText, "fallback", fallback)) {
shader->SetFallback(fallback);
}
std::string propertiesArray;
if (TryExtractArray(jsonText, "properties", propertiesArray)) {
std::vector<std::string> propertyObjects;
@@ -2683,9 +3157,10 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
const std::string magic(fileHeader.magic, fileHeader.magic + 7);
const bool isLegacySchema = magic == "XCSHD01" && fileHeader.schemaVersion == 1u;
const bool isSchemaV2 = magic == "XCSHD02" && fileHeader.schemaVersion == 2u;
const bool isSchemaV3 = magic == "XCSHD03" && fileHeader.schemaVersion == 3u;
const bool isCurrentSchema =
magic == "XCSHD03" && fileHeader.schemaVersion == kShaderArtifactSchemaVersion;
if (!isLegacySchema && !isSchemaV2 && !isCurrentSchema) {
magic == "XCSHD04" && fileHeader.schemaVersion == kShaderArtifactSchemaVersion;
if (!isLegacySchema && !isSchemaV2 && !isSchemaV3 && !isCurrentSchema) {
return LoadResult("Invalid shader artifact header: " + path);
}
@@ -2693,14 +3168,20 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
Containers::String shaderName;
Containers::String shaderSourcePath;
Containers::String shaderFallback;
if (!ReadShaderArtifactString(data, offset, shaderName) ||
!ReadShaderArtifactString(data, offset, shaderSourcePath)) {
return LoadResult("Failed to parse shader artifact strings: " + path);
}
if (isCurrentSchema &&
!ReadShaderArtifactString(data, offset, shaderFallback)) {
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);
shader->SetFallback(shaderFallback);
ShaderArtifactHeader shaderHeader;
if (!ReadShaderArtifactValue(data, offset, shaderHeader)) {
@@ -2728,6 +3209,8 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
Core::uint32 resourceCount = 0;
Core::uint32 keywordDeclarationCount = 0;
Core::uint32 variantCount = 0;
Core::uint32 hasFixedFunctionState = 0;
MaterialRenderState fixedFunctionState = {};
if (!ReadShaderArtifactString(data, offset, passName)) {
return LoadResult("Failed to read shader artifact passes: " + path);
}
@@ -2741,7 +3224,7 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
tagCount = passHeader.tagCount;
resourceCount = passHeader.resourceCount;
variantCount = passHeader.variantCount;
} else {
} else if (isSchemaV2 || isSchemaV3) {
ShaderPassArtifactHeader passHeader = {};
if (!ReadShaderArtifactValue(data, offset, passHeader)) {
return LoadResult("Failed to read shader artifact passes: " + path);
@@ -2751,10 +3234,24 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
resourceCount = passHeader.resourceCount;
keywordDeclarationCount = passHeader.keywordDeclarationCount;
variantCount = passHeader.variantCount;
} else {
ShaderPassArtifactHeaderV4 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;
hasFixedFunctionState = passHeader.hasFixedFunctionState;
fixedFunctionState = passHeader.fixedFunctionState;
}
ShaderPass pass = {};
pass.name = passName;
pass.hasFixedFunctionState = hasFixedFunctionState != 0u;
pass.fixedFunctionState = fixedFunctionState;
shader->AddPass(pass);
for (Core::uint32 tagIndex = 0; tagIndex < tagCount; ++tagIndex) {