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

@@ -11,9 +11,9 @@ namespace XCEngine {
namespace Resources {
constexpr Core::uint32 kTextureArtifactSchemaVersion = 1;
constexpr Core::uint32 kMaterialArtifactSchemaVersion = 3;
constexpr Core::uint32 kMaterialArtifactSchemaVersion = 4;
constexpr Core::uint32 kMeshArtifactSchemaVersion = 2;
constexpr Core::uint32 kShaderArtifactSchemaVersion = 3;
constexpr Core::uint32 kShaderArtifactSchemaVersion = 4;
constexpr Core::uint32 kUIDocumentArtifactSchemaVersion = 2;
struct TextureArtifactHeader {
@@ -46,7 +46,7 @@ struct MeshArtifactHeader {
};
struct MaterialArtifactFileHeader {
char magic[8] = { 'X', 'C', 'M', 'A', 'T', '0', '3', '\0' };
char magic[8] = { 'X', 'C', 'M', 'A', 'T', '0', '4', '\0' };
Core::uint32 schemaVersion = kMaterialArtifactSchemaVersion;
};
@@ -58,10 +58,20 @@ struct MaterialArtifactHeaderV2 {
Core::uint32 textureBindingCount = 0;
};
struct MaterialArtifactHeaderV3 {
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;
};
struct MaterialArtifactHeader {
Core::int32 renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
MaterialRenderState renderState = {};
Core::uint32 tagCount = 0;
Core::uint32 hasRenderStateOverride = 0;
Core::uint32 keywordCount = 0;
Core::uint32 propertyCount = 0;
Core::uint32 textureBindingCount = 0;
@@ -73,7 +83,7 @@ struct MaterialPropertyArtifact {
};
struct ShaderArtifactFileHeader {
char magic[8] = { 'X', 'C', 'S', 'H', 'D', '0', '3', '\0' };
char magic[8] = { 'X', 'C', 'S', 'H', 'D', '0', '4', '\0' };
Core::uint32 schemaVersion = kShaderArtifactSchemaVersion;
};
@@ -95,6 +105,15 @@ struct ShaderPassArtifactHeader {
Core::uint32 variantCount = 0;
};
struct ShaderPassArtifactHeaderV4 {
Core::uint32 tagCount = 0;
Core::uint32 resourceCount = 0;
Core::uint32 keywordDeclarationCount = 0;
Core::uint32 variantCount = 0;
Core::uint32 hasFixedFunctionState = 0;
MaterialRenderState fixedFunctionState = {};
};
struct ShaderPropertyArtifact {
Core::uint32 propertyType = 0;
};

View File

@@ -115,6 +115,155 @@ inline bool TryBuildBuiltinPassResourceBindingPlan(
return true;
}
inline void AppendBuiltinPassResourceBinding(
Containers::Array<Resources::ShaderResourceBindingDesc>& bindings,
const char* name,
Resources::ShaderResourceType type,
Core::uint32 set,
Core::uint32 binding,
const char* semantic) {
Resources::ShaderResourceBindingDesc desc = {};
desc.name = name;
desc.type = type;
desc.set = set;
desc.binding = binding;
desc.semantic = semantic;
bindings.PushBack(desc);
}
inline bool TryBuildImplicitBuiltinPassResourceBindings(
const Resources::ShaderPass& shaderPass,
Containers::Array<Resources::ShaderResourceBindingDesc>& outBindings) {
outBindings.Clear();
if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::ForwardLit)) {
AppendBuiltinPassResourceBinding(
outBindings,
"PerObjectConstants",
Resources::ShaderResourceType::ConstantBuffer,
0u,
0u,
"PerObject");
AppendBuiltinPassResourceBinding(
outBindings,
"LightingConstants",
Resources::ShaderResourceType::ConstantBuffer,
1u,
0u,
"Lighting");
AppendBuiltinPassResourceBinding(
outBindings,
"MaterialConstants",
Resources::ShaderResourceType::ConstantBuffer,
2u,
0u,
"Material");
AppendBuiltinPassResourceBinding(
outBindings,
"ShadowReceiverConstants",
Resources::ShaderResourceType::ConstantBuffer,
3u,
0u,
"ShadowReceiver");
AppendBuiltinPassResourceBinding(
outBindings,
"BaseColorTexture",
Resources::ShaderResourceType::Texture2D,
4u,
0u,
"BaseColorTexture");
AppendBuiltinPassResourceBinding(
outBindings,
"LinearClampSampler",
Resources::ShaderResourceType::Sampler,
5u,
0u,
"LinearClampSampler");
AppendBuiltinPassResourceBinding(
outBindings,
"ShadowMapTexture",
Resources::ShaderResourceType::Texture2D,
6u,
0u,
"ShadowMapTexture");
AppendBuiltinPassResourceBinding(
outBindings,
"ShadowMapSampler",
Resources::ShaderResourceType::Sampler,
7u,
0u,
"ShadowMapSampler");
return true;
}
if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::Unlit) ||
ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::DepthOnly) ||
ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::ShadowCaster)) {
AppendBuiltinPassResourceBinding(
outBindings,
"PerObjectConstants",
Resources::ShaderResourceType::ConstantBuffer,
0u,
0u,
"PerObject");
AppendBuiltinPassResourceBinding(
outBindings,
"MaterialConstants",
Resources::ShaderResourceType::ConstantBuffer,
1u,
0u,
"Material");
AppendBuiltinPassResourceBinding(
outBindings,
"BaseColorTexture",
Resources::ShaderResourceType::Texture2D,
2u,
0u,
"BaseColorTexture");
AppendBuiltinPassResourceBinding(
outBindings,
"LinearClampSampler",
Resources::ShaderResourceType::Sampler,
3u,
0u,
"LinearClampSampler");
return true;
}
if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::ObjectId)) {
AppendBuiltinPassResourceBinding(
outBindings,
"PerObjectConstants",
Resources::ShaderResourceType::ConstantBuffer,
0u,
0u,
"PerObject");
return true;
}
return false;
}
inline bool TryBuildBuiltinPassResourceBindingPlan(
const Resources::ShaderPass& shaderPass,
BuiltinPassResourceBindingPlan& outPlan,
Containers::String* outError = nullptr) {
if (!shaderPass.resources.Empty()) {
return TryBuildBuiltinPassResourceBindingPlan(shaderPass.resources, outPlan, outError);
}
Containers::Array<Resources::ShaderResourceBindingDesc> implicitBindings;
if (!TryBuildImplicitBuiltinPassResourceBindings(shaderPass, implicitBindings)) {
if (outError != nullptr) {
*outError = "Builtin pass does not declare explicit bindings and no implicit contract is available";
}
outPlan = {};
return false;
}
return TryBuildBuiltinPassResourceBindingPlan(implicitBindings, outPlan, outError);
}
inline RHI::DescriptorType ToBuiltinPassDescriptorType(Resources::ShaderResourceType type) {
switch (type) {
case Resources::ShaderResourceType::ConstantBuffer:

View File

@@ -2,6 +2,7 @@
#include <XCEngine/RHI/RHITypes.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Shader/Shader.h>
namespace XCEngine {
namespace Rendering {
@@ -96,59 +97,74 @@ inline RHI::BlendOp ToRHIBlendOp(Resources::MaterialBlendOp op) {
}
}
inline RHI::RasterizerDesc BuildRasterizerState(const Resources::Material* material) {
inline Resources::MaterialRenderState ResolveEffectiveRenderState(
const Resources::ShaderPass* shaderPass,
const Resources::Material* material) {
Resources::MaterialRenderState renderState = {};
if (shaderPass != nullptr && shaderPass->hasFixedFunctionState) {
renderState = shaderPass->fixedFunctionState;
} else if (material != nullptr) {
renderState = material->GetRenderState();
}
if (material != nullptr && material->HasRenderStateOverride()) {
renderState = material->GetRenderState();
}
return renderState;
}
inline RHI::RasterizerDesc BuildRasterizerState(const Resources::MaterialRenderState& renderState) {
RHI::RasterizerDesc desc = {};
desc.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
desc.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
desc.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
desc.depthClipEnable = true;
if (material != nullptr) {
const Resources::MaterialRenderState& renderState = material->GetRenderState();
desc.cullMode = static_cast<uint32_t>(ToRHICullMode(renderState.cullMode));
}
desc.cullMode = static_cast<uint32_t>(ToRHICullMode(renderState.cullMode));
return desc;
}
inline RHI::BlendDesc BuildBlendState(const Resources::Material* material) {
inline RHI::BlendDesc BuildBlendState(const Resources::MaterialRenderState& renderState) {
RHI::BlendDesc desc = {};
if (material != nullptr) {
const Resources::MaterialRenderState& renderState = material->GetRenderState();
desc.blendEnable = renderState.blendEnable;
desc.srcBlend = static_cast<uint32_t>(ToRHIBlendFactor(renderState.srcBlend));
desc.dstBlend = static_cast<uint32_t>(ToRHIBlendFactor(renderState.dstBlend));
desc.srcBlendAlpha = static_cast<uint32_t>(ToRHIBlendFactor(renderState.srcBlendAlpha));
desc.dstBlendAlpha = static_cast<uint32_t>(ToRHIBlendFactor(renderState.dstBlendAlpha));
desc.blendOp = static_cast<uint32_t>(ToRHIBlendOp(renderState.blendOp));
desc.blendOpAlpha = static_cast<uint32_t>(ToRHIBlendOp(renderState.blendOpAlpha));
desc.colorWriteMask = renderState.colorWriteMask;
}
desc.blendEnable = renderState.blendEnable;
desc.srcBlend = static_cast<uint32_t>(ToRHIBlendFactor(renderState.srcBlend));
desc.dstBlend = static_cast<uint32_t>(ToRHIBlendFactor(renderState.dstBlend));
desc.srcBlendAlpha = static_cast<uint32_t>(ToRHIBlendFactor(renderState.srcBlendAlpha));
desc.dstBlendAlpha = static_cast<uint32_t>(ToRHIBlendFactor(renderState.dstBlendAlpha));
desc.blendOp = static_cast<uint32_t>(ToRHIBlendOp(renderState.blendOp));
desc.blendOpAlpha = static_cast<uint32_t>(ToRHIBlendOp(renderState.blendOpAlpha));
desc.colorWriteMask = renderState.colorWriteMask;
return desc;
}
inline RHI::DepthStencilStateDesc BuildDepthStencilState(const Resources::Material* material) {
inline RHI::DepthStencilStateDesc BuildDepthStencilState(const Resources::MaterialRenderState& renderState) {
RHI::DepthStencilStateDesc desc = {};
desc.depthTestEnable = true;
desc.depthWriteEnable = true;
desc.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Less);
desc.depthTestEnable = renderState.depthTestEnable;
desc.depthWriteEnable = renderState.depthWriteEnable;
desc.depthFunc = static_cast<uint32_t>(ToRHIComparisonFunc(renderState.depthFunc));
desc.stencilEnable = false;
if (material != nullptr) {
const Resources::MaterialRenderState& renderState = material->GetRenderState();
desc.depthTestEnable = renderState.depthTestEnable;
desc.depthWriteEnable = renderState.depthWriteEnable;
desc.depthFunc = static_cast<uint32_t>(ToRHIComparisonFunc(renderState.depthFunc));
}
return desc;
}
inline void ApplyRenderState(const Resources::MaterialRenderState& renderState, RHI::GraphicsPipelineDesc& pipelineDesc) {
pipelineDesc.rasterizerState = BuildRasterizerState(renderState);
pipelineDesc.blendState = BuildBlendState(renderState);
pipelineDesc.depthStencilState = BuildDepthStencilState(renderState);
}
inline void ApplyMaterialRenderState(const Resources::Material* material, RHI::GraphicsPipelineDesc& pipelineDesc) {
pipelineDesc.rasterizerState = BuildRasterizerState(material);
pipelineDesc.blendState = BuildBlendState(material);
pipelineDesc.depthStencilState = BuildDepthStencilState(material);
ApplyRenderState(ResolveEffectiveRenderState(nullptr, material), pipelineDesc);
}
inline void ApplyResolvedRenderState(
const Resources::ShaderPass* shaderPass,
const Resources::Material* material,
RHI::GraphicsPipelineDesc& pipelineDesc) {
ApplyRenderState(ResolveEffectiveRenderState(shaderPass, material), pipelineDesc);
}
struct MaterialRenderStateHash {

View File

@@ -3,6 +3,7 @@
#include <XCEngine/Core/Asset/AssetRef.h>
#include <XCEngine/Core/Asset/ResourceHandle.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Resources/Material/MaterialRenderState.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Core/Containers/HashMap.h>
@@ -25,87 +26,6 @@ enum class MaterialRenderQueue : Core::int32 {
Overlay = 4000
};
enum class MaterialCullMode : Core::uint8 {
None = 0,
Front = 1,
Back = 2
};
enum class MaterialComparisonFunc : Core::uint8 {
Never = 0,
Less = 1,
Equal = 2,
LessEqual = 3,
Greater = 4,
NotEqual = 5,
GreaterEqual = 6,
Always = 7
};
enum class MaterialBlendOp : Core::uint8 {
Add = 0,
Subtract = 1,
ReverseSubtract = 2,
Min = 3,
Max = 4
};
enum class MaterialBlendFactor : Core::uint8 {
Zero = 0,
One = 1,
SrcColor = 2,
InvSrcColor = 3,
SrcAlpha = 4,
InvSrcAlpha = 5,
DstAlpha = 6,
InvDstAlpha = 7,
DstColor = 8,
InvDstColor = 9,
SrcAlphaSat = 10,
BlendFactor = 11,
InvBlendFactor = 12,
Src1Color = 13,
InvSrc1Color = 14,
Src1Alpha = 15,
InvSrc1Alpha = 16
};
struct MaterialRenderState {
bool blendEnable = false;
MaterialBlendFactor srcBlend = MaterialBlendFactor::One;
MaterialBlendFactor dstBlend = MaterialBlendFactor::Zero;
MaterialBlendFactor srcBlendAlpha = MaterialBlendFactor::One;
MaterialBlendFactor dstBlendAlpha = MaterialBlendFactor::Zero;
MaterialBlendOp blendOp = MaterialBlendOp::Add;
MaterialBlendOp blendOpAlpha = MaterialBlendOp::Add;
Core::uint8 colorWriteMask = 0xF;
bool depthTestEnable = true;
bool depthWriteEnable = true;
MaterialComparisonFunc depthFunc = MaterialComparisonFunc::Less;
MaterialCullMode cullMode = MaterialCullMode::None;
bool operator==(const MaterialRenderState& other) const {
return blendEnable == other.blendEnable &&
srcBlend == other.srcBlend &&
dstBlend == other.dstBlend &&
srcBlendAlpha == other.srcBlendAlpha &&
dstBlendAlpha == other.dstBlendAlpha &&
blendOp == other.blendOp &&
blendOpAlpha == other.blendOpAlpha &&
colorWriteMask == other.colorWriteMask &&
depthTestEnable == other.depthTestEnable &&
depthWriteEnable == other.depthWriteEnable &&
depthFunc == other.depthFunc &&
cullMode == other.cullMode;
}
bool operator!=(const MaterialRenderState& other) const {
return !(*this == other);
}
};
enum class MaterialPropertyType {
Float, Float2, Float3, Float4,
Int, Int2, Int3, Int4,
@@ -179,6 +99,8 @@ public:
void SetRenderState(const MaterialRenderState& renderState);
const MaterialRenderState& GetRenderState() const { return m_renderState; }
bool HasRenderStateOverride() const { return m_hasRenderStateOverride; }
void SetRenderStateOverrideEnabled(bool enabled);
void SetShaderPass(const Containers::String& shaderPass);
const Containers::String& GetShaderPass() const { return m_shaderPass; }
@@ -256,6 +178,7 @@ private:
ResourceHandle<class Shader> m_shader;
Core::int32 m_renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
MaterialRenderState m_renderState;
bool m_hasRenderStateOverride = false;
Containers::String m_shaderPass;
Containers::Array<MaterialTagEntry> m_tags;
ShaderKeywordSet m_keywordSet;

View File

@@ -0,0 +1,90 @@
#pragma once
#include <XCEngine/Core/Types.h>
namespace XCEngine {
namespace Resources {
enum class MaterialCullMode : Core::uint8 {
None = 0,
Front = 1,
Back = 2
};
enum class MaterialComparisonFunc : Core::uint8 {
Never = 0,
Less = 1,
Equal = 2,
LessEqual = 3,
Greater = 4,
NotEqual = 5,
GreaterEqual = 6,
Always = 7
};
enum class MaterialBlendOp : Core::uint8 {
Add = 0,
Subtract = 1,
ReverseSubtract = 2,
Min = 3,
Max = 4
};
enum class MaterialBlendFactor : Core::uint8 {
Zero = 0,
One = 1,
SrcColor = 2,
InvSrcColor = 3,
SrcAlpha = 4,
InvSrcAlpha = 5,
DstAlpha = 6,
InvDstAlpha = 7,
DstColor = 8,
InvDstColor = 9,
SrcAlphaSat = 10,
BlendFactor = 11,
InvBlendFactor = 12,
Src1Color = 13,
InvSrc1Color = 14,
Src1Alpha = 15,
InvSrc1Alpha = 16
};
struct MaterialRenderState {
bool blendEnable = false;
MaterialBlendFactor srcBlend = MaterialBlendFactor::One;
MaterialBlendFactor dstBlend = MaterialBlendFactor::Zero;
MaterialBlendFactor srcBlendAlpha = MaterialBlendFactor::One;
MaterialBlendFactor dstBlendAlpha = MaterialBlendFactor::Zero;
MaterialBlendOp blendOp = MaterialBlendOp::Add;
MaterialBlendOp blendOpAlpha = MaterialBlendOp::Add;
Core::uint8 colorWriteMask = 0xF;
bool depthTestEnable = true;
bool depthWriteEnable = true;
MaterialComparisonFunc depthFunc = MaterialComparisonFunc::Less;
MaterialCullMode cullMode = MaterialCullMode::None;
bool operator==(const MaterialRenderState& other) const {
return blendEnable == other.blendEnable &&
srcBlend == other.srcBlend &&
dstBlend == other.dstBlend &&
srcBlendAlpha == other.srcBlendAlpha &&
dstBlendAlpha == other.dstBlendAlpha &&
blendOp == other.blendOp &&
blendOpAlpha == other.blendOpAlpha &&
colorWriteMask == other.colorWriteMask &&
depthTestEnable == other.depthTestEnable &&
depthWriteEnable == other.depthWriteEnable &&
depthFunc == other.depthFunc &&
cullMode == other.cullMode;
}
bool operator!=(const MaterialRenderState& other) const {
return !(*this == other);
}
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -3,6 +3,7 @@
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Containers/Array.h>
#include <XCEngine/Core/Types.h>
#include <XCEngine/Resources/Material/MaterialRenderState.h>
#include <XCEngine/Resources/Shader/ShaderKeywordTypes.h>
namespace XCEngine {
@@ -97,6 +98,8 @@ struct ShaderStageVariant {
struct ShaderPass {
Containers::String name;
bool hasFixedFunctionState = false;
MaterialRenderState fixedFunctionState;
Containers::Array<ShaderPassTagEntry> tags;
Containers::Array<ShaderResourceBindingDesc> resources;
Containers::Array<ShaderKeywordDeclaration> keywordDeclarations;
@@ -138,6 +141,8 @@ public:
void ClearProperties();
const Containers::Array<ShaderPropertyDesc>& GetProperties() const { return m_properties; }
const ShaderPropertyDesc* FindProperty(const Containers::String& propertyName) const;
void SetFallback(const Containers::String& fallback);
const Containers::String& GetFallback() const { return m_fallback; }
void AddPass(const ShaderPass& pass);
void ClearPasses();
@@ -189,6 +194,7 @@ private:
Containers::Array<ShaderAttribute> m_attributes;
Containers::Array<ShaderPropertyDesc> m_properties;
Containers::Array<ShaderPass> m_passes;
Containers::String m_fallback;
class IRHIShader* m_rhiResource = nullptr;
};

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) {

View File

@@ -295,6 +295,30 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplic
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitForwardContract) {
ShaderPass pass = {};
pass.name = "ForwardLit";
ShaderPassTagEntry tag = {};
tag.name = "LightMode";
tag.value = "ForwardBase";
pass.tags.PushBack(tag);
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass, plan, &error)) << error.CStr();
EXPECT_TRUE(plan.perObject.IsValid());
EXPECT_TRUE(plan.lighting.IsValid());
EXPECT_TRUE(plan.material.IsValid());
EXPECT_TRUE(plan.shadowReceiver.IsValid());
EXPECT_TRUE(plan.baseColorTexture.IsValid());
EXPECT_TRUE(plan.linearClampSampler.IsValid());
EXPECT_TRUE(plan.shadowMapTexture.IsValid());
EXPECT_TRUE(plan.shadowMapSampler.IsValid());
EXPECT_EQ(plan.firstDescriptorSet, 0u);
EXPECT_EQ(plan.descriptorSetCount, 8u);
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitUnlitResources) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinUnlitShaderPath());
@@ -529,6 +553,45 @@ TEST(BuiltinObjectIdPass_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitO
delete shader;
}
TEST(BuiltinObjectIdPass_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitObjectIdContract) {
ShaderPass pass = {};
pass.name = "ObjectId";
ShaderPassTagEntry tag = {};
tag.name = "LightMode";
tag.value = "ObjectId";
pass.tags.PushBack(tag);
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass, plan, &error)) << error.CStr();
ASSERT_EQ(plan.bindings.Size(), 1u);
EXPECT_TRUE(plan.perObject.IsValid());
EXPECT_FALSE(plan.material.IsValid());
EXPECT_EQ(plan.firstDescriptorSet, 0u);
EXPECT_EQ(plan.descriptorSetCount, 1u);
}
TEST(BuiltinDepthStylePass_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitDepthOnlyContract) {
ShaderPass pass = {};
pass.name = "DepthOnly";
ShaderPassTagEntry tag = {};
tag.name = "LightMode";
tag.value = "DepthOnly";
pass.tags.PushBack(tag);
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass, plan, &error)) << error.CStr();
EXPECT_TRUE(plan.perObject.IsValid());
EXPECT_TRUE(plan.material.IsValid());
EXPECT_TRUE(plan.baseColorTexture.IsValid());
EXPECT_TRUE(plan.linearClampSampler.IsValid());
EXPECT_EQ(plan.firstDescriptorSet, 0u);
EXPECT_EQ(plan.descriptorSetCount, 4u);
}
TEST(BuiltinObjectIdPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
const InputLayoutDesc inputLayout = BuiltinObjectIdPass::BuildInputLayout();

View File

@@ -24,6 +24,20 @@ void WriteTextFile(const std::filesystem::path& path, const std::string& content
ASSERT_TRUE(static_cast<bool>(output));
}
const ShaderPassTagEntry* FindPassTag(const ShaderPass* pass, const char* name) {
if (pass == nullptr || name == nullptr) {
return nullptr;
}
for (const ShaderPassTagEntry& tag : pass->tags) {
if (tag.name == name) {
return &tag;
}
}
return nullptr;
}
TEST(ShaderLoader, GetResourceType) {
ShaderLoader loader;
EXPECT_EQ(loader.GetResourceType(), ResourceType::Shader);
@@ -640,11 +654,13 @@ TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringBuildsGenericHlslVar
const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(pass->tags.Size(), 2u);
ASSERT_EQ(pass->tags.Size(), 3u);
EXPECT_EQ(pass->tags[0].name, "Queue");
EXPECT_EQ(pass->tags[0].value, "Geometry");
EXPECT_EQ(pass->tags[1].name, "LightMode");
EXPECT_EQ(pass->tags[1].value, "ForwardLit");
EXPECT_EQ(pass->tags[1].name, "LOD");
EXPECT_EQ(pass->tags[1].value, "200");
EXPECT_EQ(pass->tags[2].name, "LightMode");
EXPECT_EQ(pass->tags[2].value, "ForwardLit");
EXPECT_TRUE(pass->resources.Empty());
ASSERT_EQ(pass->keywordDeclarations.Size(), 2u);
EXPECT_EQ(pass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::MultiCompile);
@@ -708,6 +724,134 @@ TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringBuildsGenericHlslVar
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringParsesPassStateAndFallback) {
namespace fs = std::filesystem;
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_single_source_pass_state";
const fs::path shaderPath = shaderRoot / "single_source_state.shader";
fs::remove_all(shaderRoot);
fs::create_directories(shaderRoot);
WriteTextFile(
shaderPath,
R"(Shader "SingleSourceStateful"
{
Fallback "Legacy/Diffuse"
SubShader
{
Pass
{
Name "ForwardLit"
Cull Front
ZWrite Off
ZTest LEqual
Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha
ColorMask RGB
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
float4 Vert() : SV_POSITION { return 0; }
float4 Frag() : SV_TARGET { return 1; }
ENDHLSL
}
}
}
)");
ShaderLoader loader;
LoadResult result = loader.Load(shaderPath.string().c_str());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
auto* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
EXPECT_EQ(shader->GetFallback(), "Legacy/Diffuse");
const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr);
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Front);
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_TRUE(pass->fixedFunctionState.depthTestEnable);
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
EXPECT_TRUE(pass->fixedFunctionState.blendEnable);
EXPECT_EQ(pass->fixedFunctionState.srcBlend, MaterialBlendFactor::SrcAlpha);
EXPECT_EQ(pass->fixedFunctionState.dstBlend, MaterialBlendFactor::InvSrcAlpha);
EXPECT_EQ(pass->fixedFunctionState.srcBlendAlpha, MaterialBlendFactor::One);
EXPECT_EQ(pass->fixedFunctionState.dstBlendAlpha, MaterialBlendFactor::InvSrcAlpha);
EXPECT_EQ(pass->fixedFunctionState.colorWriteMask, 0x7u);
delete shader;
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, AssetDatabaseCreatesShaderArtifactFromSingleSourceAuthoringPreservesPassStateAndFallback) {
namespace fs = std::filesystem;
const fs::path projectRoot = fs::temp_directory_path() / "xc_shader_single_source_artifact_pass_state";
const fs::path shaderDir = projectRoot / "Assets" / "Shaders";
const fs::path shaderPath = shaderDir / "single_source_state.shader";
fs::remove_all(projectRoot);
fs::create_directories(shaderDir);
WriteTextFile(
shaderPath,
R"(Shader "ArtifactSingleSourceStateful"
{
Fallback "Legacy/Cutout"
SubShader
{
Pass
{
Name "ForwardLit"
Cull Off
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGBA
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
float4 Vert() : SV_POSITION { return 0; }
float4 Frag() : SV_TARGET { return 1; }
ENDHLSL
}
}
}
)");
AssetDatabase database;
database.Initialize(projectRoot.string().c_str());
AssetDatabase::ResolvedAsset resolvedAsset;
ASSERT_TRUE(database.EnsureArtifact("Assets/Shaders/single_source_state.shader", ResourceType::Shader, resolvedAsset));
ASSERT_TRUE(resolvedAsset.artifactReady);
ShaderLoader loader;
LoadResult result = loader.Load(resolvedAsset.artifactMainPath.CStr());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
auto* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
EXPECT_EQ(shader->GetFallback(), "Legacy/Cutout");
const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr);
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_TRUE(pass->fixedFunctionState.blendEnable);
EXPECT_EQ(pass->fixedFunctionState.srcBlend, MaterialBlendFactor::SrcAlpha);
EXPECT_EQ(pass->fixedFunctionState.dstBlend, MaterialBlendFactor::InvSrcAlpha);
EXPECT_EQ(pass->fixedFunctionState.colorWriteMask, 0xFu);
delete shader;
database.Shutdown();
fs::remove_all(projectRoot);
}
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringParsesMultiCompileLocalKeywords) {
namespace fs = std::filesystem;
@@ -770,6 +914,82 @@ TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringParsesMultiCompileLo
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringParsesFallbackAndFixedFunctionStateInheritance) {
namespace fs = std::filesystem;
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_single_source_fixed_state";
const fs::path shaderPath = shaderRoot / "single_source_fixed_state.shader";
fs::remove_all(shaderRoot);
fs::create_directories(shaderRoot);
WriteTextFile(
shaderPath,
R"(Shader "SingleSourceFixedState"
{
Fallback "Legacy Shaders/Diffuse"
SubShader
{
Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
LOD 310
Cull Front
ZWrite Off
Pass
{
Name "ForwardLit"
Tags { "LightMode" = "ForwardLit" }
Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGB
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
float4 Vert() : SV_POSITION { return 0; }
float4 Frag() : SV_TARGET { return 1; }
ENDHLSL
}
}
}
)");
ShaderLoader loader;
LoadResult result = loader.Load(shaderPath.string().c_str());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
auto* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
EXPECT_EQ(shader->GetFallback(), "Legacy Shaders/Diffuse");
const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr);
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Front);
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_TRUE(pass->fixedFunctionState.depthTestEnable);
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
EXPECT_TRUE(pass->fixedFunctionState.blendEnable);
EXPECT_EQ(pass->fixedFunctionState.srcBlend, MaterialBlendFactor::SrcAlpha);
EXPECT_EQ(pass->fixedFunctionState.dstBlend, MaterialBlendFactor::InvSrcAlpha);
EXPECT_EQ(pass->fixedFunctionState.srcBlendAlpha, MaterialBlendFactor::SrcAlpha);
EXPECT_EQ(pass->fixedFunctionState.dstBlendAlpha, MaterialBlendFactor::InvSrcAlpha);
EXPECT_EQ(pass->fixedFunctionState.colorWriteMask, 0x7);
const ShaderPassTagEntry* queueTag = FindPassTag(pass, "Queue");
ASSERT_NE(queueTag, nullptr);
EXPECT_EQ(queueTag->value, "Transparent");
const ShaderPassTagEntry* lodTag = FindPassTag(pass, "LOD");
ASSERT_NE(lodTag, nullptr);
EXPECT_EQ(lodTag->value, "310");
const ShaderPassTagEntry* lightModeTag = FindPassTag(pass, "LightMode");
ASSERT_NE(lightModeTag, nullptr);
EXPECT_EQ(lightModeTag->value, "ForwardLit");
delete shader;
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringRejectsBackendPragma) {
namespace fs = std::filesystem;