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