rendering: add builtin alpha-test pass support

This commit is contained in:
2026-04-06 21:05:50 +08:00
parent eea38d57d1
commit 9568cf0a16
26 changed files with 856 additions and 104 deletions

View File

@@ -1,7 +1,20 @@
// XC_BUILTIN_DEPTH_ONLY_OPENGL_PS
#version 430
layout(location = 0) out vec4 fragColor;
layout(binding = 0) uniform sampler2D uBaseColorTexture;
layout(std140, binding = 1) uniform MaterialConstants {
vec4 gBaseColorFactor;
vec4 gAlphaCutoffParams;
};
in vec2 vTexCoord;
void main() {
fragColor = vec4(0.0);
#ifdef XC_ALPHA_TEST
vec4 baseColor = texture(uBaseColorTexture, vTexCoord) * gBaseColorFactor;
if (baseColor.a < gAlphaCutoffParams.x) {
discard;
}
#endif
}

View File

@@ -1,7 +1,21 @@
// XC_BUILTIN_DEPTH_ONLY_VULKAN_PS
#version 450
layout(location = 0) out vec4 fragColor;
layout(set = 2, binding = 0) uniform texture2D uBaseColorTexture;
layout(set = 3, binding = 0) uniform sampler uLinearSampler;
layout(set = 1, binding = 0, std140) uniform MaterialConstants {
vec4 gBaseColorFactor;
vec4 gAlphaCutoffParams;
};
layout(location = 0) in vec2 vTexCoord;
void main() {
fragColor = vec4(0.0);
#ifdef XC_ALPHA_TEST
vec4 baseColor = texture(sampler2D(uBaseColorTexture, uLinearSampler), vTexCoord) * gBaseColorFactor;
if (baseColor.a < gAlphaCutoffParams.x) {
discard;
}
#endif
}

View File

