Add builtin GaussianSplat forward pass baseline

This commit is contained in:
2026-04-10 23:11:11 +08:00
parent 15b42c248f
commit 107b320aa7
12 changed files with 1642 additions and 19 deletions

View File

@@ -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/BuiltinInfiniteGridPass.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.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/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/Passes/BuiltinVolumetricPass.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/CameraRenderer.cpp ${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/BuiltinInfiniteGridPass.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdOutlinePass.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/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/BuiltinVolumetricPass.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderSurface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderSurface.cpp

View 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
}
}
}

View File

@@ -62,6 +62,18 @@ inline bool TryBuildBuiltinPassResourceBindingPlan(
case BuiltinPassResourceSemantic::VolumeField: case BuiltinPassResourceSemantic::VolumeField:
location = &outPlan.volumeField; location = &outPlan.volumeField;
break; 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: case BuiltinPassResourceSemantic::BaseColorTexture:
location = &outPlan.baseColorTexture; location = &outPlan.baseColorTexture;
break; break;
@@ -334,6 +346,24 @@ inline bool TryBuildBuiltinPassDefaultResourceBindings(
return true; 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)) { if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::PostProcess)) {
AppendBuiltinPassResourceBinding( AppendBuiltinPassResourceBinding(
outBindings, outBindings,
@@ -570,6 +600,18 @@ inline bool TryBuildBuiltinPassSetLayouts(
case BuiltinPassResourceSemantic::VolumeField: case BuiltinPassResourceSemantic::VolumeField:
setLayout.usesVolumeField = true; setLayout.usesVolumeField = true;
break; 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: case BuiltinPassResourceSemantic::BaseColorTexture:
setLayout.usesTexture = true; setLayout.usesTexture = true;
setLayout.usesBaseColorTexture = true; setLayout.usesBaseColorTexture = true;

View File

@@ -27,6 +27,8 @@ inline const char* GetBuiltinPassCanonicalName(BuiltinMaterialPass pass) {
return "selectionmask"; return "selectionmask";
case BuiltinMaterialPass::Skybox: case BuiltinMaterialPass::Skybox:
return "skybox"; return "skybox";
case BuiltinMaterialPass::GaussianSplat:
return "gaussiansplat";
case BuiltinMaterialPass::Volumetric: case BuiltinMaterialPass::Volumetric:
return "volumetric"; return "volumetric";
case BuiltinMaterialPass::PostProcess: case BuiltinMaterialPass::PostProcess:
@@ -145,6 +147,30 @@ inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic(
return BuiltinPassResourceSemantic::VolumeField; 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") || if (semantic == Containers::String("basecolortexture") ||
semantic == Containers::String("maintex")) { semantic == Containers::String("maintex")) {
return BuiltinPassResourceSemantic::BaseColorTexture; return BuiltinPassResourceSemantic::BaseColorTexture;
@@ -207,6 +233,14 @@ inline const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemant
return "PassConstants"; return "PassConstants";
case BuiltinPassResourceSemantic::VolumeField: case BuiltinPassResourceSemantic::VolumeField:
return "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: case BuiltinPassResourceSemantic::BaseColorTexture:
return "BaseColorTexture"; return "BaseColorTexture";
case BuiltinPassResourceSemantic::SourceColorTexture: case BuiltinPassResourceSemantic::SourceColorTexture:
@@ -295,6 +329,12 @@ inline bool IsBuiltinPassResourceTypeCompatible(
case BuiltinPassResourceSemantic::VolumeField: case BuiltinPassResourceSemantic::VolumeField:
return type == Resources::ShaderResourceType::StructuredBuffer || return type == Resources::ShaderResourceType::StructuredBuffer ||
type == Resources::ShaderResourceType::RawBuffer; 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::BaseColorTexture:
case BuiltinPassResourceSemantic::SourceColorTexture: case BuiltinPassResourceSemantic::SourceColorTexture:
case BuiltinPassResourceSemantic::SkyboxPanoramicTexture: case BuiltinPassResourceSemantic::SkyboxPanoramicTexture:

View File

@@ -20,6 +20,7 @@ enum class BuiltinMaterialPass : Core::uint32 {
ObjectId, ObjectId,
SelectionMask, SelectionMask,
Skybox, Skybox,
GaussianSplat,
Volumetric, Volumetric,
PostProcess, PostProcess,
FinalColor, FinalColor,
@@ -45,6 +46,11 @@ enum class BuiltinPassResourceSemantic : Core::uint8 {
Environment, Environment,
PassConstants, PassConstants,
VolumeField, VolumeField,
GaussianSplatOrderBuffer,
GaussianSplatPositionBuffer,
GaussianSplatOtherBuffer,
GaussianSplatColorBuffer,
GaussianSplatSHBuffer,
BaseColorTexture, BaseColorTexture,
SourceColorTexture, SourceColorTexture,
SkyboxPanoramicTexture, SkyboxPanoramicTexture,
@@ -78,6 +84,11 @@ struct BuiltinPassResourceBindingPlan {
PassResourceBindingLocation shadowReceiver = {}; PassResourceBindingLocation shadowReceiver = {};
PassResourceBindingLocation environment = {}; PassResourceBindingLocation environment = {};
PassResourceBindingLocation passConstants = {}; PassResourceBindingLocation passConstants = {};
PassResourceBindingLocation gaussianSplatOrderBuffer = {};
PassResourceBindingLocation gaussianSplatPositionBuffer = {};
PassResourceBindingLocation gaussianSplatOtherBuffer = {};
PassResourceBindingLocation gaussianSplatColorBuffer = {};
PassResourceBindingLocation gaussianSplatSHBuffer = {};
PassResourceBindingLocation baseColorTexture = {}; PassResourceBindingLocation baseColorTexture = {};
PassResourceBindingLocation sourceColorTexture = {}; PassResourceBindingLocation sourceColorTexture = {};
PassResourceBindingLocation skyboxPanoramicTexture = {}; PassResourceBindingLocation skyboxPanoramicTexture = {};
@@ -111,6 +122,11 @@ struct BuiltinPassSetLayoutMetadata {
bool usesPassConstants = false; bool usesPassConstants = false;
bool usesMaterialBuffers = false; bool usesMaterialBuffers = false;
bool usesVolumeField = false; bool usesVolumeField = false;
bool usesGaussianSplatOrderBuffer = false;
bool usesGaussianSplatPositionBuffer = false;
bool usesGaussianSplatOtherBuffer = false;
bool usesGaussianSplatColorBuffer = false;
bool usesGaussianSplatSHBuffer = false;
bool usesTexture = false; bool usesTexture = false;
bool usesBaseColorTexture = false; bool usesBaseColorTexture = false;
bool usesSourceColorTexture = false; bool usesSourceColorTexture = false;

View File

@@ -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

View File

@@ -39,6 +39,7 @@ namespace Pipelines {
} // namespace Pipelines } // namespace Pipelines
namespace Passes { namespace Passes {
class BuiltinGaussianSplatPass;
class BuiltinVolumetricPass; class BuiltinVolumetricPass;
} // namespace Passes } // namespace Passes
@@ -355,6 +356,7 @@ private:
OwnedDescriptorSet m_skyboxSamplerSet = {}; OwnedDescriptorSet m_skyboxSamplerSet = {};
RHI::RHIResourceView* m_skyboxBoundPanoramicTextureView = nullptr; RHI::RHIResourceView* m_skyboxBoundPanoramicTextureView = nullptr;
RHI::RHIResourceView* m_skyboxBoundCubemapTextureView = nullptr; RHI::RHIResourceView* m_skyboxBoundCubemapTextureView = nullptr;
std::unique_ptr<Passes::BuiltinGaussianSplatPass> m_gaussianSplatPass;
std::unique_ptr<Passes::BuiltinVolumetricPass> m_volumetricPass; std::unique_ptr<Passes::BuiltinVolumetricPass> m_volumetricPass;
}; };

View File

@@ -38,6 +38,7 @@ Containers::String GetBuiltinObjectIdOutlineShaderPath();
Containers::String GetBuiltinSelectionMaskShaderPath(); Containers::String GetBuiltinSelectionMaskShaderPath();
Containers::String GetBuiltinSelectionOutlineShaderPath(); Containers::String GetBuiltinSelectionOutlineShaderPath();
Containers::String GetBuiltinSkyboxShaderPath(); Containers::String GetBuiltinSkyboxShaderPath();
Containers::String GetBuiltinGaussianSplatShaderPath();
Containers::String GetBuiltinVolumetricShaderPath(); Containers::String GetBuiltinVolumetricShaderPath();
Containers::String GetBuiltinColorScalePostProcessShaderPath(); Containers::String GetBuiltinColorScalePostProcessShaderPath();
Containers::String GetBuiltinFinalColorShaderPath(); Containers::String GetBuiltinFinalColorShaderPath();

View 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

View File

@@ -4,6 +4,7 @@
#include "Core/Asset/ResourceManager.h" #include "Core/Asset/ResourceManager.h"
#include "RHI/RHICommandList.h" #include "RHI/RHICommandList.h"
#include "Rendering/Internal/RenderSurfacePipelineUtils.h" #include "Rendering/Internal/RenderSurfacePipelineUtils.h"
#include "Rendering/Passes/BuiltinGaussianSplatPass.h"
#include "Rendering/Materials/RenderMaterialResolve.h" #include "Rendering/Materials/RenderMaterialResolve.h"
#include "Rendering/Passes/BuiltinVolumetricPass.h" #include "Rendering/Passes/BuiltinVolumetricPass.h"
#include "Rendering/RenderSurface.h" #include "Rendering/RenderSurface.h"
@@ -66,6 +67,7 @@ std::vector<RHI::RHIResourceView*> CollectSurfaceColorAttachments(const RenderSu
} // namespace } // namespace
BuiltinForwardPipeline::BuiltinForwardPipeline() { BuiltinForwardPipeline::BuiltinForwardPipeline() {
m_gaussianSplatPass = std::make_unique<Passes::BuiltinGaussianSplatPass>();
m_volumetricPass = std::make_unique<Passes::BuiltinVolumetricPass>(); m_volumetricPass = std::make_unique<Passes::BuiltinVolumetricPass>();
} }
@@ -109,11 +111,16 @@ RHI::InputLayoutDesc BuiltinForwardPipeline::BuildInputLayout() {
bool BuiltinForwardPipeline::Initialize(const RenderContext& context) { bool BuiltinForwardPipeline::Initialize(const RenderContext& context) {
return EnsureInitialized(context) && return EnsureInitialized(context) &&
m_gaussianSplatPass != nullptr &&
m_gaussianSplatPass->Initialize(context) &&
m_volumetricPass != nullptr && m_volumetricPass != nullptr &&
m_volumetricPass->Initialize(context); m_volumetricPass->Initialize(context);
} }
void BuiltinForwardPipeline::Shutdown() { void BuiltinForwardPipeline::Shutdown() {
if (m_gaussianSplatPass != nullptr) {
m_gaussianSplatPass->Shutdown();
}
if (m_volumetricPass != nullptr) { if (m_volumetricPass != nullptr) {
m_volumetricPass->Shutdown(); m_volumetricPass->Shutdown();
} }
@@ -128,6 +135,17 @@ bool BuiltinForwardPipeline::Render(
return false; 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 = { const RenderPassContext passContext = {
context, context,
surface, surface,
@@ -150,6 +168,9 @@ bool BuiltinForwardPipeline::Render(
if (renderResult) { if (renderResult) {
renderResult = ExecuteForwardSkyboxPass(passContext); renderResult = ExecuteForwardSkyboxPass(passContext);
} }
if (renderResult && m_gaussianSplatPass != nullptr) {
renderResult = m_gaussianSplatPass->Execute(passContext);
}
if (renderResult && m_volumetricPass != nullptr) { if (renderResult && m_volumetricPass != nullptr) {
renderResult = m_volumetricPass->Execute(passContext); renderResult = m_volumetricPass->Execute(passContext);
} }

View File

@@ -36,6 +36,7 @@ constexpr const char* kBuiltinObjectIdOutlineShaderPath = "builtin://shaders/obj
constexpr const char* kBuiltinSelectionMaskShaderPath = "builtin://shaders/selection-mask"; constexpr const char* kBuiltinSelectionMaskShaderPath = "builtin://shaders/selection-mask";
constexpr const char* kBuiltinSelectionOutlineShaderPath = "builtin://shaders/selection-outline"; constexpr const char* kBuiltinSelectionOutlineShaderPath = "builtin://shaders/selection-outline";
constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox"; 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* kBuiltinVolumetricShaderPath = "builtin://shaders/volumetric";
constexpr const char* kBuiltinColorScalePostProcessShaderPath = constexpr const char* kBuiltinColorScalePostProcessShaderPath =
"builtin://shaders/color-scale-post-process"; "builtin://shaders/color-scale-post-process";
@@ -68,6 +69,8 @@ constexpr const char* kBuiltinSelectionOutlineShaderAssetRelativePath =
"engine/assets/builtin/shaders/selection-outline.shader"; "engine/assets/builtin/shaders/selection-outline.shader";
constexpr const char* kBuiltinSkyboxShaderAssetRelativePath = constexpr const char* kBuiltinSkyboxShaderAssetRelativePath =
"engine/assets/builtin/shaders/skybox.shader"; "engine/assets/builtin/shaders/skybox.shader";
constexpr const char* kBuiltinGaussianSplatShaderAssetRelativePath =
"engine/assets/builtin/shaders/gaussian-splat.shader";
constexpr const char* kBuiltinVolumetricShaderAssetRelativePath = constexpr const char* kBuiltinVolumetricShaderAssetRelativePath =
"engine/assets/builtin/shaders/volumetric.shader"; "engine/assets/builtin/shaders/volumetric.shader";
constexpr const char* kBuiltinColorScalePostProcessShaderAssetRelativePath = constexpr const char* kBuiltinColorScalePostProcessShaderAssetRelativePath =
@@ -164,6 +167,9 @@ const char* GetBuiltinShaderAssetRelativePath(const Containers::String& builtinS
if (builtinShaderPath == Containers::String(kBuiltinSkyboxShaderPath)) { if (builtinShaderPath == Containers::String(kBuiltinSkyboxShaderPath)) {
return kBuiltinSkyboxShaderAssetRelativePath; return kBuiltinSkyboxShaderAssetRelativePath;
} }
if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatShaderPath)) {
return kBuiltinGaussianSplatShaderAssetRelativePath;
}
if (builtinShaderPath == Containers::String(kBuiltinVolumetricShaderPath)) { if (builtinShaderPath == Containers::String(kBuiltinVolumetricShaderPath)) {
return kBuiltinVolumetricShaderAssetRelativePath; return kBuiltinVolumetricShaderAssetRelativePath;
} }
@@ -734,6 +740,10 @@ Shader* BuildBuiltinSkyboxShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromAsset(path); return TryLoadBuiltinShaderFromAsset(path);
} }
Shader* BuildBuiltinGaussianSplatShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromAsset(path);
}
Shader* BuildBuiltinVolumetricShader(const Containers::String& path) { Shader* BuildBuiltinVolumetricShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromAsset(path); return TryLoadBuiltinShaderFromAsset(path);
} }
@@ -852,6 +862,10 @@ bool TryGetBuiltinShaderPathByShaderName(
outPath = GetBuiltinSkyboxShaderPath(); outPath = GetBuiltinSkyboxShaderPath();
return true; return true;
} }
if (shaderName == "Builtin Gaussian Splat") {
outPath = GetBuiltinGaussianSplatShaderPath();
return true;
}
if (shaderName == "Builtin Volumetric") { if (shaderName == "Builtin Volumetric") {
outPath = GetBuiltinVolumetricShaderPath(); outPath = GetBuiltinVolumetricShaderPath();
return true; return true;
@@ -939,6 +953,10 @@ Containers::String GetBuiltinSkyboxShaderPath() {
return Containers::String(kBuiltinSkyboxShaderPath); return Containers::String(kBuiltinSkyboxShaderPath);
} }
Containers::String GetBuiltinGaussianSplatShaderPath() {
return Containers::String(kBuiltinGaussianSplatShaderPath);
}
Containers::String GetBuiltinVolumetricShaderPath() { Containers::String GetBuiltinVolumetricShaderPath() {
return Containers::String(kBuiltinVolumetricShaderPath); return Containers::String(kBuiltinVolumetricShaderPath);
} }
@@ -1057,6 +1075,8 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) {
shader = BuildBuiltinSelectionOutlineShader(path); shader = BuildBuiltinSelectionOutlineShader(path);
} else if (path == GetBuiltinSkyboxShaderPath()) { } else if (path == GetBuiltinSkyboxShaderPath()) {
shader = BuildBuiltinSkyboxShader(path); shader = BuildBuiltinSkyboxShader(path);
} else if (path == GetBuiltinGaussianSplatShaderPath()) {
shader = BuildBuiltinGaussianSplatShader(path);
} else if (path == GetBuiltinVolumetricShaderPath()) { } else if (path == GetBuiltinVolumetricShaderPath()) {
shader = BuildBuiltinVolumetricShader(path); shader = BuildBuiltinVolumetricShader(path);
} else if (path == GetBuiltinColorScalePostProcessShaderPath()) { } else if (path == GetBuiltinColorScalePostProcessShaderPath()) {

View File

@@ -1,12 +1,14 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "Rendering/Detail/ShaderVariantUtils.h" #include "Rendering/Internal/ShaderVariantUtils.h"
#include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h> #include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h>
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
#include <XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h> #include <XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h>
#include <XCEngine/Rendering/Passes/BuiltinObjectIdPass.h> #include <XCEngine/Rendering/Passes/BuiltinObjectIdPass.h>
#include <XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h> #include <XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h>
#include <XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h> #include <XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Resources/BuiltinResources.h> #include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Mesh/Mesh.h> #include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.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 } // namespace
TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
@@ -116,6 +158,162 @@ TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVert
EXPECT_EQ(texcoord.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, uv0))); 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) { TEST(BuiltinForwardPipeline_Test, SplitsSceneItemsIntoOpaqueAndTransparentQueueRanges) {
EXPECT_FALSE(IsTransparentRenderQueue(static_cast<int>(MaterialRenderQueue::Background))); EXPECT_FALSE(IsTransparentRenderQueue(static_cast<int>(MaterialRenderQueue::Background)));
EXPECT_FALSE(IsTransparentRenderQueue(static_cast<int>(MaterialRenderQueue::Geometry))); EXPECT_FALSE(IsTransparentRenderQueue(static_cast<int>(MaterialRenderQueue::Geometry)));
@@ -207,7 +405,7 @@ TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderBuildsVulkanRuntimeSourceWit
ASSERT_NE(fragmentVariant, nullptr); ASSERT_NE(fragmentVariant, nullptr);
const std::string runtimeSource = const std::string runtimeSource =
::XCEngine::Rendering::Detail::BuildRuntimeShaderSource( ::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
*pass, *pass,
XCEngine::Resources::ShaderBackend::Vulkan, XCEngine::Resources::ShaderBackend::Vulkan,
*fragmentVariant); *fragmentVariant);
@@ -243,7 +441,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringUnlit
ASSERT_NE(d3d12Fragment, nullptr); ASSERT_NE(d3d12Fragment, nullptr);
ShaderCompileDesc d3d12CompileDesc = {}; ShaderCompileDesc d3d12CompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::D3D12, XCEngine::Resources::ShaderBackend::D3D12,
*d3d12Fragment, *d3d12Fragment,
@@ -271,7 +469,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringUnlit
ASSERT_NE(vulkanFragment, nullptr); ASSERT_NE(vulkanFragment, nullptr);
ShaderCompileDesc vulkanCompileDesc = {}; ShaderCompileDesc vulkanCompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::Vulkan, XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment, *vulkanFragment,
@@ -314,7 +512,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringForwa
ASSERT_NE(d3d12Fragment, nullptr); ASSERT_NE(d3d12Fragment, nullptr);
ShaderCompileDesc d3d12CompileDesc = {}; ShaderCompileDesc d3d12CompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::D3D12, XCEngine::Resources::ShaderBackend::D3D12,
*d3d12Fragment, *d3d12Fragment,
@@ -365,7 +563,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringForwa
ASSERT_NE(vulkanFragment, nullptr); ASSERT_NE(vulkanFragment, nullptr);
ShaderCompileDesc vulkanCompileDesc = {}; ShaderCompileDesc vulkanCompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::Vulkan, XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment, *vulkanFragment,
@@ -432,7 +630,7 @@ TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesForwardShadowVariantToL
ASSERT_NE(openGLFragment, nullptr); ASSERT_NE(openGLFragment, nullptr);
ShaderCompileDesc compileDesc = {}; ShaderCompileDesc compileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::OpenGL, XCEngine::Resources::ShaderBackend::OpenGL,
*openGLFragment, *openGLFragment,
@@ -509,7 +707,7 @@ TEST(BuiltinDepthStylePass_Test, OpenGLRuntimeTranspilesDepthOnlyAlphaVariantWit
const ShaderStageVariant& variant, const ShaderStageVariant& variant,
std::string& glslSource) { std::string& glslSource) {
ShaderCompileDesc compileDesc = {}; ShaderCompileDesc compileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
targetPass, targetPass,
XCEngine::Resources::ShaderBackend::OpenGL, XCEngine::Resources::ShaderBackend::OpenGL,
variant, variant,
@@ -573,7 +771,7 @@ TEST(BuiltinDepthStylePass_Test, OpenGLRuntimeTranspilesShadowCasterAlphaVariant
const ShaderStageVariant& variant, const ShaderStageVariant& variant,
std::string& glslSource) { std::string& glslSource) {
ShaderCompileDesc compileDesc = {}; ShaderCompileDesc compileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
targetPass, targetPass,
XCEngine::Resources::ShaderBackend::OpenGL, XCEngine::Resources::ShaderBackend::OpenGL,
variant, variant,
@@ -676,6 +874,219 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
delete shader; 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) { TEST(BuiltinForwardPipeline_Test, BuiltinVolumetricShaderUsesAuthoringContract) {
ShaderLoader loader; ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinVolumetricShaderPath()); LoadResult result = loader.Load(GetBuiltinVolumetricShaderPath());
@@ -893,7 +1304,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringFinal
ASSERT_NE(d3d12Fragment, nullptr); ASSERT_NE(d3d12Fragment, nullptr);
ShaderCompileDesc d3d12CompileDesc = {}; ShaderCompileDesc d3d12CompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::D3D12, XCEngine::Resources::ShaderBackend::D3D12,
*d3d12Fragment, *d3d12Fragment,
@@ -923,7 +1334,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringFinal
ASSERT_NE(vulkanFragment, nullptr); ASSERT_NE(vulkanFragment, nullptr);
const std::string runtimeSource = const std::string runtimeSource =
::XCEngine::Rendering::Detail::BuildRuntimeShaderSource( ::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
*pass, *pass,
XCEngine::Resources::ShaderBackend::Vulkan, XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment); *vulkanFragment);
@@ -941,7 +1352,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringFinal
"register(s0, space2)")); "register(s0, space2)"));
ShaderCompileDesc vulkanCompileDesc = {}; ShaderCompileDesc vulkanCompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::Vulkan, XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment, *vulkanFragment,
@@ -974,7 +1385,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringVolum
ASSERT_NE(d3d12Fragment, nullptr); ASSERT_NE(d3d12Fragment, nullptr);
ShaderCompileDesc d3d12CompileDesc = {}; ShaderCompileDesc d3d12CompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::D3D12, XCEngine::Resources::ShaderBackend::D3D12,
*d3d12Fragment, *d3d12Fragment,
@@ -1006,7 +1417,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringVolum
ASSERT_NE(vulkanFragment, nullptr); ASSERT_NE(vulkanFragment, nullptr);
ShaderCompileDesc vulkanCompileDesc = {}; ShaderCompileDesc vulkanCompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::Vulkan, XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment, *vulkanFragment,
@@ -1053,7 +1464,7 @@ TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesFinalColorVariantToComb
ASSERT_NE(openGLFragment, nullptr); ASSERT_NE(openGLFragment, nullptr);
ShaderCompileDesc compileDesc = {}; ShaderCompileDesc compileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::OpenGL, XCEngine::Resources::ShaderBackend::OpenGL,
*openGLFragment, *openGLFragment,
@@ -1098,7 +1509,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringColor
ASSERT_NE(d3d12Fragment, nullptr); ASSERT_NE(d3d12Fragment, nullptr);
ShaderCompileDesc d3d12CompileDesc = {}; ShaderCompileDesc d3d12CompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::D3D12, XCEngine::Resources::ShaderBackend::D3D12,
*d3d12Fragment, *d3d12Fragment,
@@ -1128,7 +1539,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringColor
ASSERT_NE(vulkanFragment, nullptr); ASSERT_NE(vulkanFragment, nullptr);
const std::string runtimeSource = const std::string runtimeSource =
::XCEngine::Rendering::Detail::BuildRuntimeShaderSource( ::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
*pass, *pass,
XCEngine::Resources::ShaderBackend::Vulkan, XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment); *vulkanFragment);
@@ -1146,7 +1557,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringColor
"register(s0, space2)")); "register(s0, space2)"));
ShaderCompileDesc vulkanCompileDesc = {}; ShaderCompileDesc vulkanCompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::Vulkan, XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment, *vulkanFragment,
@@ -1179,7 +1590,7 @@ TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesColorScaleVariantToComb
ASSERT_NE(openGLFragment, nullptr); ASSERT_NE(openGLFragment, nullptr);
ShaderCompileDesc compileDesc = {}; ShaderCompileDesc compileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant( ::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass, *pass,
XCEngine::Resources::ShaderBackend::OpenGL, XCEngine::Resources::ShaderBackend::OpenGL,
*openGLFragment, *openGLFragment,