Add builtin GaussianSplat forward pass baseline
This commit is contained in:
@@ -508,6 +508,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinSelectionOutlinePass.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/CameraRenderer.cpp
|
||||
@@ -525,6 +526,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinSelectionOutlinePass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinVolumetricPass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderSurface.cpp
|
||||
|
||||
113
engine/assets/builtin/shaders/gaussian-splat.shader
Normal file
113
engine/assets/builtin/shaders/gaussian-splat.shader
Normal file
@@ -0,0 +1,113 @@
|
||||
Shader "Builtin Gaussian Splat"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_PointScale ("Point Scale", Float) = 1.0
|
||||
_OpacityScale ("Opacity Scale", Float) = 1.0
|
||||
}
|
||||
HLSLINCLUDE
|
||||
cbuffer PerObjectConstants
|
||||
{
|
||||
float4x4 gProjectionMatrix;
|
||||
float4x4 gViewMatrix;
|
||||
float4x4 gModelMatrix;
|
||||
float4 gCameraRight;
|
||||
float4 gCameraUp;
|
||||
};
|
||||
|
||||
cbuffer MaterialConstants
|
||||
{
|
||||
float4 gSplatParams;
|
||||
};
|
||||
|
||||
struct GaussianSplatOtherData
|
||||
{
|
||||
float4 rotation;
|
||||
float4 scaleReserved;
|
||||
};
|
||||
|
||||
StructuredBuffer<float3> GaussianSplatPositions;
|
||||
StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther;
|
||||
StructuredBuffer<float4> GaussianSplatColor;
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
float4 position : SV_POSITION;
|
||||
float2 localUv : TEXCOORD0;
|
||||
float4 colorOpacity : TEXCOORD1;
|
||||
};
|
||||
|
||||
float2 ResolveQuadCorner(uint vertexId)
|
||||
{
|
||||
switch (vertexId)
|
||||
{
|
||||
case 0u: return float2(-1.0, -1.0);
|
||||
case 1u: return float2( 1.0, -1.0);
|
||||
case 2u: return float2( 1.0, 1.0);
|
||||
case 3u: return float2(-1.0, -1.0);
|
||||
case 4u: return float2( 1.0, 1.0);
|
||||
default: return float2(-1.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
VSOutput MainVS(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
|
||||
{
|
||||
VSOutput output;
|
||||
const float2 corner = ResolveQuadCorner(vertexId);
|
||||
const float3 localCenter = GaussianSplatPositions[instanceId];
|
||||
const GaussianSplatOtherData otherData = GaussianSplatOther[instanceId];
|
||||
const float4 colorOpacity = GaussianSplatColor[instanceId];
|
||||
|
||||
const float3 worldCenter = mul(gModelMatrix, float4(localCenter, 1.0)).xyz;
|
||||
const float maxAxisScale =
|
||||
max(max(otherData.scaleReserved.x, otherData.scaleReserved.y), otherData.scaleReserved.z);
|
||||
const float radius = max(maxAxisScale * gSplatParams.x, 0.0001);
|
||||
const float3 worldPosition =
|
||||
worldCenter +
|
||||
gCameraRight.xyz * (corner.x * radius) +
|
||||
gCameraUp.xyz * (corner.y * radius);
|
||||
|
||||
output.position = mul(gProjectionMatrix, mul(gViewMatrix, float4(worldPosition, 1.0)));
|
||||
output.localUv = corner;
|
||||
output.colorOpacity = colorOpacity;
|
||||
return output;
|
||||
}
|
||||
|
||||
float4 MainPS(VSOutput input) : SV_TARGET
|
||||
{
|
||||
const float radiusSq = dot(input.localUv, input.localUv);
|
||||
if (radiusSq > 1.0)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
const float gaussianFalloff = exp(-radiusSq * 2.5);
|
||||
const float alpha = saturate(input.colorOpacity.a * gSplatParams.y * gaussianFalloff);
|
||||
if (alpha <= 0.001)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
return float4(input.colorOpacity.rgb, alpha);
|
||||
}
|
||||
ENDHLSL
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags { "Queue" = "Transparent" }
|
||||
Pass
|
||||
{
|
||||
Name "GaussianSplat"
|
||||
Tags { "LightMode" = "GaussianSplat" }
|
||||
Cull Off
|
||||
ZWrite Off
|
||||
ZTest LEqual
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
HLSLPROGRAM
|
||||
#pragma target 4.5
|
||||
#pragma vertex MainVS
|
||||
#pragma fragment MainPS
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,18 @@ inline bool TryBuildBuiltinPassResourceBindingPlan(
|
||||
case BuiltinPassResourceSemantic::VolumeField:
|
||||
location = &outPlan.volumeField;
|
||||
break;
|
||||
case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer:
|
||||
location = &outPlan.gaussianSplatPositionBuffer;
|
||||
break;
|
||||
case BuiltinPassResourceSemantic::GaussianSplatOtherBuffer:
|
||||
location = &outPlan.gaussianSplatOtherBuffer;
|
||||
break;
|
||||
case BuiltinPassResourceSemantic::GaussianSplatColorBuffer:
|
||||
location = &outPlan.gaussianSplatColorBuffer;
|
||||
break;
|
||||
case BuiltinPassResourceSemantic::GaussianSplatSHBuffer:
|
||||
location = &outPlan.gaussianSplatSHBuffer;
|
||||
break;
|
||||
case BuiltinPassResourceSemantic::BaseColorTexture:
|
||||
location = &outPlan.baseColorTexture;
|
||||
break;
|
||||
@@ -334,6 +346,24 @@ inline bool TryBuildBuiltinPassDefaultResourceBindings(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::GaussianSplat)) {
|
||||
AppendBuiltinPassResourceBinding(
|
||||
outBindings,
|
||||
"PerObjectConstants",
|
||||
Resources::ShaderResourceType::ConstantBuffer,
|
||||
0u,
|
||||
0u,
|
||||
"PerObject");
|
||||
AppendBuiltinPassResourceBinding(
|
||||
outBindings,
|
||||
"MaterialConstants",
|
||||
Resources::ShaderResourceType::ConstantBuffer,
|
||||
1u,
|
||||
0u,
|
||||
"Material");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::PostProcess)) {
|
||||
AppendBuiltinPassResourceBinding(
|
||||
outBindings,
|
||||
@@ -570,6 +600,18 @@ inline bool TryBuildBuiltinPassSetLayouts(
|
||||
case BuiltinPassResourceSemantic::VolumeField:
|
||||
setLayout.usesVolumeField = true;
|
||||
break;
|
||||
case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer:
|
||||
setLayout.usesGaussianSplatPositionBuffer = true;
|
||||
break;
|
||||
case BuiltinPassResourceSemantic::GaussianSplatOtherBuffer:
|
||||
setLayout.usesGaussianSplatOtherBuffer = true;
|
||||
break;
|
||||
case BuiltinPassResourceSemantic::GaussianSplatColorBuffer:
|
||||
setLayout.usesGaussianSplatColorBuffer = true;
|
||||
break;
|
||||
case BuiltinPassResourceSemantic::GaussianSplatSHBuffer:
|
||||
setLayout.usesGaussianSplatSHBuffer = true;
|
||||
break;
|
||||
case BuiltinPassResourceSemantic::BaseColorTexture:
|
||||
setLayout.usesTexture = true;
|
||||
setLayout.usesBaseColorTexture = true;
|
||||
|
||||
@@ -27,6 +27,8 @@ inline const char* GetBuiltinPassCanonicalName(BuiltinMaterialPass pass) {
|
||||
return "selectionmask";
|
||||
case BuiltinMaterialPass::Skybox:
|
||||
return "skybox";
|
||||
case BuiltinMaterialPass::GaussianSplat:
|
||||
return "gaussiansplat";
|
||||
case BuiltinMaterialPass::Volumetric:
|
||||
return "volumetric";
|
||||
case BuiltinMaterialPass::PostProcess:
|
||||
@@ -145,6 +147,30 @@ inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic(
|
||||
return BuiltinPassResourceSemantic::VolumeField;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("gaussiansplatpositionbuffer") ||
|
||||
semantic == Containers::String("gaussiansplatpositions") ||
|
||||
semantic == Containers::String("splatpositions")) {
|
||||
return BuiltinPassResourceSemantic::GaussianSplatPositionBuffer;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("gaussiansplatotherbuffer") ||
|
||||
semantic == Containers::String("gaussiansplatother") ||
|
||||
semantic == Containers::String("splatother")) {
|
||||
return BuiltinPassResourceSemantic::GaussianSplatOtherBuffer;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("gaussiansplatcolorbuffer") ||
|
||||
semantic == Containers::String("gaussiansplatcolor") ||
|
||||
semantic == Containers::String("splatcolor")) {
|
||||
return BuiltinPassResourceSemantic::GaussianSplatColorBuffer;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("gaussiansplatshbuffer") ||
|
||||
semantic == Containers::String("gaussiansplatsh") ||
|
||||
semantic == Containers::String("splatsh")) {
|
||||
return BuiltinPassResourceSemantic::GaussianSplatSHBuffer;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("basecolortexture") ||
|
||||
semantic == Containers::String("maintex")) {
|
||||
return BuiltinPassResourceSemantic::BaseColorTexture;
|
||||
@@ -207,6 +233,14 @@ inline const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemant
|
||||
return "PassConstants";
|
||||
case BuiltinPassResourceSemantic::VolumeField:
|
||||
return "VolumeField";
|
||||
case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer:
|
||||
return "GaussianSplatPositionBuffer";
|
||||
case BuiltinPassResourceSemantic::GaussianSplatOtherBuffer:
|
||||
return "GaussianSplatOtherBuffer";
|
||||
case BuiltinPassResourceSemantic::GaussianSplatColorBuffer:
|
||||
return "GaussianSplatColorBuffer";
|
||||
case BuiltinPassResourceSemantic::GaussianSplatSHBuffer:
|
||||
return "GaussianSplatSHBuffer";
|
||||
case BuiltinPassResourceSemantic::BaseColorTexture:
|
||||
return "BaseColorTexture";
|
||||
case BuiltinPassResourceSemantic::SourceColorTexture:
|
||||
@@ -295,6 +329,12 @@ inline bool IsBuiltinPassResourceTypeCompatible(
|
||||
case BuiltinPassResourceSemantic::VolumeField:
|
||||
return type == Resources::ShaderResourceType::StructuredBuffer ||
|
||||
type == Resources::ShaderResourceType::RawBuffer;
|
||||
case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer:
|
||||
case BuiltinPassResourceSemantic::GaussianSplatOtherBuffer:
|
||||
case BuiltinPassResourceSemantic::GaussianSplatColorBuffer:
|
||||
case BuiltinPassResourceSemantic::GaussianSplatSHBuffer:
|
||||
return type == Resources::ShaderResourceType::StructuredBuffer ||
|
||||
type == Resources::ShaderResourceType::RawBuffer;
|
||||
case BuiltinPassResourceSemantic::BaseColorTexture:
|
||||
case BuiltinPassResourceSemantic::SourceColorTexture:
|
||||
case BuiltinPassResourceSemantic::SkyboxPanoramicTexture:
|
||||
|
||||
@@ -20,6 +20,7 @@ enum class BuiltinMaterialPass : Core::uint32 {
|
||||
ObjectId,
|
||||
SelectionMask,
|
||||
Skybox,
|
||||
GaussianSplat,
|
||||
Volumetric,
|
||||
PostProcess,
|
||||
FinalColor,
|
||||
@@ -45,6 +46,11 @@ enum class BuiltinPassResourceSemantic : Core::uint8 {
|
||||
Environment,
|
||||
PassConstants,
|
||||
VolumeField,
|
||||
GaussianSplatOrderBuffer,
|
||||
GaussianSplatPositionBuffer,
|
||||
GaussianSplatOtherBuffer,
|
||||
GaussianSplatColorBuffer,
|
||||
GaussianSplatSHBuffer,
|
||||
BaseColorTexture,
|
||||
SourceColorTexture,
|
||||
SkyboxPanoramicTexture,
|
||||
@@ -78,6 +84,11 @@ struct BuiltinPassResourceBindingPlan {
|
||||
PassResourceBindingLocation shadowReceiver = {};
|
||||
PassResourceBindingLocation environment = {};
|
||||
PassResourceBindingLocation passConstants = {};
|
||||
PassResourceBindingLocation gaussianSplatOrderBuffer = {};
|
||||
PassResourceBindingLocation gaussianSplatPositionBuffer = {};
|
||||
PassResourceBindingLocation gaussianSplatOtherBuffer = {};
|
||||
PassResourceBindingLocation gaussianSplatColorBuffer = {};
|
||||
PassResourceBindingLocation gaussianSplatSHBuffer = {};
|
||||
PassResourceBindingLocation baseColorTexture = {};
|
||||
PassResourceBindingLocation sourceColorTexture = {};
|
||||
PassResourceBindingLocation skyboxPanoramicTexture = {};
|
||||
@@ -111,6 +122,11 @@ struct BuiltinPassSetLayoutMetadata {
|
||||
bool usesPassConstants = false;
|
||||
bool usesMaterialBuffers = false;
|
||||
bool usesVolumeField = false;
|
||||
bool usesGaussianSplatOrderBuffer = false;
|
||||
bool usesGaussianSplatPositionBuffer = false;
|
||||
bool usesGaussianSplatOtherBuffer = false;
|
||||
bool usesGaussianSplatColorBuffer = false;
|
||||
bool usesGaussianSplatSHBuffer = false;
|
||||
bool usesTexture = false;
|
||||
bool usesBaseColorTexture = false;
|
||||
bool usesSourceColorTexture = false;
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Core/Math/Matrix4.h>
|
||||
#include <XCEngine/Rendering/Builtin/BuiltinPassTypes.h>
|
||||
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
||||
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
||||
#include <XCEngine/RHI/RHIPipelineState.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
class GameObject;
|
||||
} // namespace Components
|
||||
|
||||
namespace Resources {
|
||||
class GaussianSplat;
|
||||
class Material;
|
||||
class Shader;
|
||||
} // namespace Resources
|
||||
|
||||
namespace Rendering {
|
||||
struct VisibleGaussianSplatItem;
|
||||
|
||||
namespace Passes {
|
||||
|
||||
class BuiltinGaussianSplatPass final : public RenderPass {
|
||||
public:
|
||||
~BuiltinGaussianSplatPass() override;
|
||||
|
||||
const char* GetName() const override;
|
||||
bool Initialize(const RenderContext& context) override;
|
||||
bool PrepareGaussianSplatResources(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData);
|
||||
bool Execute(const RenderPassContext& context) override;
|
||||
void Shutdown() override;
|
||||
|
||||
private:
|
||||
struct PerObjectConstants {
|
||||
Math::Matrix4x4 projection = Math::Matrix4x4::Identity();
|
||||
Math::Matrix4x4 view = Math::Matrix4x4::Identity();
|
||||
Math::Matrix4x4 model = Math::Matrix4x4::Identity();
|
||||
Math::Vector4 cameraRight = Math::Vector4::Zero();
|
||||
Math::Vector4 cameraUp = Math::Vector4::Zero();
|
||||
};
|
||||
|
||||
struct OwnedDescriptorSet {
|
||||
RHI::RHIDescriptorPool* pool = nullptr;
|
||||
RHI::RHIDescriptorSet* set = nullptr;
|
||||
};
|
||||
|
||||
struct PassLayoutKey {
|
||||
const Resources::Shader* shader = nullptr;
|
||||
Containers::String passName;
|
||||
|
||||
bool operator==(const PassLayoutKey& other) const {
|
||||
return shader == other.shader && passName == other.passName;
|
||||
}
|
||||
};
|
||||
|
||||
struct PassLayoutKeyHash {
|
||||
size_t operator()(const PassLayoutKey& key) const noexcept {
|
||||
size_t hash = reinterpret_cast<size_t>(key.shader);
|
||||
hash ^= std::hash<Containers::String>{}(key.passName) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
struct PassResourceLayout {
|
||||
RHI::RHIPipelineLayout* pipelineLayout = nullptr;
|
||||
Core::uint32 firstDescriptorSet = 0;
|
||||
Core::uint32 descriptorSetCount = 0;
|
||||
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
||||
PassResourceBindingLocation perObject = {};
|
||||
PassResourceBindingLocation material = {};
|
||||
PassResourceBindingLocation gaussianSplatPositionBuffer = {};
|
||||
PassResourceBindingLocation gaussianSplatOtherBuffer = {};
|
||||
PassResourceBindingLocation gaussianSplatColorBuffer = {};
|
||||
PassResourceBindingLocation gaussianSplatSHBuffer = {};
|
||||
};
|
||||
|
||||
struct DynamicDescriptorSetKey {
|
||||
PassLayoutKey passLayout = {};
|
||||
Core::uint32 setIndex = 0;
|
||||
Core::uint64 objectId = 0;
|
||||
const Resources::Material* material = nullptr;
|
||||
const Resources::GaussianSplat* gaussianSplat = nullptr;
|
||||
|
||||
bool operator==(const DynamicDescriptorSetKey& other) const {
|
||||
return passLayout == other.passLayout &&
|
||||
setIndex == other.setIndex &&
|
||||
objectId == other.objectId &&
|
||||
material == other.material &&
|
||||
gaussianSplat == other.gaussianSplat;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
hash ^= reinterpret_cast<size_t>(key.gaussianSplat) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
struct CachedDescriptorSet {
|
||||
OwnedDescriptorSet descriptorSet = {};
|
||||
Core::uint64 materialVersion = 0;
|
||||
RHI::RHIResourceView* positionsView = nullptr;
|
||||
RHI::RHIResourceView* otherView = nullptr;
|
||||
RHI::RHIResourceView* colorView = nullptr;
|
||||
RHI::RHIResourceView* shView = nullptr;
|
||||
};
|
||||
|
||||
struct ResolvedShaderPass {
|
||||
const Resources::Shader* shader = nullptr;
|
||||
const Resources::ShaderPass* pass = nullptr;
|
||||
Containers::String passName;
|
||||
};
|
||||
|
||||
struct PipelineStateKey {
|
||||
Resources::MaterialRenderState renderState;
|
||||
const Resources::Shader* shader = nullptr;
|
||||
Containers::String passName;
|
||||
Containers::String keywordSignature;
|
||||
Core::uint32 renderTargetCount = 0;
|
||||
Core::uint32 renderTargetFormat = 0;
|
||||
Core::uint32 depthStencilFormat = 0;
|
||||
Core::uint32 sampleCount = 1;
|
||||
Core::uint32 sampleQuality = 0;
|
||||
|
||||
bool operator==(const PipelineStateKey& other) const {
|
||||
return renderState == other.renderState &&
|
||||
shader == other.shader &&
|
||||
passName == other.passName &&
|
||||
keywordSignature == other.keywordSignature &&
|
||||
renderTargetCount == other.renderTargetCount &&
|
||||
renderTargetFormat == other.renderTargetFormat &&
|
||||
depthStencilFormat == other.depthStencilFormat &&
|
||||
sampleCount == other.sampleCount &&
|
||||
sampleQuality == other.sampleQuality;
|
||||
}
|
||||
};
|
||||
|
||||
struct PipelineStateKeyHash {
|
||||
size_t operator()(const PipelineStateKey& key) const noexcept {
|
||||
size_t hash = MaterialRenderStateHash()(key.renderState);
|
||||
hash ^= reinterpret_cast<size_t>(key.shader) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Containers::String>{}(key.passName) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Containers::String>{}(key.keywordSignature) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Core::uint32>{}(key.renderTargetCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Core::uint32>{}(key.renderTargetFormat) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Core::uint32>{}(key.depthStencilFormat) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Core::uint32>{}(key.sampleCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Core::uint32>{}(key.sampleQuality) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
bool EnsureInitialized(const RenderContext& context);
|
||||
bool CreateResources(const RenderContext& context);
|
||||
void DestroyResources();
|
||||
|
||||
const Resources::Material* ResolveGaussianSplatMaterial(
|
||||
const VisibleGaussianSplatItem& visibleGaussianSplat) const;
|
||||
ResolvedShaderPass ResolveGaussianSplatShaderPass(
|
||||
const RenderSceneData& sceneData,
|
||||
const Resources::Material* material) const;
|
||||
PassResourceLayout* GetOrCreatePassResourceLayout(
|
||||
const RenderContext& context,
|
||||
const ResolvedShaderPass& resolvedShaderPass);
|
||||
RHI::RHIPipelineState* GetOrCreatePipelineState(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData,
|
||||
const Resources::Material* material);
|
||||
bool CreateOwnedDescriptorSet(
|
||||
const BuiltinPassSetLayoutMetadata& setLayout,
|
||||
OwnedDescriptorSet& descriptorSet);
|
||||
CachedDescriptorSet* GetOrCreateDynamicDescriptorSet(
|
||||
const PassLayoutKey& passLayoutKey,
|
||||
const PassResourceLayout& passLayout,
|
||||
const BuiltinPassSetLayoutMetadata& setLayout,
|
||||
Core::uint32 setIndex,
|
||||
Core::uint64 objectId,
|
||||
const Resources::Material* material,
|
||||
const Resources::GaussianSplat* gaussianSplat,
|
||||
const MaterialConstantPayloadView& materialConstants,
|
||||
const RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat);
|
||||
void DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet);
|
||||
void DestroyPassResourceLayout(PassResourceLayout& passLayout);
|
||||
bool DrawVisibleGaussianSplat(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData,
|
||||
const VisibleGaussianSplatItem& visibleGaussianSplat);
|
||||
|
||||
RHI::RHIDevice* m_device = nullptr;
|
||||
RHI::RHIType m_backendType = RHI::RHIType::D3D12;
|
||||
Resources::ResourceHandle<Resources::Shader> m_builtinGaussianSplatShader;
|
||||
std::unique_ptr<Resources::Material> m_builtinGaussianSplatMaterial;
|
||||
RenderResourceCache m_resourceCache;
|
||||
|
||||
std::unordered_map<PassLayoutKey, PassResourceLayout, PassLayoutKeyHash> m_passResourceLayouts;
|
||||
std::unordered_map<PipelineStateKey, RHI::RHIPipelineState*, PipelineStateKeyHash> m_pipelineStates;
|
||||
std::unordered_map<DynamicDescriptorSetKey, CachedDescriptorSet, DynamicDescriptorSetKeyHash> m_dynamicDescriptorSets;
|
||||
};
|
||||
|
||||
} // namespace Passes
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -39,6 +39,7 @@ namespace Pipelines {
|
||||
} // namespace Pipelines
|
||||
|
||||
namespace Passes {
|
||||
class BuiltinGaussianSplatPass;
|
||||
class BuiltinVolumetricPass;
|
||||
} // namespace Passes
|
||||
|
||||
@@ -355,6 +356,7 @@ private:
|
||||
OwnedDescriptorSet m_skyboxSamplerSet = {};
|
||||
RHI::RHIResourceView* m_skyboxBoundPanoramicTextureView = nullptr;
|
||||
RHI::RHIResourceView* m_skyboxBoundCubemapTextureView = nullptr;
|
||||
std::unique_ptr<Passes::BuiltinGaussianSplatPass> m_gaussianSplatPass;
|
||||
std::unique_ptr<Passes::BuiltinVolumetricPass> m_volumetricPass;
|
||||
};
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ Containers::String GetBuiltinObjectIdOutlineShaderPath();
|
||||
Containers::String GetBuiltinSelectionMaskShaderPath();
|
||||
Containers::String GetBuiltinSelectionOutlineShaderPath();
|
||||
Containers::String GetBuiltinSkyboxShaderPath();
|
||||
Containers::String GetBuiltinGaussianSplatShaderPath();
|
||||
Containers::String GetBuiltinVolumetricShaderPath();
|
||||
Containers::String GetBuiltinColorScalePostProcessShaderPath();
|
||||
Containers::String GetBuiltinFinalColorShaderPath();
|
||||
|
||||
730
engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp
Normal file
730
engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp
Normal file
@@ -0,0 +1,730 @@
|
||||
#include "Rendering/Passes/BuiltinGaussianSplatPass.h"
|
||||
|
||||
#include "Components/GameObject.h"
|
||||
#include "Debug/Logger.h"
|
||||
#include "RHI/RHICommandList.h"
|
||||
#include "RHI/RHIDevice.h"
|
||||
#include "Rendering/Builtin/BuiltinPassLayoutUtils.h"
|
||||
#include "Rendering/FrameData/RenderSceneData.h"
|
||||
#include "Rendering/FrameData/VisibleGaussianSplatItem.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
#include "Resources/BuiltinResources.h"
|
||||
#include "Resources/GaussianSplat/GaussianSplat.h"
|
||||
#include "Resources/Material/Material.h"
|
||||
#include "Resources/Shader/Shader.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
namespace Passes {
|
||||
|
||||
namespace {
|
||||
|
||||
Resources::ShaderKeywordSet ResolvePassKeywordSet(
|
||||
const RenderSceneData& sceneData,
|
||||
const Resources::Material* material) {
|
||||
return Resources::CombineShaderKeywordSets(
|
||||
sceneData.globalShaderKeywords,
|
||||
material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet());
|
||||
}
|
||||
|
||||
const Resources::ShaderPass* FindCompatibleGaussianSplatPass(
|
||||
const Resources::Shader& shader,
|
||||
const RenderSceneData& sceneData,
|
||||
const Resources::Material* material,
|
||||
Resources::ShaderBackend backend) {
|
||||
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
|
||||
for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) {
|
||||
if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::GaussianSplat) &&
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(
|
||||
shader,
|
||||
shaderPass.name,
|
||||
backend,
|
||||
keywordSet)) {
|
||||
return &shaderPass;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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,
|
||||
const RenderSurface& surface) {
|
||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = pipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
::XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
|
||||
pipelineDesc.depthStencilFormat =
|
||||
static_cast<uint32_t>(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface));
|
||||
ApplyResolvedRenderState(&shaderPass, material, pipelineDesc);
|
||||
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
||||
if (const Resources::ShaderStageVariant* vertexVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet)) {
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
shaderPass,
|
||||
backend,
|
||||
*vertexVariant,
|
||||
pipelineDesc.vertexShader);
|
||||
}
|
||||
if (const Resources::ShaderStageVariant* fragmentVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend, keywordSet)) {
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
shaderPass,
|
||||
backend,
|
||||
*fragmentVariant,
|
||||
pipelineDesc.fragmentShader);
|
||||
}
|
||||
|
||||
return pipelineDesc;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BuiltinGaussianSplatPass::~BuiltinGaussianSplatPass() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
const char* BuiltinGaussianSplatPass::GetName() const {
|
||||
return "BuiltinGaussianSplatPass";
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPass::Initialize(const RenderContext& context) {
|
||||
return EnsureInitialized(context);
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPass::PrepareGaussianSplatResources(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData) {
|
||||
if (!EnsureInitialized(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const VisibleGaussianSplatItem& visibleGaussianSplat : sceneData.visibleGaussianSplats) {
|
||||
if (visibleGaussianSplat.gaussianSplat == nullptr ||
|
||||
!visibleGaussianSplat.gaussianSplat->IsValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const RenderResourceCache::CachedGaussianSplat* cachedGaussianSplat =
|
||||
m_resourceCache.GetOrCreateGaussianSplat(m_device, visibleGaussianSplat.gaussianSplat);
|
||||
if (cachedGaussianSplat == nullptr ||
|
||||
cachedGaussianSplat->positions.shaderResourceView == nullptr ||
|
||||
cachedGaussianSplat->other.shaderResourceView == nullptr ||
|
||||
cachedGaussianSplat->color.shaderResourceView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPass::Execute(const RenderPassContext& context) {
|
||||
if (!context.renderContext.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.sceneData.visibleGaussianSplats.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(context.surface) ||
|
||||
colorAttachments.empty() ||
|
||||
colorAttachments[0] == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Math::RectInt renderArea = context.surface.GetRenderArea();
|
||||
if (renderArea.width <= 0 || renderArea.height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!PrepareGaussianSplatResources(context.renderContext, context.sceneData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::RHICommandList* commandList = context.renderContext.commandList;
|
||||
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
||||
commandList->SetRenderTargets(1, &renderTarget, context.surface.GetDepthAttachment());
|
||||
|
||||
const RHI::Viewport viewport = {
|
||||
static_cast<float>(renderArea.x),
|
||||
static_cast<float>(renderArea.y),
|
||||
static_cast<float>(renderArea.width),
|
||||
static_cast<float>(renderArea.height),
|
||||
0.0f,
|
||||
1.0f
|
||||
};
|
||||
const RHI::Rect scissorRect = {
|
||||
renderArea.x,
|
||||
renderArea.y,
|
||||
renderArea.x + renderArea.width,
|
||||
renderArea.y + renderArea.height
|
||||
};
|
||||
commandList->SetViewport(viewport);
|
||||
commandList->SetScissorRect(scissorRect);
|
||||
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||
|
||||
for (const VisibleGaussianSplatItem& visibleGaussianSplat : context.sceneData.visibleGaussianSplats) {
|
||||
if (!DrawVisibleGaussianSplat(
|
||||
context.renderContext,
|
||||
context.surface,
|
||||
context.sceneData,
|
||||
visibleGaussianSplat)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BuiltinGaussianSplatPass::Shutdown() {
|
||||
DestroyResources();
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPass::EnsureInitialized(const RenderContext& context) {
|
||||
if (!context.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_device == context.device &&
|
||||
m_backendType == context.backendType &&
|
||||
m_builtinGaussianSplatShader.IsValid() &&
|
||||
m_builtinGaussianSplatMaterial != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DestroyResources();
|
||||
return CreateResources(context);
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPass::CreateResources(const RenderContext& context) {
|
||||
m_device = context.device;
|
||||
m_backendType = context.backendType;
|
||||
m_builtinGaussianSplatShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
|
||||
Resources::GetBuiltinGaussianSplatShaderPath());
|
||||
if (!m_builtinGaussianSplatShader.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinGaussianSplatPass failed to load builtin gaussian splat shader resource");
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_builtinGaussianSplatMaterial = std::make_unique<Resources::Material>();
|
||||
Resources::IResource::ConstructParams params = {};
|
||||
params.name = "BuiltinGaussianSplatMaterial";
|
||||
params.path = "builtin://materials/gaussian-splat-default";
|
||||
params.guid = Resources::ResourceGUID::Generate(params.path);
|
||||
m_builtinGaussianSplatMaterial->Initialize(params);
|
||||
m_builtinGaussianSplatMaterial->SetShader(m_builtinGaussianSplatShader);
|
||||
m_builtinGaussianSplatMaterial->SetRenderQueue(Resources::MaterialRenderQueue::Transparent);
|
||||
return true;
|
||||
}
|
||||
|
||||
void BuiltinGaussianSplatPass::DestroyResources() {
|
||||
m_resourceCache.Shutdown();
|
||||
|
||||
for (auto& pipelinePair : m_pipelineStates) {
|
||||
if (pipelinePair.second != nullptr) {
|
||||
pipelinePair.second->Shutdown();
|
||||
delete pipelinePair.second;
|
||||
}
|
||||
}
|
||||
m_pipelineStates.clear();
|
||||
|
||||
for (auto& descriptorSetPair : m_dynamicDescriptorSets) {
|
||||
DestroyOwnedDescriptorSet(descriptorSetPair.second.descriptorSet);
|
||||
}
|
||||
m_dynamicDescriptorSets.clear();
|
||||
|
||||
for (auto& passLayoutPair : m_passResourceLayouts) {
|
||||
DestroyPassResourceLayout(passLayoutPair.second);
|
||||
}
|
||||
m_passResourceLayouts.clear();
|
||||
|
||||
m_builtinGaussianSplatMaterial.reset();
|
||||
m_builtinGaussianSplatShader.Reset();
|
||||
m_device = nullptr;
|
||||
m_backendType = RHI::RHIType::D3D12;
|
||||
}
|
||||
|
||||
const Resources::Material* BuiltinGaussianSplatPass::ResolveGaussianSplatMaterial(
|
||||
const VisibleGaussianSplatItem& visibleGaussianSplat) const {
|
||||
if (visibleGaussianSplat.material != nullptr &&
|
||||
visibleGaussianSplat.material->GetShader() != nullptr &&
|
||||
MatchesBuiltinPass(visibleGaussianSplat.material, BuiltinMaterialPass::GaussianSplat)) {
|
||||
return visibleGaussianSplat.material;
|
||||
}
|
||||
|
||||
return m_builtinGaussianSplatMaterial.get();
|
||||
}
|
||||
|
||||
BuiltinGaussianSplatPass::ResolvedShaderPass BuiltinGaussianSplatPass::ResolveGaussianSplatShaderPass(
|
||||
const RenderSceneData& sceneData,
|
||||
const Resources::Material* material) const {
|
||||
ResolvedShaderPass resolved = {};
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType);
|
||||
|
||||
if (material != nullptr && material->GetShader() != nullptr) {
|
||||
const Resources::Shader* shader = material->GetShader();
|
||||
if (const Resources::ShaderPass* shaderPass =
|
||||
FindCompatibleGaussianSplatPass(*shader, sceneData, material, backend)) {
|
||||
resolved.shader = shader;
|
||||
resolved.pass = shaderPass;
|
||||
resolved.passName = shaderPass->name;
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_builtinGaussianSplatShader.IsValid()) {
|
||||
const Resources::Shader* shader = m_builtinGaussianSplatShader.Get();
|
||||
if (const Resources::ShaderPass* shaderPass =
|
||||
FindCompatibleGaussianSplatPass(*shader, sceneData, material, backend)) {
|
||||
resolved.shader = shader;
|
||||
resolved.pass = shaderPass;
|
||||
resolved.passName = shaderPass->name;
|
||||
}
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
BuiltinGaussianSplatPass::PassResourceLayout* BuiltinGaussianSplatPass::GetOrCreatePassResourceLayout(
|
||||
const RenderContext& context,
|
||||
const ResolvedShaderPass& resolvedShaderPass) {
|
||||
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PassLayoutKey passLayoutKey = {};
|
||||
passLayoutKey.shader = resolvedShaderPass.shader;
|
||||
passLayoutKey.passName = resolvedShaderPass.passName;
|
||||
|
||||
const auto existing = m_passResourceLayouts.find(passLayoutKey);
|
||||
if (existing != m_passResourceLayouts.end()) {
|
||||
return &existing->second;
|
||||
}
|
||||
|
||||
PassResourceLayout passLayout = {};
|
||||
auto failLayout = [this, &passLayout](const char* message) -> PassResourceLayout* {
|
||||
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message);
|
||||
DestroyPassResourceLayout(passLayout);
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
BuiltinPassResourceBindingPlan bindingPlan = {};
|
||||
Containers::String bindingPlanError;
|
||||
if (!TryBuildBuiltinPassResourceBindingPlan(*resolvedShaderPass.pass, bindingPlan, &bindingPlanError)) {
|
||||
const Containers::String contextualError =
|
||||
Containers::String("BuiltinGaussianSplatPass failed to resolve pass resource bindings for shader='") +
|
||||
resolvedShaderPass.shader->GetPath() +
|
||||
"', pass='" + resolvedShaderPass.passName +
|
||||
"': " + bindingPlanError +
|
||||
". Bindings: " + DescribeShaderResourceBindings(resolvedShaderPass.pass->resources);
|
||||
return failLayout(contextualError.CStr());
|
||||
}
|
||||
|
||||
Containers::String setLayoutError;
|
||||
if (!TryBuildBuiltinPassSetLayouts(bindingPlan, passLayout.setLayouts, &setLayoutError)) {
|
||||
return failLayout(setLayoutError.CStr());
|
||||
}
|
||||
|
||||
passLayout.firstDescriptorSet = bindingPlan.firstDescriptorSet;
|
||||
passLayout.descriptorSetCount = bindingPlan.descriptorSetCount;
|
||||
passLayout.perObject = bindingPlan.perObject;
|
||||
passLayout.material = bindingPlan.material;
|
||||
passLayout.gaussianSplatPositionBuffer = bindingPlan.gaussianSplatPositionBuffer;
|
||||
passLayout.gaussianSplatOtherBuffer = bindingPlan.gaussianSplatOtherBuffer;
|
||||
passLayout.gaussianSplatColorBuffer = bindingPlan.gaussianSplatColorBuffer;
|
||||
passLayout.gaussianSplatSHBuffer = bindingPlan.gaussianSplatSHBuffer;
|
||||
|
||||
if (!passLayout.perObject.IsValid()) {
|
||||
return failLayout("BuiltinGaussianSplatPass requires a PerObject resource binding");
|
||||
}
|
||||
if (!passLayout.material.IsValid()) {
|
||||
return failLayout("BuiltinGaussianSplatPass requires a Material resource binding");
|
||||
}
|
||||
if (!passLayout.gaussianSplatPositionBuffer.IsValid() ||
|
||||
!passLayout.gaussianSplatOtherBuffer.IsValid() ||
|
||||
!passLayout.gaussianSplatColorBuffer.IsValid()) {
|
||||
return failLayout("BuiltinGaussianSplatPass requires position, other, and color gaussian splat buffer bindings");
|
||||
}
|
||||
|
||||
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 = {};
|
||||
pipelineLayoutDesc.setLayouts = nativeSetLayouts.empty() ? nullptr : nativeSetLayouts.data();
|
||||
pipelineLayoutDesc.setLayoutCount = static_cast<Core::uint32>(nativeSetLayouts.size());
|
||||
passLayout.pipelineLayout = context.device->CreatePipelineLayout(pipelineLayoutDesc);
|
||||
if (passLayout.pipelineLayout == nullptr) {
|
||||
return failLayout("BuiltinGaussianSplatPass failed to create pipeline layout from shader pass resources");
|
||||
}
|
||||
|
||||
const auto result = m_passResourceLayouts.emplace(passLayoutKey, passLayout);
|
||||
PassResourceLayout& storedPassLayout = result.first->second;
|
||||
RefreshBuiltinPassSetLayouts(storedPassLayout.setLayouts);
|
||||
return &storedPassLayout;
|
||||
}
|
||||
|
||||
RHI::RHIPipelineState* BuiltinGaussianSplatPass::GetOrCreatePipelineState(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData,
|
||||
const Resources::Material* material) {
|
||||
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
|
||||
const ResolvedShaderPass resolvedShaderPass = ResolveGaussianSplatShaderPass(sceneData, material);
|
||||
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass);
|
||||
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PipelineStateKey pipelineKey = {};
|
||||
pipelineKey.renderState =
|
||||
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material));
|
||||
pipelineKey.shader = resolvedShaderPass.shader;
|
||||
pipelineKey.passName = resolvedShaderPass.passName;
|
||||
pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet);
|
||||
pipelineKey.renderTargetCount =
|
||||
::XCEngine::Rendering::Internal::HasSingleColorAttachment(surface) ? 1u : 0u;
|
||||
pipelineKey.renderTargetFormat =
|
||||
static_cast<Core::uint32>(::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u));
|
||||
pipelineKey.depthStencilFormat =
|
||||
static_cast<Core::uint32>(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface));
|
||||
pipelineKey.sampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
pipelineKey.sampleQuality = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
||||
|
||||
const auto existing = m_pipelineStates.find(pipelineKey);
|
||||
if (existing != m_pipelineStates.end()) {
|
||||
return existing->second;
|
||||
}
|
||||
|
||||
const RHI::GraphicsPipelineDesc pipelineDesc =
|
||||
CreatePipelineDesc(
|
||||
context.backendType,
|
||||
passLayout->pipelineLayout,
|
||||
*resolvedShaderPass.shader,
|
||||
*resolvedShaderPass.pass,
|
||||
resolvedShaderPass.passName,
|
||||
keywordSet,
|
||||
material,
|
||||
surface);
|
||||
RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc);
|
||||
if (pipelineState == nullptr || !pipelineState->IsValid()) {
|
||||
if (pipelineState != nullptr) {
|
||||
pipelineState->Shutdown();
|
||||
delete pipelineState;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_pipelineStates.emplace(pipelineKey, pipelineState);
|
||||
return pipelineState;
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPass::CreateOwnedDescriptorSet(
|
||||
const BuiltinPassSetLayoutMetadata& setLayout,
|
||||
OwnedDescriptorSet& descriptorSet) {
|
||||
RHI::DescriptorPoolDesc poolDesc = {};
|
||||
poolDesc.type = setLayout.heapType;
|
||||
poolDesc.descriptorCount = CountBuiltinPassHeapDescriptors(setLayout.heapType, setLayout.bindings);
|
||||
poolDesc.shaderVisible = setLayout.shaderVisible;
|
||||
|
||||
descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc);
|
||||
if (descriptorSet.pool == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
descriptorSet.set = descriptorSet.pool->AllocateSet(setLayout.layout);
|
||||
if (descriptorSet.set == nullptr) {
|
||||
DestroyOwnedDescriptorSet(descriptorSet);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCreateDynamicDescriptorSet(
|
||||
const PassLayoutKey& passLayoutKey,
|
||||
const PassResourceLayout& passLayout,
|
||||
const BuiltinPassSetLayoutMetadata& setLayout,
|
||||
Core::uint32 setIndex,
|
||||
Core::uint64 objectId,
|
||||
const Resources::Material* material,
|
||||
const Resources::GaussianSplat* gaussianSplat,
|
||||
const MaterialConstantPayloadView& materialConstants,
|
||||
const RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat) {
|
||||
DynamicDescriptorSetKey key = {};
|
||||
key.passLayout = passLayoutKey;
|
||||
key.setIndex = setIndex;
|
||||
key.objectId = objectId;
|
||||
key.material = material;
|
||||
key.gaussianSplat = gaussianSplat;
|
||||
|
||||
CachedDescriptorSet& cachedDescriptorSet = m_dynamicDescriptorSets[key];
|
||||
if (cachedDescriptorSet.descriptorSet.set == nullptr) {
|
||||
if (!CreateOwnedDescriptorSet(setLayout, cachedDescriptorSet.descriptorSet)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const Core::uint64 materialVersion = material != nullptr ? material->GetChangeVersion() : 0u;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (setLayout.usesGaussianSplatPositionBuffer) {
|
||||
if (cachedGaussianSplat.positions.shaderResourceView == nullptr ||
|
||||
!passLayout.gaussianSplatPositionBuffer.IsValid() ||
|
||||
passLayout.gaussianSplatPositionBuffer.set != setIndex) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (cachedDescriptorSet.positionsView != cachedGaussianSplat.positions.shaderResourceView) {
|
||||
cachedDescriptorSet.descriptorSet.set->Update(
|
||||
passLayout.gaussianSplatPositionBuffer.binding,
|
||||
cachedGaussianSplat.positions.shaderResourceView);
|
||||
}
|
||||
}
|
||||
|
||||
if (setLayout.usesGaussianSplatOtherBuffer) {
|
||||
if (cachedGaussianSplat.other.shaderResourceView == nullptr ||
|
||||
!passLayout.gaussianSplatOtherBuffer.IsValid() ||
|
||||
passLayout.gaussianSplatOtherBuffer.set != setIndex) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (cachedDescriptorSet.otherView != cachedGaussianSplat.other.shaderResourceView) {
|
||||
cachedDescriptorSet.descriptorSet.set->Update(
|
||||
passLayout.gaussianSplatOtherBuffer.binding,
|
||||
cachedGaussianSplat.other.shaderResourceView);
|
||||
}
|
||||
}
|
||||
|
||||
if (setLayout.usesGaussianSplatColorBuffer) {
|
||||
if (cachedGaussianSplat.color.shaderResourceView == nullptr ||
|
||||
!passLayout.gaussianSplatColorBuffer.IsValid() ||
|
||||
passLayout.gaussianSplatColorBuffer.set != setIndex) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (cachedDescriptorSet.colorView != cachedGaussianSplat.color.shaderResourceView) {
|
||||
cachedDescriptorSet.descriptorSet.set->Update(
|
||||
passLayout.gaussianSplatColorBuffer.binding,
|
||||
cachedGaussianSplat.color.shaderResourceView);
|
||||
}
|
||||
}
|
||||
|
||||
if (setLayout.usesGaussianSplatSHBuffer) {
|
||||
if (cachedGaussianSplat.sh.shaderResourceView == nullptr ||
|
||||
!passLayout.gaussianSplatSHBuffer.IsValid() ||
|
||||
passLayout.gaussianSplatSHBuffer.set != setIndex) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (cachedDescriptorSet.shView != cachedGaussianSplat.sh.shaderResourceView) {
|
||||
cachedDescriptorSet.descriptorSet.set->Update(
|
||||
passLayout.gaussianSplatSHBuffer.binding,
|
||||
cachedGaussianSplat.sh.shaderResourceView);
|
||||
}
|
||||
}
|
||||
|
||||
cachedDescriptorSet.materialVersion = materialVersion;
|
||||
cachedDescriptorSet.positionsView = cachedGaussianSplat.positions.shaderResourceView;
|
||||
cachedDescriptorSet.otherView = cachedGaussianSplat.other.shaderResourceView;
|
||||
cachedDescriptorSet.colorView = cachedGaussianSplat.color.shaderResourceView;
|
||||
cachedDescriptorSet.shView = cachedGaussianSplat.sh.shaderResourceView;
|
||||
return &cachedDescriptorSet;
|
||||
}
|
||||
|
||||
void BuiltinGaussianSplatPass::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) {
|
||||
if (descriptorSet.set != nullptr) {
|
||||
descriptorSet.set->Shutdown();
|
||||
delete descriptorSet.set;
|
||||
descriptorSet.set = nullptr;
|
||||
}
|
||||
|
||||
if (descriptorSet.pool != nullptr) {
|
||||
descriptorSet.pool->Shutdown();
|
||||
delete descriptorSet.pool;
|
||||
descriptorSet.pool = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void BuiltinGaussianSplatPass::DestroyPassResourceLayout(PassResourceLayout& passLayout) {
|
||||
if (passLayout.pipelineLayout != nullptr) {
|
||||
passLayout.pipelineLayout->Shutdown();
|
||||
delete passLayout.pipelineLayout;
|
||||
passLayout.pipelineLayout = nullptr;
|
||||
}
|
||||
|
||||
passLayout.setLayouts.clear();
|
||||
passLayout.firstDescriptorSet = 0;
|
||||
passLayout.descriptorSetCount = 0;
|
||||
passLayout.perObject = {};
|
||||
passLayout.material = {};
|
||||
passLayout.gaussianSplatPositionBuffer = {};
|
||||
passLayout.gaussianSplatOtherBuffer = {};
|
||||
passLayout.gaussianSplatColorBuffer = {};
|
||||
passLayout.gaussianSplatSHBuffer = {};
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData,
|
||||
const VisibleGaussianSplatItem& visibleGaussianSplat) {
|
||||
if (visibleGaussianSplat.gameObject == nullptr ||
|
||||
visibleGaussianSplat.gaussianSplat == nullptr ||
|
||||
!visibleGaussianSplat.gaussianSplat->IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const RenderResourceCache::CachedGaussianSplat* cachedGaussianSplat =
|
||||
m_resourceCache.GetOrCreateGaussianSplat(m_device, visibleGaussianSplat.gaussianSplat);
|
||||
if (cachedGaussianSplat == nullptr ||
|
||||
cachedGaussianSplat->positions.shaderResourceView == nullptr ||
|
||||
cachedGaussianSplat->other.shaderResourceView == nullptr ||
|
||||
cachedGaussianSplat->color.shaderResourceView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Resources::Material* material = ResolveGaussianSplatMaterial(visibleGaussianSplat);
|
||||
if (material == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ResolvedShaderPass resolvedShaderPass = ResolveGaussianSplatShaderPass(sceneData, material);
|
||||
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PassLayoutKey passLayoutKey = {};
|
||||
passLayoutKey.shader = resolvedShaderPass.shader;
|
||||
passLayoutKey.passName = resolvedShaderPass.passName;
|
||||
|
||||
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass);
|
||||
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material);
|
||||
if (passLayout == nullptr || pipelineState == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material);
|
||||
if (!materialConstants.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::RHICommandList* commandList = context.commandList;
|
||||
commandList->SetPipelineState(pipelineState);
|
||||
|
||||
const PerObjectConstants perObjectConstants = {
|
||||
sceneData.cameraData.projection,
|
||||
sceneData.cameraData.view,
|
||||
visibleGaussianSplat.localToWorld.Transpose(),
|
||||
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
||||
Math::Vector4(sceneData.cameraData.worldUp, 0.0f)
|
||||
};
|
||||
|
||||
if (passLayout->descriptorSetCount > 0u) {
|
||||
std::vector<RHI::RHIDescriptorSet*> descriptorSets(passLayout->descriptorSetCount, nullptr);
|
||||
for (Core::uint32 descriptorOffset = 0u; 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.usesPerObject ||
|
||||
setLayout.usesMaterial ||
|
||||
setLayout.usesGaussianSplatPositionBuffer ||
|
||||
setLayout.usesGaussianSplatOtherBuffer ||
|
||||
setLayout.usesGaussianSplatColorBuffer ||
|
||||
setLayout.usesGaussianSplatSHBuffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Core::uint64 objectId =
|
||||
setLayout.usesPerObject ? visibleGaussianSplat.gameObject->GetID() : 0u;
|
||||
const Resources::Material* materialKey =
|
||||
setLayout.usesMaterial ? material : nullptr;
|
||||
const Resources::GaussianSplat* gaussianSplatKey =
|
||||
(setLayout.usesGaussianSplatPositionBuffer ||
|
||||
setLayout.usesGaussianSplatOtherBuffer ||
|
||||
setLayout.usesGaussianSplatColorBuffer ||
|
||||
setLayout.usesGaussianSplatSHBuffer)
|
||||
? visibleGaussianSplat.gaussianSplat
|
||||
: nullptr;
|
||||
|
||||
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
|
||||
passLayoutKey,
|
||||
*passLayout,
|
||||
setLayout,
|
||||
setIndex,
|
||||
objectId,
|
||||
materialKey,
|
||||
gaussianSplatKey,
|
||||
materialConstants,
|
||||
*cachedGaussianSplat);
|
||||
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::RHIDescriptorSet* descriptorSet = cachedDescriptorSet->descriptorSet.set;
|
||||
if (setLayout.usesPerObject) {
|
||||
if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
descriptorSet->WriteConstant(
|
||||
passLayout->perObject.binding,
|
||||
&perObjectConstants,
|
||||
sizeof(perObjectConstants));
|
||||
}
|
||||
|
||||
descriptorSets[descriptorOffset] = descriptorSet;
|
||||
}
|
||||
|
||||
commandList->SetGraphicsDescriptorSets(
|
||||
passLayout->firstDescriptorSet,
|
||||
passLayout->descriptorSetCount,
|
||||
descriptorSets.data(),
|
||||
passLayout->pipelineLayout);
|
||||
}
|
||||
|
||||
ApplyDynamicRenderState(ResolveEffectiveRenderState(resolvedShaderPass.pass, material), *commandList);
|
||||
commandList->Draw(6u, cachedGaussianSplat->splatCount, 0u, 0u);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Passes
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Core/Asset/ResourceManager.h"
|
||||
#include "RHI/RHICommandList.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Passes/BuiltinGaussianSplatPass.h"
|
||||
#include "Rendering/Materials/RenderMaterialResolve.h"
|
||||
#include "Rendering/Passes/BuiltinVolumetricPass.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
@@ -66,6 +67,7 @@ std::vector<RHI::RHIResourceView*> CollectSurfaceColorAttachments(const RenderSu
|
||||
} // namespace
|
||||
|
||||
BuiltinForwardPipeline::BuiltinForwardPipeline() {
|
||||
m_gaussianSplatPass = std::make_unique<Passes::BuiltinGaussianSplatPass>();
|
||||
m_volumetricPass = std::make_unique<Passes::BuiltinVolumetricPass>();
|
||||
}
|
||||
|
||||
@@ -109,11 +111,16 @@ RHI::InputLayoutDesc BuiltinForwardPipeline::BuildInputLayout() {
|
||||
|
||||
bool BuiltinForwardPipeline::Initialize(const RenderContext& context) {
|
||||
return EnsureInitialized(context) &&
|
||||
m_gaussianSplatPass != nullptr &&
|
||||
m_gaussianSplatPass->Initialize(context) &&
|
||||
m_volumetricPass != nullptr &&
|
||||
m_volumetricPass->Initialize(context);
|
||||
}
|
||||
|
||||
void BuiltinForwardPipeline::Shutdown() {
|
||||
if (m_gaussianSplatPass != nullptr) {
|
||||
m_gaussianSplatPass->Shutdown();
|
||||
}
|
||||
if (m_volumetricPass != nullptr) {
|
||||
m_volumetricPass->Shutdown();
|
||||
}
|
||||
@@ -128,6 +135,17 @@ bool BuiltinForwardPipeline::Render(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_volumetricPass != nullptr &&
|
||||
!sceneData.visibleVolumes.empty() &&
|
||||
!m_volumetricPass->PrepareVolumeResources(context, sceneData)) {
|
||||
return false;
|
||||
}
|
||||
if (m_gaussianSplatPass != nullptr &&
|
||||
!sceneData.visibleGaussianSplats.empty() &&
|
||||
!m_gaussianSplatPass->PrepareGaussianSplatResources(context, sceneData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const RenderPassContext passContext = {
|
||||
context,
|
||||
surface,
|
||||
@@ -150,6 +168,9 @@ bool BuiltinForwardPipeline::Render(
|
||||
if (renderResult) {
|
||||
renderResult = ExecuteForwardSkyboxPass(passContext);
|
||||
}
|
||||
if (renderResult && m_gaussianSplatPass != nullptr) {
|
||||
renderResult = m_gaussianSplatPass->Execute(passContext);
|
||||
}
|
||||
if (renderResult && m_volumetricPass != nullptr) {
|
||||
renderResult = m_volumetricPass->Execute(passContext);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ constexpr const char* kBuiltinObjectIdOutlineShaderPath = "builtin://shaders/obj
|
||||
constexpr const char* kBuiltinSelectionMaskShaderPath = "builtin://shaders/selection-mask";
|
||||
constexpr const char* kBuiltinSelectionOutlineShaderPath = "builtin://shaders/selection-outline";
|
||||
constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox";
|
||||
constexpr const char* kBuiltinGaussianSplatShaderPath = "builtin://shaders/gaussian-splat";
|
||||
constexpr const char* kBuiltinVolumetricShaderPath = "builtin://shaders/volumetric";
|
||||
constexpr const char* kBuiltinColorScalePostProcessShaderPath =
|
||||
"builtin://shaders/color-scale-post-process";
|
||||
@@ -68,6 +69,8 @@ constexpr const char* kBuiltinSelectionOutlineShaderAssetRelativePath =
|
||||
"engine/assets/builtin/shaders/selection-outline.shader";
|
||||
constexpr const char* kBuiltinSkyboxShaderAssetRelativePath =
|
||||
"engine/assets/builtin/shaders/skybox.shader";
|
||||
constexpr const char* kBuiltinGaussianSplatShaderAssetRelativePath =
|
||||
"engine/assets/builtin/shaders/gaussian-splat.shader";
|
||||
constexpr const char* kBuiltinVolumetricShaderAssetRelativePath =
|
||||
"engine/assets/builtin/shaders/volumetric.shader";
|
||||
constexpr const char* kBuiltinColorScalePostProcessShaderAssetRelativePath =
|
||||
@@ -164,6 +167,9 @@ const char* GetBuiltinShaderAssetRelativePath(const Containers::String& builtinS
|
||||
if (builtinShaderPath == Containers::String(kBuiltinSkyboxShaderPath)) {
|
||||
return kBuiltinSkyboxShaderAssetRelativePath;
|
||||
}
|
||||
if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatShaderPath)) {
|
||||
return kBuiltinGaussianSplatShaderAssetRelativePath;
|
||||
}
|
||||
if (builtinShaderPath == Containers::String(kBuiltinVolumetricShaderPath)) {
|
||||
return kBuiltinVolumetricShaderAssetRelativePath;
|
||||
}
|
||||
@@ -734,6 +740,10 @@ Shader* BuildBuiltinSkyboxShader(const Containers::String& path) {
|
||||
return TryLoadBuiltinShaderFromAsset(path);
|
||||
}
|
||||
|
||||
Shader* BuildBuiltinGaussianSplatShader(const Containers::String& path) {
|
||||
return TryLoadBuiltinShaderFromAsset(path);
|
||||
}
|
||||
|
||||
Shader* BuildBuiltinVolumetricShader(const Containers::String& path) {
|
||||
return TryLoadBuiltinShaderFromAsset(path);
|
||||
}
|
||||
@@ -852,6 +862,10 @@ bool TryGetBuiltinShaderPathByShaderName(
|
||||
outPath = GetBuiltinSkyboxShaderPath();
|
||||
return true;
|
||||
}
|
||||
if (shaderName == "Builtin Gaussian Splat") {
|
||||
outPath = GetBuiltinGaussianSplatShaderPath();
|
||||
return true;
|
||||
}
|
||||
if (shaderName == "Builtin Volumetric") {
|
||||
outPath = GetBuiltinVolumetricShaderPath();
|
||||
return true;
|
||||
@@ -939,6 +953,10 @@ Containers::String GetBuiltinSkyboxShaderPath() {
|
||||
return Containers::String(kBuiltinSkyboxShaderPath);
|
||||
}
|
||||
|
||||
Containers::String GetBuiltinGaussianSplatShaderPath() {
|
||||
return Containers::String(kBuiltinGaussianSplatShaderPath);
|
||||
}
|
||||
|
||||
Containers::String GetBuiltinVolumetricShaderPath() {
|
||||
return Containers::String(kBuiltinVolumetricShaderPath);
|
||||
}
|
||||
@@ -1057,6 +1075,8 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) {
|
||||
shader = BuildBuiltinSelectionOutlineShader(path);
|
||||
} else if (path == GetBuiltinSkyboxShaderPath()) {
|
||||
shader = BuildBuiltinSkyboxShader(path);
|
||||
} else if (path == GetBuiltinGaussianSplatShaderPath()) {
|
||||
shader = BuildBuiltinGaussianSplatShader(path);
|
||||
} else if (path == GetBuiltinVolumetricShaderPath()) {
|
||||
shader = BuildBuiltinVolumetricShader(path);
|
||||
} else if (path == GetBuiltinColorScalePostProcessShaderPath()) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Rendering/Detail/ShaderVariantUtils.h"
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
|
||||
#include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h>
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include <XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h>
|
||||
#include <XCEngine/Rendering/Passes/BuiltinObjectIdPass.h>
|
||||
#include <XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h>
|
||||
#include <XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
@@ -87,6 +89,46 @@ void AppendDefaultBuiltinPassResources(ShaderPass& pass) {
|
||||
}
|
||||
}
|
||||
|
||||
class TestResourceView final : public XCEngine::RHI::RHIResourceView {
|
||||
public:
|
||||
TestResourceView(
|
||||
XCEngine::RHI::ResourceViewType viewType,
|
||||
XCEngine::RHI::ResourceViewDimension dimension,
|
||||
XCEngine::RHI::Format format)
|
||||
: m_viewType(viewType),
|
||||
m_dimension(dimension),
|
||||
m_format(format) {
|
||||
}
|
||||
|
||||
void Shutdown() override {
|
||||
}
|
||||
|
||||
void* GetNativeHandle() override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IsValid() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
XCEngine::RHI::ResourceViewType GetViewType() const override {
|
||||
return m_viewType;
|
||||
}
|
||||
|
||||
XCEngine::RHI::ResourceViewDimension GetDimension() const override {
|
||||
return m_dimension;
|
||||
}
|
||||
|
||||
XCEngine::RHI::Format GetFormat() const override {
|
||||
return m_format;
|
||||
}
|
||||
|
||||
private:
|
||||
XCEngine::RHI::ResourceViewType m_viewType;
|
||||
XCEngine::RHI::ResourceViewDimension m_dimension;
|
||||
XCEngine::RHI::Format m_format;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
|
||||
@@ -116,6 +158,162 @@ TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVert
|
||||
EXPECT_EQ(texcoord.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, uv0)));
|
||||
}
|
||||
|
||||
TEST(RenderSurfacePipelineUtils_Test, ResolvesContiguousSurfaceAttachmentFormatsIntoPipelineDesc) {
|
||||
TestResourceView color0(
|
||||
ResourceViewType::RenderTarget,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::R16G16B16A16_Float);
|
||||
TestResourceView color1(
|
||||
ResourceViewType::RenderTarget,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::R8G8B8A8_UNorm);
|
||||
TestResourceView depth(
|
||||
ResourceViewType::DepthStencil,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::D32_Float);
|
||||
|
||||
RenderSurface surface(1280u, 720u);
|
||||
surface.SetColorAttachments({ &color0, &color1 });
|
||||
surface.SetDepthAttachment(&depth);
|
||||
surface.SetSampleDesc(4u, 0u);
|
||||
|
||||
GraphicsPipelineDesc pipelineDesc = {};
|
||||
XCEngine::Rendering::Internal::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
|
||||
|
||||
EXPECT_EQ(pipelineDesc.renderTargetCount, 2u);
|
||||
EXPECT_EQ(static_cast<Format>(pipelineDesc.renderTargetFormats[0]), Format::R16G16B16A16_Float);
|
||||
EXPECT_EQ(static_cast<Format>(pipelineDesc.renderTargetFormats[1]), Format::R8G8B8A8_UNorm);
|
||||
EXPECT_EQ(static_cast<Format>(pipelineDesc.depthStencilFormat), Format::D32_Float);
|
||||
EXPECT_EQ(pipelineDesc.sampleCount, 4u);
|
||||
EXPECT_EQ(pipelineDesc.sampleQuality, 0u);
|
||||
}
|
||||
|
||||
TEST(RenderSurfacePipelineUtils_Test, PropagatesSampleQualityIntoPipelineDesc) {
|
||||
TestResourceView color0(
|
||||
ResourceViewType::RenderTarget,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::R8G8B8A8_UNorm);
|
||||
|
||||
RenderSurface surface(640u, 360u);
|
||||
surface.SetColorAttachment(&color0);
|
||||
surface.SetSampleDesc(4u, 3u);
|
||||
|
||||
GraphicsPipelineDesc pipelineDesc = {};
|
||||
XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
|
||||
|
||||
EXPECT_EQ(pipelineDesc.renderTargetCount, 1u);
|
||||
EXPECT_EQ(pipelineDesc.sampleCount, 4u);
|
||||
EXPECT_EQ(pipelineDesc.sampleQuality, 3u);
|
||||
}
|
||||
|
||||
TEST(RenderSurfacePipelineUtils_Test, StopsAtFirstNullColorAttachmentWhenResolvingFormats) {
|
||||
TestResourceView color0(
|
||||
ResourceViewType::RenderTarget,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::R16G16B16A16_Float);
|
||||
TestResourceView color2(
|
||||
ResourceViewType::RenderTarget,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::R8G8B8A8_UNorm);
|
||||
|
||||
RenderSurface surface(1280u, 720u);
|
||||
surface.SetColorAttachments({ &color0, nullptr, &color2 });
|
||||
|
||||
const uint32_t colorAttachmentCount =
|
||||
XCEngine::Rendering::Internal::ResolveSurfaceColorAttachmentCount(surface);
|
||||
const auto colorFormats =
|
||||
XCEngine::Rendering::Internal::ResolveSurfaceColorFormats(surface);
|
||||
|
||||
EXPECT_EQ(colorAttachmentCount, 1u);
|
||||
EXPECT_EQ(static_cast<Format>(colorFormats[0]), Format::R16G16B16A16_Float);
|
||||
EXPECT_EQ(static_cast<Format>(colorFormats[1]), Format::Unknown);
|
||||
}
|
||||
|
||||
TEST(RenderSurfacePipelineUtils_Test, NormalizesInvalidSampleCountToSingleSample) {
|
||||
RenderSurface surface(640u, 480u);
|
||||
surface.SetSampleDesc(0u, 7u);
|
||||
|
||||
EXPECT_EQ(surface.GetSampleCount(), 1u);
|
||||
EXPECT_EQ(surface.GetSampleQuality(), 0u);
|
||||
EXPECT_EQ(XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface), 1u);
|
||||
}
|
||||
|
||||
TEST(RenderSurfacePipelineUtils_Test, AcceptsDepthStyleSurfaceWithDepthOnlyOutput) {
|
||||
TestResourceView depth(
|
||||
ResourceViewType::DepthStencil,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::D32_Float);
|
||||
|
||||
RenderSurface surface(640u, 480u);
|
||||
surface.SetDepthAttachment(&depth);
|
||||
surface.SetSampleDesc(1u, 0u);
|
||||
|
||||
EXPECT_TRUE(XCEngine::Rendering::Internal::IsDepthStyleCompatibleSurface(surface));
|
||||
}
|
||||
|
||||
TEST(RenderSurfacePipelineUtils_Test, RejectsDepthStyleSurfaceWithoutKnownDepthFormat) {
|
||||
TestResourceView color(
|
||||
ResourceViewType::RenderTarget,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::R8G8B8A8_UNorm);
|
||||
|
||||
RenderSurface surface(640u, 480u);
|
||||
surface.SetColorAttachment(&color);
|
||||
|
||||
EXPECT_FALSE(XCEngine::Rendering::Internal::IsDepthStyleCompatibleSurface(surface));
|
||||
}
|
||||
|
||||
TEST(RenderSurfacePipelineUtils_Test, RejectsDepthStyleSurfaceWithUnknownSingleColorFormat) {
|
||||
TestResourceView color(
|
||||
ResourceViewType::RenderTarget,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::Unknown);
|
||||
TestResourceView depth(
|
||||
ResourceViewType::DepthStencil,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::D32_Float);
|
||||
|
||||
RenderSurface surface(640u, 480u);
|
||||
surface.SetColorAttachment(&color);
|
||||
surface.SetDepthAttachment(&depth);
|
||||
|
||||
EXPECT_FALSE(XCEngine::Rendering::Internal::IsDepthStyleCompatibleSurface(surface));
|
||||
}
|
||||
|
||||
TEST(RenderSurfacePipelineUtils_Test, RejectsDepthStyleSurfaceWithMultipleColorAttachments) {
|
||||
TestResourceView color0(
|
||||
ResourceViewType::RenderTarget,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::R16G16B16A16_Float);
|
||||
TestResourceView color1(
|
||||
ResourceViewType::RenderTarget,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::R8G8B8A8_UNorm);
|
||||
TestResourceView depth(
|
||||
ResourceViewType::DepthStencil,
|
||||
ResourceViewDimension::Texture2D,
|
||||
Format::D32_Float);
|
||||
|
||||
RenderSurface surface(640u, 480u);
|
||||
surface.SetColorAttachments({ &color0, &color1 });
|
||||
surface.SetDepthAttachment(&depth);
|
||||
|
||||
EXPECT_FALSE(XCEngine::Rendering::Internal::IsDepthStyleCompatibleSurface(surface));
|
||||
}
|
||||
|
||||
TEST(RenderSurface_Test, DefaultsDepthStateToDepthWriteAndSupportsOverrides) {
|
||||
RenderSurface surface(640u, 480u);
|
||||
|
||||
EXPECT_EQ(surface.GetDepthStateBefore(), ResourceStates::DepthWrite);
|
||||
EXPECT_EQ(surface.GetDepthStateAfter(), ResourceStates::DepthWrite);
|
||||
|
||||
surface.SetDepthStateBefore(ResourceStates::Common);
|
||||
surface.SetDepthStateAfter(ResourceStates::PixelShaderResource);
|
||||
|
||||
EXPECT_EQ(surface.GetDepthStateBefore(), ResourceStates::Common);
|
||||
EXPECT_EQ(surface.GetDepthStateAfter(), ResourceStates::PixelShaderResource);
|
||||
}
|
||||
|
||||
TEST(BuiltinForwardPipeline_Test, SplitsSceneItemsIntoOpaqueAndTransparentQueueRanges) {
|
||||
EXPECT_FALSE(IsTransparentRenderQueue(static_cast<int>(MaterialRenderQueue::Background)));
|
||||
EXPECT_FALSE(IsTransparentRenderQueue(static_cast<int>(MaterialRenderQueue::Geometry)));
|
||||
@@ -207,7 +405,7 @@ TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderBuildsVulkanRuntimeSourceWit
|
||||
ASSERT_NE(fragmentVariant, nullptr);
|
||||
|
||||
const std::string runtimeSource =
|
||||
::XCEngine::Rendering::Detail::BuildRuntimeShaderSource(
|
||||
::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::Vulkan,
|
||||
*fragmentVariant);
|
||||
@@ -243,7 +441,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringUnlit
|
||||
ASSERT_NE(d3d12Fragment, nullptr);
|
||||
|
||||
ShaderCompileDesc d3d12CompileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::D3D12,
|
||||
*d3d12Fragment,
|
||||
@@ -271,7 +469,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringUnlit
|
||||
ASSERT_NE(vulkanFragment, nullptr);
|
||||
|
||||
ShaderCompileDesc vulkanCompileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::Vulkan,
|
||||
*vulkanFragment,
|
||||
@@ -314,7 +512,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringForwa
|
||||
ASSERT_NE(d3d12Fragment, nullptr);
|
||||
|
||||
ShaderCompileDesc d3d12CompileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::D3D12,
|
||||
*d3d12Fragment,
|
||||
@@ -365,7 +563,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringForwa
|
||||
ASSERT_NE(vulkanFragment, nullptr);
|
||||
|
||||
ShaderCompileDesc vulkanCompileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::Vulkan,
|
||||
*vulkanFragment,
|
||||
@@ -432,7 +630,7 @@ TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesForwardShadowVariantToL
|
||||
ASSERT_NE(openGLFragment, nullptr);
|
||||
|
||||
ShaderCompileDesc compileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::OpenGL,
|
||||
*openGLFragment,
|
||||
@@ -509,7 +707,7 @@ TEST(BuiltinDepthStylePass_Test, OpenGLRuntimeTranspilesDepthOnlyAlphaVariantWit
|
||||
const ShaderStageVariant& variant,
|
||||
std::string& glslSource) {
|
||||
ShaderCompileDesc compileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
targetPass,
|
||||
XCEngine::Resources::ShaderBackend::OpenGL,
|
||||
variant,
|
||||
@@ -573,7 +771,7 @@ TEST(BuiltinDepthStylePass_Test, OpenGLRuntimeTranspilesShadowCasterAlphaVariant
|
||||
const ShaderStageVariant& variant,
|
||||
std::string& glslSource) {
|
||||
ShaderCompileDesc compileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
targetPass,
|
||||
XCEngine::Resources::ShaderBackend::OpenGL,
|
||||
variant,
|
||||
@@ -676,6 +874,219 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatShaderUsesAuthoringContract) {
|
||||
ShaderLoader loader;
|
||||
LoadResult result = loader.Load(GetBuiltinGaussianSplatShaderPath());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
Shader* shader = static_cast<Shader*>(result.resource);
|
||||
ASSERT_NE(shader, nullptr);
|
||||
|
||||
const ShaderPass* pass = shader->FindPass("GaussianSplat");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
EXPECT_EQ(pass->resources.Size(), 5u);
|
||||
EXPECT_TRUE(pass->hasFixedFunctionState);
|
||||
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
|
||||
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
|
||||
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
|
||||
EXPECT_TRUE(pass->fixedFunctionState.blendEnable);
|
||||
|
||||
const ShaderPropertyDesc* pointScale = shader->FindProperty("_PointScale");
|
||||
ASSERT_NE(pointScale, nullptr);
|
||||
EXPECT_EQ(pointScale->type, ShaderPropertyType::Float);
|
||||
|
||||
const ShaderPropertyDesc* opacityScale = shader->FindProperty("_OpacityScale");
|
||||
ASSERT_NE(opacityScale, nullptr);
|
||||
EXPECT_EQ(opacityScale->type, ShaderPropertyType::Float);
|
||||
|
||||
const ShaderResourceBindingDesc* positions =
|
||||
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatPositions");
|
||||
ASSERT_NE(positions, nullptr);
|
||||
EXPECT_EQ(positions->type, ShaderResourceType::StructuredBuffer);
|
||||
EXPECT_EQ(positions->set, 2u);
|
||||
EXPECT_EQ(positions->binding, 0u);
|
||||
EXPECT_EQ(
|
||||
ResolveBuiltinPassResourceSemantic(*positions),
|
||||
BuiltinPassResourceSemantic::GaussianSplatPositionBuffer);
|
||||
|
||||
const ShaderResourceBindingDesc* other =
|
||||
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatOther");
|
||||
ASSERT_NE(other, nullptr);
|
||||
EXPECT_EQ(other->type, ShaderResourceType::StructuredBuffer);
|
||||
EXPECT_EQ(other->set, 2u);
|
||||
EXPECT_EQ(other->binding, 1u);
|
||||
EXPECT_EQ(
|
||||
ResolveBuiltinPassResourceSemantic(*other),
|
||||
BuiltinPassResourceSemantic::GaussianSplatOtherBuffer);
|
||||
|
||||
const ShaderResourceBindingDesc* color =
|
||||
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatColor");
|
||||
ASSERT_NE(color, nullptr);
|
||||
EXPECT_EQ(color->type, ShaderResourceType::StructuredBuffer);
|
||||
EXPECT_EQ(color->set, 2u);
|
||||
EXPECT_EQ(color->binding, 2u);
|
||||
EXPECT_EQ(
|
||||
ResolveBuiltinPassResourceSemantic(*color),
|
||||
BuiltinPassResourceSemantic::GaussianSplatColorBuffer);
|
||||
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedGaussianSplatShaderContract) {
|
||||
ShaderLoader loader;
|
||||
LoadResult result = loader.Load(GetBuiltinGaussianSplatShaderPath());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
Shader* shader = static_cast<Shader*>(result.resource);
|
||||
ASSERT_NE(shader, nullptr);
|
||||
|
||||
const ShaderPass* pass = shader->FindPass("GaussianSplat");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
|
||||
BuiltinPassResourceBindingPlan plan = {};
|
||||
String error;
|
||||
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
||||
ASSERT_EQ(plan.bindings.Size(), 5u);
|
||||
EXPECT_TRUE(plan.perObject.IsValid());
|
||||
EXPECT_TRUE(plan.material.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatPositionBuffer.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatOtherBuffer.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatColorBuffer.IsValid());
|
||||
EXPECT_FALSE(plan.gaussianSplatSHBuffer.IsValid());
|
||||
EXPECT_EQ(plan.perObject.set, 0u);
|
||||
EXPECT_EQ(plan.material.set, 1u);
|
||||
EXPECT_EQ(plan.gaussianSplatPositionBuffer.set, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatPositionBuffer.binding, 0u);
|
||||
EXPECT_EQ(plan.gaussianSplatOtherBuffer.set, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatOtherBuffer.binding, 1u);
|
||||
EXPECT_EQ(plan.gaussianSplatColorBuffer.set, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatColorBuffer.binding, 2u);
|
||||
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
||||
EXPECT_EQ(plan.descriptorSetCount, 3u);
|
||||
|
||||
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
||||
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
||||
ASSERT_EQ(setLayouts.size(), 3u);
|
||||
EXPECT_TRUE(setLayouts[0].usesPerObject);
|
||||
EXPECT_TRUE(setLayouts[1].usesMaterial);
|
||||
EXPECT_TRUE(setLayouts[2].usesGaussianSplatPositionBuffer);
|
||||
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOtherBuffer);
|
||||
EXPECT_TRUE(setLayouts[2].usesGaussianSplatColorBuffer);
|
||||
ASSERT_EQ(setLayouts[2].bindings.size(), 3u);
|
||||
EXPECT_EQ(
|
||||
static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
|
||||
DescriptorType::SRV);
|
||||
EXPECT_EQ(
|
||||
static_cast<DescriptorType>(setLayouts[2].bindings[1].type),
|
||||
DescriptorType::SRV);
|
||||
EXPECT_EQ(
|
||||
static_cast<DescriptorType>(setLayouts[2].bindings[2].type),
|
||||
DescriptorType::SRV);
|
||||
EXPECT_EQ(
|
||||
setLayouts[2].bindings[0].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
EXPECT_EQ(
|
||||
setLayouts[2].bindings[1].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
EXPECT_EQ(
|
||||
setLayouts[2].bindings[2].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringGaussianSplatBindingsToDescriptorSpaces) {
|
||||
ShaderLoader loader;
|
||||
LoadResult result = loader.Load(GetBuiltinGaussianSplatShaderPath());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
Shader* shader = static_cast<Shader*>(result.resource);
|
||||
ASSERT_NE(shader, nullptr);
|
||||
|
||||
const ShaderPass* pass = shader->FindPass("GaussianSplat");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
|
||||
const ShaderStageVariant* d3d12Vertex = shader->FindVariant(
|
||||
"GaussianSplat",
|
||||
XCEngine::Resources::ShaderType::Vertex,
|
||||
XCEngine::Resources::ShaderBackend::D3D12);
|
||||
ASSERT_NE(d3d12Vertex, nullptr);
|
||||
|
||||
ShaderCompileDesc d3d12CompileDesc = {};
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::D3D12,
|
||||
*d3d12Vertex,
|
||||
d3d12CompileDesc);
|
||||
const std::string d3d12Source(
|
||||
reinterpret_cast<const char*>(d3d12CompileDesc.source.data()),
|
||||
d3d12CompileDesc.source.size());
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"cbuffer PerObjectConstants",
|
||||
"register(b0)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"cbuffer MaterialConstants",
|
||||
"register(b1)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"StructuredBuffer<float3> GaussianSplatPositions",
|
||||
"register(t0)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
|
||||
"register(t1)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"StructuredBuffer<float4> GaussianSplatColor",
|
||||
"register(t2)"));
|
||||
EXPECT_EQ(d3d12Source.find("space0"), std::string::npos);
|
||||
EXPECT_EQ(d3d12Source.find("space1"), std::string::npos);
|
||||
EXPECT_EQ(d3d12Source.find("space2"), std::string::npos);
|
||||
|
||||
const ShaderStageVariant* vulkanVertex = shader->FindVariant(
|
||||
"GaussianSplat",
|
||||
XCEngine::Resources::ShaderType::Vertex,
|
||||
XCEngine::Resources::ShaderBackend::Vulkan);
|
||||
ASSERT_NE(vulkanVertex, nullptr);
|
||||
|
||||
ShaderCompileDesc vulkanCompileDesc = {};
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::Vulkan,
|
||||
*vulkanVertex,
|
||||
vulkanCompileDesc);
|
||||
const std::string vulkanSource(
|
||||
reinterpret_cast<const char*>(vulkanCompileDesc.source.data()),
|
||||
vulkanCompileDesc.source.size());
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"cbuffer PerObjectConstants",
|
||||
"register(b0, space0)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"cbuffer MaterialConstants",
|
||||
"register(b0, space1)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"StructuredBuffer<float3> GaussianSplatPositions",
|
||||
"register(t0, space2)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
|
||||
"register(t1, space2)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"StructuredBuffer<float4> GaussianSplatColor",
|
||||
"register(t2, space2)"));
|
||||
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST(BuiltinForwardPipeline_Test, BuiltinVolumetricShaderUsesAuthoringContract) {
|
||||
ShaderLoader loader;
|
||||
LoadResult result = loader.Load(GetBuiltinVolumetricShaderPath());
|
||||
@@ -893,7 +1304,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringFinal
|
||||
ASSERT_NE(d3d12Fragment, nullptr);
|
||||
|
||||
ShaderCompileDesc d3d12CompileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::D3D12,
|
||||
*d3d12Fragment,
|
||||
@@ -923,7 +1334,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringFinal
|
||||
ASSERT_NE(vulkanFragment, nullptr);
|
||||
|
||||
const std::string runtimeSource =
|
||||
::XCEngine::Rendering::Detail::BuildRuntimeShaderSource(
|
||||
::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::Vulkan,
|
||||
*vulkanFragment);
|
||||
@@ -941,7 +1352,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringFinal
|
||||
"register(s0, space2)"));
|
||||
|
||||
ShaderCompileDesc vulkanCompileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::Vulkan,
|
||||
*vulkanFragment,
|
||||
@@ -974,7 +1385,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringVolum
|
||||
ASSERT_NE(d3d12Fragment, nullptr);
|
||||
|
||||
ShaderCompileDesc d3d12CompileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::D3D12,
|
||||
*d3d12Fragment,
|
||||
@@ -1006,7 +1417,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringVolum
|
||||
ASSERT_NE(vulkanFragment, nullptr);
|
||||
|
||||
ShaderCompileDesc vulkanCompileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::Vulkan,
|
||||
*vulkanFragment,
|
||||
@@ -1053,7 +1464,7 @@ TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesFinalColorVariantToComb
|
||||
ASSERT_NE(openGLFragment, nullptr);
|
||||
|
||||
ShaderCompileDesc compileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::OpenGL,
|
||||
*openGLFragment,
|
||||
@@ -1098,7 +1509,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringColor
|
||||
ASSERT_NE(d3d12Fragment, nullptr);
|
||||
|
||||
ShaderCompileDesc d3d12CompileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::D3D12,
|
||||
*d3d12Fragment,
|
||||
@@ -1128,7 +1539,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringColor
|
||||
ASSERT_NE(vulkanFragment, nullptr);
|
||||
|
||||
const std::string runtimeSource =
|
||||
::XCEngine::Rendering::Detail::BuildRuntimeShaderSource(
|
||||
::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::Vulkan,
|
||||
*vulkanFragment);
|
||||
@@ -1146,7 +1557,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringColor
|
||||
"register(s0, space2)"));
|
||||
|
||||
ShaderCompileDesc vulkanCompileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::Vulkan,
|
||||
*vulkanFragment,
|
||||
@@ -1179,7 +1590,7 @@ TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesColorScaleVariantToComb
|
||||
ASSERT_NE(openGLFragment, nullptr);
|
||||
|
||||
ShaderCompileDesc compileDesc = {};
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::OpenGL,
|
||||
*openGLFragment,
|
||||
|
||||
Reference in New Issue
Block a user