@@ -1,8 +1,20 @@
// XC_BUILTIN_DEPTH_ONLY_D3D12_PS
struct PSInput {
float4 position : SV_POSITION;
Texture2D gBaseColorTexture : register(t0);
SamplerState gLinearSampler : register(s0);
cbuffer MaterialConstants : register(b1) {
float4 gBaseColorFactor;
float4 gAlphaCutoffParams;
};
float4 MainPS(PSInput input) : SV_TARGET {
return float4(0.0, 0.0, 0.0, 0.0);
struct PSInput {
float4 position : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
void MainPS(PSInput input) {
#ifdef XC_ALPHA_TEST
float4 baseColor = gBaseColorTexture.Sample(gLinearSampler, input.texcoord) * gBaseColorFactor;
clip(baseColor.a - gAlphaCutoffParams.x);
#endif
}

View File

@@ -1,5 +1,11 @@
Shader "Builtin Depth Only"
{
Properties
{
_BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)]
_Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)]
_MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)]
}
SubShader
{
Pass
@@ -9,10 +15,14 @@ Shader "Builtin Depth Only"
Resources
{
PerObjectConstants (ConstantBuffer, 0, 0) [Semantic(PerObject)]
MaterialConstants (ConstantBuffer, 1, 0) [Semantic(Material)]
BaseColorTexture (Texture2D, 2, 0) [Semantic(BaseColorTexture)]
LinearClampSampler (Sampler, 3, 0) [Semantic(LinearClampSampler)]
}
HLSLPROGRAM
#pragma vertex MainVS
#pragma fragment MainPS
#pragma shader_feature_local _ XC_ALPHA_TEST
#pragma backend D3D12 HLSL "depth-only.vs.hlsl" "depth-only.ps.hlsl" vs_5_0 ps_5_0
#pragma backend OpenGL GLSL "depth-only.vert.glsl" "depth-only.frag.glsl"
#pragma backend Vulkan GLSL "depth-only.vert.vk.glsl" "depth-only.frag.vk.glsl"

View File

@@ -1,6 +1,7 @@
// XC_BUILTIN_DEPTH_ONLY_OPENGL_VS
#version 430
layout(location = 0) in vec3 aPosition;
layout(location = 2) in vec2 aTexCoord;
layout(std140, binding = 0) uniform PerObjectConstants {
mat4 gProjectionMatrix;
@@ -8,8 +9,11 @@ layout(std140, binding = 0) uniform PerObjectConstants {
mat4 gModelMatrix;
};
out vec2 vTexCoord;
void main() {
vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0);
vec4 positionVS = gViewMatrix * positionWS;
gl_Position = gProjectionMatrix * positionVS;
vTexCoord = aTexCoord;
}

View File

@@ -1,6 +1,7 @@
// XC_BUILTIN_DEPTH_ONLY_VULKAN_VS
#version 450
layout(location = 0) in vec3 aPosition;
layout(location = 2) in vec2 aTexCoord;
layout(set = 0, binding = 0, std140) uniform PerObjectConstants {
mat4 gProjectionMatrix;
@@ -8,8 +9,11 @@ layout(set = 0, binding = 0, std140) uniform PerObjectConstants {
mat4 gModelMatrix;
};
layout(location = 0) out vec2 vTexCoord;
void main() {
vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0);
vec4 positionVS = gViewMatrix * positionWS;
gl_Position = gProjectionMatrix * positionVS;
vTexCoord = aTexCoord;
}

View File

@@ -7,10 +7,12 @@ cbuffer PerObjectConstants : register(b0) {
struct VSInput {
float3 position : POSITION;
float2 texcoord : TEXCOORD0;
};
struct PSInput {
float4 position : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
PSInput MainVS(VSInput input) {
@@ -18,5 +20,6 @@ PSInput MainVS(VSInput input) {
float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0));
float4 positionVS = mul(gViewMatrix, positionWS);
output.position = mul(gProjectionMatrix, positionVS);
output.texcoord = input.texcoord;
return output;
}

View File

@@ -28,6 +28,7 @@ layout(std140, binding = 1) uniform LightingConstants {
layout(std140, binding = 2) uniform MaterialConstants {
vec4 gBaseColorFactor;
vec4 gAlphaCutoffParams;
};
layout(std140, binding = 3) uniform ShadowReceiverConstants {
@@ -137,6 +138,11 @@ vec3 EvaluateAdditionalLight(AdditionalLightData light, vec3 normalWS, vec3 posi
void main() {
vec4 baseColor = texture(uBaseColorTexture, vTexCoord) * gBaseColorFactor;
#ifdef XC_ALPHA_TEST
if (baseColor.a < gAlphaCutoffParams.x) {
discard;
}
#endif
int additionalLightCount = min(int(gLightingParams.x + 0.5), XC_MAX_ADDITIONAL_LIGHTS);
if (gMainLightColorAndFlags.w < 0.5 && additionalLightCount == 0) {
fragColor = baseColor;

View File

@@ -30,6 +30,7 @@ layout(set = 1, binding = 0, std140) uniform LightingConstants {
layout(set = 2, binding = 0, std140) uniform MaterialConstants {
vec4 gBaseColorFactor;
vec4 gAlphaCutoffParams;
};
layout(set = 3, binding = 0, std140) uniform ShadowReceiverConstants {
@@ -139,6 +140,11 @@ vec3 EvaluateAdditionalLight(AdditionalLightData light, vec3 normalWS, vec3 posi
void main() {
vec4 baseColor = texture(sampler2D(uBaseColorTexture, uLinearSampler), vTexCoord) * gBaseColorFactor;
#ifdef XC_ALPHA_TEST
if (baseColor.a < gAlphaCutoffParams.x) {
discard;
}
#endif
int additionalLightCount = min(int(gLightingParams.x + 0.5), XC_MAX_ADDITIONAL_LIGHTS);
if (gMainLightColorAndFlags.w < 0.5 && additionalLightCount == 0) {
fragColor = baseColor;

View File

@@ -29,6 +29,7 @@ cbuffer LightingConstants : register(b1) {
cbuffer MaterialConstants : register(b2) {
float4 gBaseColorFactor;
float4 gAlphaCutoffParams;
};
cbuffer ShadowReceiverConstants : register(b3) {
@@ -139,6 +140,9 @@ float3 EvaluateAdditionalLight(AdditionalLightData light, float3 normalWS, float
float4 MainPS(PSInput input) : SV_TARGET {
float4 baseColor = gBaseColorTexture.Sample(gLinearSampler, input.texcoord) * gBaseColorFactor;
#ifdef XC_ALPHA_TEST
clip(baseColor.a - gAlphaCutoffParams.x);
#endif
const int additionalLightCount = min((int)gLightingParams.x, XC_MAX_ADDITIONAL_LIGHTS);
if (gMainLightColorAndFlags.a < 0.5f && additionalLightCount == 0) {
return baseColor;

View File

@@ -3,6 +3,7 @@ Shader "Builtin Forward Lit"
Properties
{
_BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)]
_Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)]
_MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)]
}
SubShader
@@ -26,6 +27,7 @@ Shader "Builtin Forward Lit"
#pragma vertex MainVS
#pragma fragment MainPS
#pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS
#pragma shader_feature_local _ XC_ALPHA_TEST
#pragma backend D3D12 HLSL "forward-lit.vs.hlsl" "forward-lit.ps.hlsl" vs_5_0 ps_5_0
#pragma backend OpenGL GLSL "forward-lit.vert.glsl" "forward-lit.frag.glsl"
#pragma backend Vulkan GLSL "forward-lit.vert.vk.glsl" "forward-lit.frag.vk.glsl"

View File

@@ -1,7 +1,21 @@
// XC_BUILTIN_SHADOW_CASTER_OPENGL_PS
#version 430
layout(location = 0) out vec4 fragColor;
layout(binding = 0) uniform sampler2D uBaseColorTexture;
layout(std140, binding = 1) uniform MaterialConstants {
vec4 gBaseColorFactor;
vec4 gAlphaCutoffParams;
};
in vec2 vTexCoord;
void main() {
fragColor = vec4(0.0);
#ifdef XC_ALPHA_TEST
vec4 baseColor = texture(uBaseColorTexture, vTexCoord) * gBaseColorFactor;
if (baseColor.a < gAlphaCutoffParams.x) {
discard;
}
#endif
gl_FragDepth = gl_FragCoord.z;
}

View File

@@ -1,7 +1,22 @@
// XC_BUILTIN_SHADOW_CASTER_VULKAN_PS
#version 450
layout(location = 0) out vec4 fragColor;
layout(set = 2, binding = 0) uniform texture2D uBaseColorTexture;
layout(set = 3, binding = 0) uniform sampler uLinearSampler;
layout(set = 1, binding = 0, std140) uniform MaterialConstants {
vec4 gBaseColorFactor;
vec4 gAlphaCutoffParams;
};
layout(location = 0) in vec2 vTexCoord;
void main() {
fragColor = vec4(0.0);
#ifdef XC_ALPHA_TEST
vec4 baseColor = texture(sampler2D(uBaseColorTexture, uLinearSampler), vTexCoord) * gBaseColorFactor;
if (baseColor.a < gAlphaCutoffParams.x) {
discard;
}
#endif
gl_FragDepth = gl_FragCoord.z;
}

View File

@@ -1,8 +1,21 @@
// XC_BUILTIN_SHADOW_CASTER_D3D12_PS
struct PSInput {
float4 position : SV_POSITION;
Texture2D gBaseColorTexture : register(t0);
SamplerState gLinearSampler : register(s0);
cbuffer MaterialConstants : register(b1) {
float4 gBaseColorFactor;
float4 gAlphaCutoffParams;
};
float4 MainPS(PSInput input) : SV_TARGET {
return float4(0.0, 0.0, 0.0, 0.0);
struct PSInput {
float4 position : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
float MainPS(PSInput input) : SV_Depth {
#ifdef XC_ALPHA_TEST
float4 baseColor = gBaseColorTexture.Sample(gLinearSampler, input.texcoord) * gBaseColorFactor;
clip(baseColor.a - gAlphaCutoffParams.x);
#endif
return input.position.z;
}

View File

@@ -1,5 +1,11 @@
Shader "Builtin Shadow Caster"
{
Properties
{
_BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)]
_Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)]
_MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)]
}
SubShader
{
Pass
@@ -9,10 +15,14 @@ Shader "Builtin Shadow Caster"
Resources
{
PerObjectConstants (ConstantBuffer, 0, 0) [Semantic(PerObject)]
MaterialConstants (ConstantBuffer, 1, 0) [Semantic(Material)]
BaseColorTexture (Texture2D, 2, 0) [Semantic(BaseColorTexture)]
LinearClampSampler (Sampler, 3, 0) [Semantic(LinearClampSampler)]
}
HLSLPROGRAM
#pragma vertex MainVS
#pragma fragment MainPS
#pragma shader_feature_local _ XC_ALPHA_TEST
#pragma backend D3D12 HLSL "shadow-caster.vs.hlsl" "shadow-caster.ps.hlsl" vs_5_0 ps_5_0
#pragma backend OpenGL GLSL "shadow-caster.vert.glsl" "shadow-caster.frag.glsl"
#pragma backend Vulkan GLSL "shadow-caster.vert.vk.glsl" "shadow-caster.frag.vk.glsl"

View File

@@ -1,6 +1,7 @@
// XC_BUILTIN_SHADOW_CASTER_OPENGL_VS
#version 430
layout(location = 0) in vec3 aPosition;
layout(location = 2) in vec2 aTexCoord;
layout(std140, binding = 0) uniform PerObjectConstants {
mat4 gProjectionMatrix;
@@ -8,8 +9,11 @@ layout(std140, binding = 0) uniform PerObjectConstants {
mat4 gModelMatrix;
};
out vec2 vTexCoord;
void main() {
vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0);
vec4 positionVS = gViewMatrix * positionWS;
gl_Position = gProjectionMatrix * positionVS;
vTexCoord = aTexCoord;
}

View File

@@ -1,6 +1,7 @@
// XC_BUILTIN_SHADOW_CASTER_VULKAN_VS
#version 450
layout(location = 0) in vec3 aPosition;
layout(location = 2) in vec2 aTexCoord;
layout(set = 0, binding = 0, std140) uniform PerObjectConstants {
mat4 gProjectionMatrix;
@@ -8,8 +9,11 @@ layout(set = 0, binding = 0, std140) uniform PerObjectConstants {
mat4 gModelMatrix;
};
layout(location = 0) out vec2 vTexCoord;
void main() {
vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0);
vec4 positionVS = gViewMatrix * positionWS;
gl_Position = gProjectionMatrix * positionVS;
vTexCoord = aTexCoord;
}

View File

@@ -7,16 +7,19 @@ cbuffer PerObjectConstants : register(b0) {
struct VSInput {
float3 position : POSITION;
float2 texcoord : TEXCOORD0;
};
struct PSInput {
float4 position : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
PSInput MainVS(VSInput input) {
PSInput output;
float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0));
float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f));
float4 positionVS = mul(gViewMatrix, positionWS);
output.position = mul(gProjectionMatrix, positionVS);
output.texcoord = input.texcoord;
return output;
}

View File

@@ -14,6 +14,7 @@ namespace Rendering {
struct BuiltinForwardMaterialData {
Math::Vector4 baseColorFactor = Math::Vector4::One();
float alphaCutoff = 0.5f;
};
enum class BuiltinSkyboxTextureMode : Core::uint8 {
@@ -141,9 +142,39 @@ inline const Resources::Texture* ResolveBuiltinBaseColorTexture(const Resources:
return nullptr;
}
inline float ResolveBuiltinAlphaCutoff(const Resources::Material* material) {
if (material == nullptr) {
return 0.5f;
}
if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "AlphaCutoff")) {
if (material->HasProperty(property->name) &&
(property->type == Resources::ShaderPropertyType::Float ||
property->type == Resources::ShaderPropertyType::Range)) {
return material->GetFloat(property->name);
}
}
static const char* kCutoffPropertyNames[] = {
"_Cutoff",
"cutoff",
"_AlphaCutoff",
"alphaCutoff"
};
for (const char* propertyName : kCutoffPropertyNames) {
if (material->HasProperty(Containers::String(propertyName))) {
return material->GetFloat(Containers::String(propertyName));
}
}
return 0.5f;
}
inline BuiltinForwardMaterialData BuildBuiltinForwardMaterialData(const Resources::Material* material) {
BuiltinForwardMaterialData data = {};
data.baseColorFactor = ResolveBuiltinBaseColorFactor(material);
data.alphaCutoff = ResolveBuiltinAlphaCutoff(material);
return data;
}

View File

@@ -3,6 +3,7 @@
#include <XCEngine/Core/Asset/ResourceHandle.h>
#include <XCEngine/Core/Math/Matrix4.h>
#include <XCEngine/Rendering/Builtin/BuiltinPassTypes.h>
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
#include <XCEngine/Rendering/RenderPass.h>
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
@@ -10,14 +11,19 @@
#include <XCEngine/RHI/RHIDescriptorSet.h>
#include <XCEngine/RHI/RHIPipelineLayout.h>
#include <XCEngine/RHI/RHIPipelineState.h>
#include <XCEngine/RHI/RHIResourceView.h>
#include <XCEngine/RHI/RHISampler.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <unordered_map>
#include <vector>
namespace XCEngine {
namespace Resources {
class Material;
class Shader;
class Texture;
} // namespace Resources
namespace Rendering {
@@ -54,6 +60,11 @@ private:
RHI::RHIDescriptorSet* set = nullptr;
};
struct FallbackPerMaterialConstants {
Math::Vector4 baseColorFactor = Math::Vector4::One();
Math::Vector4 alphaCutoffParams = Math::Vector4(0.5f, 0.0f, 0.0f, 0.0f);
};
struct PassLayoutKey {
const Resources::Shader* shader = nullptr;
Containers::String passName;
@@ -73,29 +84,46 @@ private:
struct PassResourceLayout {
RHI::RHIPipelineLayout* pipelineLayout = nullptr;
PassResourceBindingLocation perObject = {};
BuiltinPassSetLayoutMetadata perObjectSetLayout = {};
Core::uint32 firstDescriptorSet = 0;
Core::uint32 descriptorSetCount = 0;
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
std::vector<OwnedDescriptorSet> staticDescriptorSets;
PassResourceBindingLocation perObject = {};
PassResourceBindingLocation material = {};
PassResourceBindingLocation baseColorTexture = {};
PassResourceBindingLocation linearClampSampler = {};
};
struct PerObjectSetKey {
struct DynamicDescriptorSetKey {
PassLayoutKey passLayout = {};
Core::uint32 setIndex = 0;
Core::uint64 objectId = 0;
const Resources::Material* material = nullptr;
bool operator==(const PerObjectSetKey& other) const {
bool operator==(const DynamicDescriptorSetKey& other) const {
return passLayout == other.passLayout &&
setIndex == other.setIndex &&
material == other.material &&
objectId == other.objectId;
}
};
struct PerObjectSetKeyHash {
size_t operator()(const PerObjectSetKey& key) const noexcept {
struct DynamicDescriptorSetKeyHash {
size_t operator()(const DynamicDescriptorSetKey& key) const noexcept {
size_t hash = PassLayoutKeyHash()(key.passLayout);
hash ^= std::hash<Core::uint32>{}(key.setIndex) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= std::hash<Core::uint64>{}(key.objectId) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= reinterpret_cast<size_t>(key.material) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
return hash;
}
};
struct CachedDescriptorSet {
OwnedDescriptorSet descriptorSet = {};
Core::uint64 materialVersion = 0;
RHI::RHIResourceView* baseColorTextureView = nullptr;
};
struct ResolvedShaderPass {
const Resources::Shader* shader = nullptr;
const Resources::ShaderPass* pass = nullptr;
@@ -157,12 +185,23 @@ private:
bool CreateOwnedDescriptorSet(
const BuiltinPassSetLayoutMetadata& setLayout,
OwnedDescriptorSet& descriptorSet);
RHI::RHIDescriptorSet* GetOrCreatePerObjectSet(
RHI::RHIDescriptorSet* GetOrCreateStaticDescriptorSet(
const PassLayoutKey& passLayoutKey,
PassResourceLayout& passLayout,
Core::uint32 setIndex);
CachedDescriptorSet* GetOrCreateDynamicDescriptorSet(
const PassLayoutKey& passLayoutKey,
const PassResourceLayout& passLayout,
Core::uint64 objectId);
const BuiltinPassSetLayoutMetadata& setLayout,
Core::uint32 setIndex,
Core::uint64 objectId,
const Resources::Material* material,
const MaterialConstantPayloadView& materialConstants,
RHI::RHIResourceView* baseColorTextureView);
void DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet);
void DestroyPassResourceLayout(PassResourceLayout& passLayout);
RHI::RHIResourceView* ResolveTextureView(const Resources::Texture* texture);
RHI::RHIResourceView* ResolveTextureView(const VisibleRenderItem& visibleItem);
bool DrawVisibleItem(
const RenderContext& context,
const RenderSurface& surface,
@@ -178,7 +217,10 @@ private:
std::unordered_map<PassLayoutKey, PassResourceLayout, PassLayoutKeyHash> m_passResourceLayouts;
std::unordered_map<PipelineStateKey, RHI::RHIPipelineState*, PipelineStateKeyHash> m_pipelineStates;
std::unordered_map<PerObjectSetKey, OwnedDescriptorSet, PerObjectSetKeyHash> m_perObjectSets;
std::unordered_map<DynamicDescriptorSetKey, CachedDescriptorSet, DynamicDescriptorSetKeyHash> m_dynamicDescriptorSets;
RHI::RHISampler* m_sampler = nullptr;
RHI::RHITexture* m_fallbackTexture2D = nullptr;
RHI::RHIResourceView* m_fallbackTexture2DView = nullptr;
};
} // namespace Passes

View File

@@ -98,6 +98,7 @@ private:
struct FallbackPerMaterialConstants {
Math::Vector4 baseColorFactor = Math::Vector4::One();
Math::Vector4 alphaCutoffParams = Math::Vector4(0.5f, 0.0f, 0.0f, 0.0f);
};
struct SkyboxConstants {

View File

@@ -11,7 +11,9 @@
#include "Rendering/RenderSurface.h"
#include "Resources/Material/Material.h"
#include "Resources/Mesh/Mesh.h"
#include "Resources/Texture/Texture.h"
#include <algorithm>
#include <vector>
namespace XCEngine {
@@ -37,6 +39,40 @@ bool IsSupportedPerObjectOnlyBindingPlan(const BuiltinPassResourceBindingPlan& b
!bindingPlan.usesSamplers;
}
bool UsesContiguousDescriptorSets(const BuiltinPassResourceBindingPlan& bindingPlan) {
if (bindingPlan.bindings.Empty()) {
return true;
}
std::vector<bool> usedSets(static_cast<size_t>(bindingPlan.maxSetIndex) + 1u, false);
Core::uint32 minSet = UINT32_MAX;
Core::uint32 maxSet = 0;
for (const BuiltinPassResourceBindingDesc& binding : bindingPlan.bindings) {
usedSets[binding.location.set] = true;
minSet = std::min(minSet, binding.location.set);
maxSet = std::max(maxSet, binding.location.set);
}
for (Core::uint32 setIndex = minSet; setIndex <= maxSet; ++setIndex) {
if (!usedSets[setIndex]) {
return false;
}
}
return true;
}
bool IsSupportedAlphaTestBindingPlan(const BuiltinPassResourceBindingPlan& bindingPlan) {
return bindingPlan.perObject.IsValid() &&
bindingPlan.material.IsValid() &&
bindingPlan.baseColorTexture.IsValid() &&
bindingPlan.linearClampSampler.IsValid() &&
!bindingPlan.lighting.IsValid() &&
!bindingPlan.shadowReceiver.IsValid() &&
!bindingPlan.shadowMapTexture.IsValid() &&
!bindingPlan.shadowMapSampler.IsValid();
}
uint32_t ResolveSurfaceColorAttachmentCount(const RenderSurface& surface) {
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
return (!colorAttachments.empty() && colorAttachments[0] != nullptr) ? 1u : 0u;
@@ -131,16 +167,66 @@ bool BuiltinDepthStylePassBase::CreateResources(const RenderContext& context) {
return false;
}
RHI::SamplerDesc samplerDesc = {};
samplerDesc.filter = static_cast<uint32_t>(RHI::FilterMode::Linear);
samplerDesc.addressU = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
samplerDesc.addressV = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
samplerDesc.addressW = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
samplerDesc.mipLodBias = 0.0f;
samplerDesc.maxAnisotropy = 1;
samplerDesc.comparisonFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
samplerDesc.minLod = 0.0f;
samplerDesc.maxLod = 1000.0f;
m_sampler = context.device->CreateSampler(samplerDesc);
if (m_sampler == nullptr) {
DestroyResources();
return false;
}
const unsigned char whitePixels2D[4] = {
255, 255, 255, 255
};
RHI::TextureDesc fallback2DDesc = {};
fallback2DDesc.width = 1;
fallback2DDesc.height = 1;
fallback2DDesc.depth = 1;
fallback2DDesc.mipLevels = 1;
fallback2DDesc.arraySize = 1;
fallback2DDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
fallback2DDesc.textureType = static_cast<uint32_t>(RHI::TextureType::Texture2D);
fallback2DDesc.sampleCount = 1;
fallback2DDesc.sampleQuality = 0;
fallback2DDesc.flags = 0;
m_fallbackTexture2D = context.device->CreateTexture(
fallback2DDesc,
whitePixels2D,
sizeof(whitePixels2D),
sizeof(whitePixels2D));
if (m_fallbackTexture2D == nullptr) {
DestroyResources();
return false;
}
RHI::ResourceViewDesc fallback2DViewDesc = {};
fallback2DViewDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
fallback2DViewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
fallback2DViewDesc.mipLevel = 0;
m_fallbackTexture2DView = context.device->CreateShaderResourceView(m_fallbackTexture2D, fallback2DViewDesc);
if (m_fallbackTexture2DView == nullptr) {
DestroyResources();
return false;
}
return true;
}
void BuiltinDepthStylePassBase::DestroyResources() {
m_resourceCache.Shutdown();
for (auto& descriptorSetEntry : m_perObjectSets) {
DestroyOwnedDescriptorSet(descriptorSetEntry.second);
for (auto& descriptorSetEntry : m_dynamicDescriptorSets) {
DestroyOwnedDescriptorSet(descriptorSetEntry.second.descriptorSet);
}
m_perObjectSets.clear();
m_dynamicDescriptorSets.clear();
for (auto& pipelineEntry : m_pipelineStates) {
if (pipelineEntry.second != nullptr) {
@@ -155,6 +241,24 @@ void BuiltinDepthStylePassBase::DestroyResources() {
}
m_passResourceLayouts.clear();
if (m_fallbackTexture2DView != nullptr) {
m_fallbackTexture2DView->Shutdown();
delete m_fallbackTexture2DView;
m_fallbackTexture2DView = nullptr;
}
if (m_fallbackTexture2D != nullptr) {
m_fallbackTexture2D->Shutdown();
delete m_fallbackTexture2D;
m_fallbackTexture2D = nullptr;
}
if (m_sampler != nullptr) {
m_sampler->Shutdown();
delete m_sampler;
m_sampler = nullptr;
}
m_device = nullptr;
m_backendType = RHI::RHIType::D3D12;
m_builtinShader.Reset();
@@ -235,7 +339,7 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve
}
if (m_builtinShader.IsValid() &&
tryResolveFromShader(m_builtinShader.Get(), nullptr)) {
tryResolveFromShader(m_builtinShader.Get(), material)) {
return resolved;
}
@@ -259,9 +363,19 @@ bool BuiltinDepthStylePassBase::TryBuildSupportedBindingPlan(
return false;
}
if (!IsSupportedPerObjectOnlyBindingPlan(outPlan)) {
if (!UsesContiguousDescriptorSets(outPlan)) {
if (outError != nullptr) {
*outError = "Builtin depth-style pass currently requires exactly one PerObject constant-buffer binding";
*outError = "Builtin depth-style pass requires contiguous descriptor set indices";
}
return false;
}
if (!IsSupportedPerObjectOnlyBindingPlan(outPlan) &&
!IsSupportedAlphaTestBindingPlan(outPlan)) {
if (outError != nullptr) {
*outError =
"Builtin depth-style pass requires either PerObject-only bindings or the alpha-test contract "
"(PerObject + Material + BaseColorTexture + LinearClampSampler)";
}
return false;
}
@@ -304,24 +418,26 @@ BuiltinDepthStylePassBase::PassResourceLayout* BuiltinDepthStylePassBase::GetOrC
return failLayout(contextualError.CStr());
}
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
Containers::String setLayoutError;
if (!TryBuildBuiltinPassSetLayouts(bindingPlan, setLayouts, &setLayoutError)) {
if (!TryBuildBuiltinPassSetLayouts(bindingPlan, passLayout.setLayouts, &setLayoutError)) {
return failLayout(setLayoutError.CStr());
}
if (bindingPlan.perObject.set >= setLayouts.size()) {
if (bindingPlan.perObject.set >= passLayout.setLayouts.size()) {
return failLayout("Builtin depth-style pass produced an invalid PerObject descriptor set index");
}
passLayout.perObject = bindingPlan.perObject;
passLayout.firstDescriptorSet = bindingPlan.firstDescriptorSet;
passLayout.perObjectSetLayout = setLayouts[bindingPlan.perObject.set];
RefreshBuiltinPassSetLayoutMetadata(passLayout.perObjectSetLayout);
passLayout.descriptorSetCount = bindingPlan.descriptorSetCount;
passLayout.staticDescriptorSets.resize(passLayout.setLayouts.size());
passLayout.perObject = bindingPlan.perObject;
passLayout.material = bindingPlan.material;
passLayout.baseColorTexture = bindingPlan.baseColorTexture;
passLayout.linearClampSampler = bindingPlan.linearClampSampler;
std::vector<RHI::DescriptorSetLayoutDesc> nativeSetLayouts(setLayouts.size());
for (size_t setIndex = 0; setIndex < setLayouts.size(); ++setIndex) {
nativeSetLayouts[setIndex] = setLayouts[setIndex].layout;
std::vector<RHI::DescriptorSetLayoutDesc> nativeSetLayouts(passLayout.setLayouts.size());
for (size_t setIndex = 0; setIndex < passLayout.setLayouts.size(); ++setIndex) {
nativeSetLayouts[setIndex] = passLayout.setLayouts[setIndex].layout;
}
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
@@ -333,7 +449,7 @@ BuiltinDepthStylePassBase::PassResourceLayout* BuiltinDepthStylePassBase::GetOrC
}
const auto result = m_passResourceLayouts.emplace(passLayoutKey, std::move(passLayout));
RefreshBuiltinPassSetLayoutMetadata(result.first->second.perObjectSetLayout);
RefreshBuiltinPassSetLayouts(result.first->second.setLayouts);
return &result.first->second;
}
@@ -412,31 +528,98 @@ bool BuiltinDepthStylePassBase::CreateOwnedDescriptorSet(
return true;
}
RHI::RHIDescriptorSet* BuiltinDepthStylePassBase::GetOrCreatePerObjectSet(
RHI::RHIDescriptorSet* BuiltinDepthStylePassBase::GetOrCreateStaticDescriptorSet(
const PassLayoutKey& passLayoutKey,
PassResourceLayout& passLayout,
Core::uint32 setIndex) {
(void)passLayoutKey;
if (setIndex >= passLayout.setLayouts.size() ||
setIndex >= passLayout.staticDescriptorSets.size()) {
return nullptr;
}
const BuiltinPassSetLayoutMetadata& setLayout = passLayout.setLayouts[setIndex];
if (setLayout.layout.bindingCount == 0) {
return nullptr;
}
OwnedDescriptorSet& descriptorSet = passLayout.staticDescriptorSets[setIndex];
if (descriptorSet.set == nullptr) {
if (!CreateOwnedDescriptorSet(setLayout, descriptorSet)) {
return nullptr;
}
if (setLayout.usesLinearClampSampler) {
if (!passLayout.linearClampSampler.IsValid() ||
passLayout.linearClampSampler.set != setIndex ||
m_sampler == nullptr) {
DestroyOwnedDescriptorSet(descriptorSet);
return nullptr;
}
descriptorSet.set->UpdateSampler(passLayout.linearClampSampler.binding, m_sampler);
}
}
return descriptorSet.set;
}
BuiltinDepthStylePassBase::CachedDescriptorSet* BuiltinDepthStylePassBase::GetOrCreateDynamicDescriptorSet(
const PassLayoutKey& passLayoutKey,
const PassResourceLayout& passLayout,
Core::uint64 objectId) {
if (!passLayout.perObject.IsValid() ||
passLayout.perObjectSetLayout.layout.bindingCount == 0) {
return nullptr;
}
PerObjectSetKey key = {};
const BuiltinPassSetLayoutMetadata& setLayout,
Core::uint32 setIndex,
Core::uint64 objectId,
const Resources::Material* material,
const MaterialConstantPayloadView& materialConstants,
RHI::RHIResourceView* baseColorTextureView) {
DynamicDescriptorSetKey key = {};
key.passLayout = passLayoutKey;
key.setIndex = setIndex;
key.objectId = objectId;
key.material = material;
const auto existing = m_perObjectSets.find(key);
if (existing != m_perObjectSets.end()) {
return existing->second.set;
CachedDescriptorSet& cachedDescriptorSet = m_dynamicDescriptorSets[key];
if (cachedDescriptorSet.descriptorSet.set == nullptr) {
if (!CreateOwnedDescriptorSet(setLayout, cachedDescriptorSet.descriptorSet)) {
return nullptr;
}
}
OwnedDescriptorSet descriptorSet = {};
if (!CreateOwnedDescriptorSet(passLayout.perObjectSetLayout, descriptorSet)) {
return nullptr;
const Core::uint64 materialVersion = material != nullptr ? material->GetChangeVersion() : 0;
if (setLayout.usesMaterial) {
if (!passLayout.material.IsValid() ||
passLayout.material.set != setIndex ||
!materialConstants.IsValid()) {
return nullptr;
}
if (cachedDescriptorSet.materialVersion != materialVersion) {
cachedDescriptorSet.descriptorSet.set->WriteConstant(
passLayout.material.binding,
materialConstants.data,
materialConstants.size);
}
}
const auto result = m_perObjectSets.emplace(key, descriptorSet);
return result.first->second.set;
if (setLayout.usesBaseColorTexture) {
if (baseColorTextureView == nullptr ||
!passLayout.baseColorTexture.IsValid() ||
passLayout.baseColorTexture.set != setIndex) {
return nullptr;
}
if (cachedDescriptorSet.baseColorTextureView != baseColorTextureView) {
cachedDescriptorSet.descriptorSet.set->Update(
passLayout.baseColorTexture.binding,
baseColorTextureView);
}
}
cachedDescriptorSet.materialVersion = materialVersion;
cachedDescriptorSet.baseColorTextureView = baseColorTextureView;
return &cachedDescriptorSet;
}
void BuiltinDepthStylePassBase::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) {
@@ -454,15 +637,46 @@ void BuiltinDepthStylePassBase::DestroyOwnedDescriptorSet(OwnedDescriptorSet& de
}
void BuiltinDepthStylePassBase::DestroyPassResourceLayout(PassResourceLayout& passLayout) {
for (OwnedDescriptorSet& descriptorSet : passLayout.staticDescriptorSets) {
DestroyOwnedDescriptorSet(descriptorSet);
}
passLayout.staticDescriptorSets.clear();
if (passLayout.pipelineLayout != nullptr) {
passLayout.pipelineLayout->Shutdown();
delete passLayout.pipelineLayout;
passLayout.pipelineLayout = nullptr;
}
passLayout.setLayouts.clear();
passLayout.perObject = {};
passLayout.perObjectSetLayout = {};
passLayout.material = {};
passLayout.baseColorTexture = {};
passLayout.linearClampSampler = {};
passLayout.firstDescriptorSet = 0;
passLayout.descriptorSetCount = 0;
}
RHI::RHIResourceView* BuiltinDepthStylePassBase::ResolveTextureView(
const Resources::Texture* texture) {
if (texture == nullptr) {
return nullptr;
}
const RenderResourceCache::CachedTexture* cachedTexture = m_resourceCache.GetOrCreateTexture(m_device, texture);
if (cachedTexture != nullptr && cachedTexture->shaderResourceView != nullptr) {
return cachedTexture->shaderResourceView;
}
return nullptr;
}
RHI::RHIResourceView* BuiltinDepthStylePassBase::ResolveTextureView(
const VisibleRenderItem& visibleItem) {
const Resources::Material* material = ResolveMaterial(visibleItem);
const Resources::Texture* texture = ResolveBuiltinBaseColorTexture(material);
RHI::RHIResourceView* textureView = ResolveTextureView(texture);
return textureView != nullptr ? textureView : m_fallbackTexture2DView;
}
bool BuiltinDepthStylePassBase::DrawVisibleItem(
@@ -531,18 +745,6 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
commandList->SetIndexBuffer(cachedMesh->indexBufferView, 0);
}
RHI::RHIDescriptorSet* constantSet = GetOrCreatePerObjectSet(
passLayoutKey,
*passLayout,
visibleItem.gameObject->GetID());
if (constantSet == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
(Containers::String("BuiltinDepthStylePassBase failed to allocate descriptor set for ") +
visibleItem.gameObject->GetName().c_str()).CStr());
return false;
}
const Math::Matrix4x4 projectionMatrix =
m_passType == BuiltinMaterialPass::ShadowCaster
? sceneData.cameraData.viewProjection
@@ -556,14 +758,122 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
viewMatrix,
visibleItem.localToWorld.Transpose()
};
constantSet->WriteConstant(passLayout->perObject.binding, &constants, sizeof(constants));
RHI::RHIDescriptorSet* descriptorSets[] = { constantSet };
commandList->SetGraphicsDescriptorSets(
passLayout->firstDescriptorSet,
1,
descriptorSets,
passLayout->pipelineLayout);
MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material);
FallbackPerMaterialConstants fallbackMaterialConstants = {};
if (!materialConstants.IsValid()) {
const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material);
fallbackMaterialConstants.baseColorFactor = materialData.baseColorFactor;
fallbackMaterialConstants.alphaCutoffParams = Math::Vector4(
materialData.alphaCutoff,
0.0f,
0.0f,
0.0f);
static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantFields[] = {
{
Containers::String("_BaseColor"),
Resources::MaterialPropertyType::Float4,
0,
sizeof(Math::Vector4),
sizeof(Math::Vector4)
},
{
Containers::String("_Cutoff"),
Resources::MaterialPropertyType::Float,
sizeof(Math::Vector4),
sizeof(float),
sizeof(Math::Vector4)
}
};
materialConstants.data = &fallbackMaterialConstants;
materialConstants.size = sizeof(fallbackMaterialConstants);
materialConstants.layout = {
kFallbackMaterialConstantFields,
2,
sizeof(fallbackMaterialConstants)
};
}
RHI::RHIResourceView* baseColorTextureView = ResolveTextureView(visibleItem);
if (passLayout->baseColorTexture.IsValid() && baseColorTextureView == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
(Containers::String("BuiltinDepthStylePassBase failed to resolve base color texture for ") +
visibleItem.gameObject->GetName().c_str()).CStr());
return false;
}
if (passLayout->descriptorSetCount > 0) {
std::vector<RHI::RHIDescriptorSet*> descriptorSets(passLayout->descriptorSetCount, nullptr);
for (Core::uint32 descriptorOffset = 0; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) {
const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset;
if (setIndex >= passLayout->setLayouts.size()) {
return false;
}
const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex];
if (setLayout.layout.bindingCount == 0) {
return false;
}
RHI::RHIDescriptorSet* descriptorSet = nullptr;
if (setLayout.usesPerObject ||
setLayout.usesMaterial ||
setLayout.usesBaseColorTexture) {
const Core::uint64 objectId =
setLayout.usesPerObject ? visibleItem.gameObject->GetID() : 0;
const Resources::Material* materialKey =
(setLayout.usesMaterial || setLayout.usesBaseColorTexture) ? material : nullptr;
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
passLayoutKey,
*passLayout,
setLayout,
setIndex,
objectId,
materialKey,
materialConstants,
baseColorTextureView);
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
(Containers::String("BuiltinDepthStylePassBase failed to allocate dynamic descriptor set for ") +
visibleItem.gameObject->GetName().c_str()).CStr());
return false;
}
descriptorSet = cachedDescriptorSet->descriptorSet.set;
if (setLayout.usesPerObject) {
if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) {
return false;
}
descriptorSet->WriteConstant(
passLayout->perObject.binding,
&constants,
sizeof(constants));
}
} else {
descriptorSet = GetOrCreateStaticDescriptorSet(passLayoutKey, *passLayout, setIndex);
if (descriptorSet == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
(Containers::String("BuiltinDepthStylePassBase failed to allocate static descriptor set for ") +
visibleItem.gameObject->GetName().c_str()).CStr());
return false;
}
}
descriptorSets[descriptorOffset] = descriptorSet;
}
commandList->SetGraphicsDescriptorSets(
passLayout->firstDescriptorSet,
passLayout->descriptorSetCount,
descriptorSets.data(),
passLayout->pipelineLayout);
}
if (visibleItem.hasSection) {
const Containers::Array<Resources::MeshSection>& sections = visibleItem.mesh->GetSections();

View File

@@ -183,7 +183,7 @@ BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfac
if (builtinShaderHandle->IsValid()) {
const Resources::Shader* builtinShader = builtinShaderHandle->Get();
if (const Resources::ShaderPass* shaderPass =
FindCompatibleSurfacePass(*builtinShader, sceneData, nullptr, pass, backend)) {
FindCompatibleSurfacePass(*builtinShader, sceneData, material, pass, backend)) {
resolved.shader = builtinShader;
resolved.pass = shaderPass;
resolved.passName = shaderPass->name;
@@ -705,18 +705,32 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
if (!materialConstants.IsValid()) {
const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material);
fallbackMaterialConstants.baseColorFactor = materialData.baseColorFactor;
static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantField = {
Containers::String("baseColorFactor"),
Resources::MaterialPropertyType::Float4,
0,
sizeof(FallbackPerMaterialConstants),
sizeof(FallbackPerMaterialConstants)
fallbackMaterialConstants.alphaCutoffParams = Math::Vector4(
materialData.alphaCutoff,
0.0f,
0.0f,
0.0f);
static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantFields[] = {
{
Containers::String("_BaseColor"),
Resources::MaterialPropertyType::Float4,
0,
sizeof(Math::Vector4),
sizeof(Math::Vector4)
},
{
Containers::String("_Cutoff"),
Resources::MaterialPropertyType::Float,
sizeof(Math::Vector4),
sizeof(float),
sizeof(Math::Vector4)
}
};
materialConstants.data = &fallbackMaterialConstants;
materialConstants.size = sizeof(fallbackMaterialConstants);
materialConstants.layout = {
&kFallbackMaterialConstantField,
1,
kFallbackMaterialConstantFields,
2,
sizeof(fallbackMaterialConstants)
};
}