rendering: formalize unity-style shader pass contracts
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = ¤tSubShader->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) {
|
||||
|
||||
Reference in New Issue
Block a user