#include "ShaderAuthoringInternal.h" namespace XCEngine { namespace Resources { namespace Internal { namespace { std::vector NormalizeDirectiveTokens( const std::vector& tokens, size_t startIndex) { std::vector normalizedTokens; normalizedTokens.reserve(tokens.size() > startIndex ? tokens.size() - startIndex : 0u); for (size_t tokenIndex = startIndex; tokenIndex < tokens.size(); ++tokenIndex) { if (tokens[tokenIndex] == ",") { continue; } std::string normalizedToken = tokens[tokenIndex]; while (!normalizedToken.empty() && normalizedToken.back() == ',') { normalizedToken.pop_back(); } if (!normalizedToken.empty()) { normalizedTokens.push_back(std::move(normalizedToken)); } } return normalizedTokens; } bool TryParseUInt8Value(const std::string& token, Core::uint8& outValue) { try { const unsigned long value = std::stoul(token, nullptr, 0); if (value > 0xFFul) { return false; } outValue = static_cast(value); return true; } catch (...) { return false; } } } // namespace MaterialRenderState BuildDefaultAuthoringFixedFunctionState() { 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 = BuildDefaultAuthoringFixedFunctionState(); } } bool TryParseAuthoringBoolDirectiveToken(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 TryParseAuthoringCullMode(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 TryParseAuthoringComparisonFunc(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 TryParseAuthoringBlendFactor(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 TryParseAuthoringBlendOp(const std::string& token, MaterialBlendOp& outOp) { const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower(); if (normalized == "add") { outOp = MaterialBlendOp::Add; return true; } if (normalized == "sub" || normalized == "subtract") { outOp = MaterialBlendOp::Subtract; return true; } if (normalized == "revsub" || normalized == "reverse_subtract" || normalized == "reversesubtract") { outOp = MaterialBlendOp::ReverseSubtract; return true; } if (normalized == "min") { outOp = MaterialBlendOp::Min; return true; } if (normalized == "max") { outOp = MaterialBlendOp::Max; return true; } return false; } bool TryParseAuthoringStencilOp(const std::string& token, MaterialStencilOp& outOp) { const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower(); if (normalized == "keep") { outOp = MaterialStencilOp::Keep; return true; } if (normalized == "zero") { outOp = MaterialStencilOp::Zero; return true; } if (normalized == "replace") { outOp = MaterialStencilOp::Replace; return true; } if (normalized == "incrsat" || normalized == "incr_sat") { outOp = MaterialStencilOp::IncrSat; return true; } if (normalized == "decrsat" || normalized == "decr_sat") { outOp = MaterialStencilOp::DecrSat; return true; } if (normalized == "invert") { outOp = MaterialStencilOp::Invert; return true; } if (normalized == "incrwrap" || normalized == "incr_wrap" || normalized == "incr") { outOp = MaterialStencilOp::IncrWrap; return true; } if (normalized == "decrwrap" || normalized == "decr_wrap" || normalized == "decr") { outOp = MaterialStencilOp::DecrWrap; return true; } return false; } bool TryParseAuthoringColorMask(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 TryParseAuthoringBlendDirective( const std::vector& tokens, MaterialRenderState& outState) { const std::vector normalizedTokens = NormalizeDirectiveTokens(tokens, 0u); if (normalizedTokens.size() != 2u && normalizedTokens.size() != 3u && normalizedTokens.size() != 5u) { return false; } if (normalizedTokens.size() == 2u) { bool enabled = false; if (!TryParseAuthoringBoolDirectiveToken(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 (!TryParseAuthoringBlendFactor(normalizedTokens[1], srcBlend) || !TryParseAuthoringBlendFactor(normalizedTokens[2], dstBlend)) { return false; } outState.blendEnable = true; outState.srcBlend = srcBlend; outState.dstBlend = dstBlend; if (normalizedTokens.size() == 5u) { if (!TryParseAuthoringBlendFactor(normalizedTokens[3], outState.srcBlendAlpha) || !TryParseAuthoringBlendFactor(normalizedTokens[4], outState.dstBlendAlpha)) { return false; } } else { outState.srcBlendAlpha = srcBlend; outState.dstBlendAlpha = dstBlend; } return true; } bool TryParseAuthoringBlendOpDirective( const std::vector& tokens, MaterialRenderState& outState) { const std::vector normalizedTokens = NormalizeDirectiveTokens(tokens, 1u); if (normalizedTokens.size() != 1u && normalizedTokens.size() != 2u) { return false; } if (!TryParseAuthoringBlendOp(normalizedTokens[0], outState.blendOp)) { return false; } if (normalizedTokens.size() == 2u) { return TryParseAuthoringBlendOp(normalizedTokens[1], outState.blendOpAlpha); } outState.blendOpAlpha = outState.blendOp; return true; } bool TryParseAuthoringOffsetDirective( const std::vector& tokens, MaterialRenderState& outState) { const std::vector normalizedTokens = NormalizeDirectiveTokens(tokens, 1u); if (normalizedTokens.size() != 2u) { return false; } try { outState.depthBiasFactor = std::stof(normalizedTokens[0]); outState.depthBiasUnits = static_cast(std::stol(normalizedTokens[1])); return true; } catch (...) { return false; } } bool TryParseAuthoringStencilDirective( const std::vector& tokens, MaterialStencilState& outState) { if (tokens.size() != 2u && tokens.size() != 3u) { return false; } const Containers::String directive = Containers::String(tokens[0].c_str()).Trim().ToLower(); if (directive == "ref") { Core::uint8 reference = 0; if (!TryParseUInt8Value(tokens[1], reference)) { return false; } outState.reference = reference; outState.enabled = true; return true; } if (directive == "readmask") { Core::uint8 readMask = 0; if (!TryParseUInt8Value(tokens[1], readMask)) { return false; } outState.readMask = readMask; outState.enabled = true; return true; } if (directive == "writemask") { Core::uint8 writeMask = 0; if (!TryParseUInt8Value(tokens[1], writeMask)) { return false; } outState.writeMask = writeMask; outState.enabled = true; return true; } auto applyComparison = [&](MaterialStencilFaceState& faceState) -> bool { return TryParseAuthoringComparisonFunc(tokens[1], faceState.func); }; auto applyOperation = [&](MaterialStencilFaceState& faceState, MaterialStencilOp MaterialStencilFaceState::* member) -> bool { MaterialStencilOp op = MaterialStencilOp::Keep; if (!TryParseAuthoringStencilOp(tokens[1], op)) { return false; } faceState.*member = op; return true; }; if (directive == "comp") { outState.enabled = true; return applyComparison(outState.front) && applyComparison(outState.back); } if (directive == "pass") { outState.enabled = true; return applyOperation(outState.front, &MaterialStencilFaceState::passOp) && applyOperation(outState.back, &MaterialStencilFaceState::passOp); } if (directive == "fail") { outState.enabled = true; return applyOperation(outState.front, &MaterialStencilFaceState::failOp) && applyOperation(outState.back, &MaterialStencilFaceState::failOp); } if (directive == "zfail") { outState.enabled = true; return applyOperation(outState.front, &MaterialStencilFaceState::depthFailOp) && applyOperation(outState.back, &MaterialStencilFaceState::depthFailOp); } if (directive == "compfront") { outState.enabled = true; return applyComparison(outState.front); } if (directive == "passfront") { outState.enabled = true; return applyOperation(outState.front, &MaterialStencilFaceState::passOp); } if (directive == "failfront") { outState.enabled = true; return applyOperation(outState.front, &MaterialStencilFaceState::failOp); } if (directive == "zfailfront") { outState.enabled = true; return applyOperation(outState.front, &MaterialStencilFaceState::depthFailOp); } if (directive == "compback") { outState.enabled = true; return applyComparison(outState.back); } if (directive == "passback") { outState.enabled = true; return applyOperation(outState.back, &MaterialStencilFaceState::passOp); } if (directive == "failback") { outState.enabled = true; return applyOperation(outState.back, &MaterialStencilFaceState::failOp); } if (directive == "zfailback") { outState.enabled = true; return applyOperation(outState.back, &MaterialStencilFaceState::depthFailOp); } return false; } bool TryParseAuthoringUsePassReference( const Containers::String& reference, Containers::String& outShaderName, Containers::String& outPassName) { outShaderName.Clear(); outPassName.Clear(); const std::string referenceText = reference.Trim().CStr(); const size_t slashPos = referenceText.rfind('/'); if (slashPos == std::string::npos || slashPos == 0u || slashPos + 1u >= referenceText.size()) { return false; } outShaderName = Containers::String(referenceText.substr(0, slashPos).c_str()); outPassName = Containers::String(referenceText.substr(slashPos + 1u).c_str()); return !outShaderName.Empty() && !outPassName.Empty(); } void SetOrReplaceAuthoringTag( std::vector& tags, const Containers::String& name, const Containers::String& value) { for (ShaderTagIR& tag : tags) { if (tag.name == name) { tag.value = value; return; } } tags.push_back({ name, value }); } bool TryParseShaderKeywordDeclarationPragma( const std::vector& pragmaTokens, ShaderKeywordDeclaration& outDeclaration) { outDeclaration = {}; if (pragmaTokens.size() < 3u) { return false; } if (pragmaTokens[1] == "multi_compile") { outDeclaration.type = ShaderKeywordDeclarationType::MultiCompile; } else if (pragmaTokens[1] == "multi_compile_local") { outDeclaration.type = ShaderKeywordDeclarationType::MultiCompileLocal; } 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(); } } // namespace Internal } // namespace Resources } // namespace XCEngine