Formalize gaussian splat prepare-order pass

This commit is contained in:
2026-04-11 03:02:30 +08:00
parent 5191bb1149
commit fac6e588a8
16 changed files with 1529 additions and 68 deletions

View File

@@ -0,0 +1,181 @@
# Shader 统一预编译缓存并入 Library 正式方案
文档日期2026-04-11
## 1. 目标
把 shader 的预编译结果正式并入现有 `Library/Artifacts` 体系,不再允许出现:
1. project shader 走 `Library`builtin shader 走裸加载。
2. 运行时每次开编辑器都重新现场编译重 shader。
3. 只修 D3D12一到 Vulkan / OpenGL 又退回运行时临时编译。
本轮正式目标:
1. builtin shader 与 project shader 统一走同一个 artifact 产物格式。
2. `ShaderStageVariant` 保持现有 authoring / runtime 变体模型不变,不扩成“每后端一个 variant”。
3. 每个 variant 可以携带“按后端区分的已编译 payload”。
4. D3D12 / Vulkan / OpenGL 运行时优先消费 artifact 中的 payload只有 miss 时才 fallback 到现场编译。
## 2. 架构原则
### 2.1 唯一正式缓存位置
唯一正式 shader 预编译缓存位置是:
`project/Library/Artifacts/.../main.xcshader`
不新增独立 runtime cache 目录,不做旁路缓存。
### 2.2 builtin shader 也必须纳入 Library
外部标识仍然保持:
`builtin://shaders/...`
但在有 project root 时,真实加载路径应优先变成:
`Library/Artifacts/.../main.xcshader`
这样 builtin shader 与普通 `Assets/*.shader` 在缓存语义上完全一致。
### 2.3 不改变现有 variant 选择逻辑
现有很多代码和测试都依赖:
1. authoring variant 可以是 `Generic`
2. 运行时按 backend 查找时先找精确 backend再回退 `Generic`
因此不把一个 `Generic` variant 展开成三份后端 variant而是让一个 variant 内部携带:
1. 旧字段 `compiledBinary`
2. 新字段 `backendCompiledBinaries`
## 3. 数据模型
### 3.1 运行时 compile desc
`RHI::ShaderCompileDesc` 新增:
1. `compiledBinaryBackend`
2. `compiledBinary`
含义:
1. source / fileName 仍然描述“可回退到现场编译的输入”
2. compiledBinary 描述“当前后端可直接消费的已编译 payload”
### 3.2 shader variant
`Resources::ShaderStageVariant` 支持:
1. `GetCompiledBinaryForBackend(...)`
2. `SetCompiledBinaryForBackend(...)`
语义:
1. backend-specific variant 可以继续把本后端 payload 放在旧 `compiledBinary`
2. `Generic` variant 的 D3D12 / Vulkan / OpenGL payload 放在 `backendCompiledBinaries`
### 3.3 artifact schema
shader artifact schema 升级为 `6``ShaderVariantArtifactHeader` 新增:
1. `backendCompiledBinaryCount`
并追加 `ShaderBackendCompiledBinaryArtifactHeader + payload` 序列。
## 4. 导入阶段
导入 `.shader` 时:
1. 先用 `ShaderLoader` 生成现有 authoring/runtime variant。
2. 对每个 pass / variant 生成运行时 compile desc。
3. 按目标后端尝试预编译:
- D3D12生成 DXBC
- Vulkan生成 Vulkan SPIR-V
- OpenGL生成 OpenGL-target SPIR-V
4. 把结果写回 variant。
5. 最终统一写入 `main.xcshader` artifact。
后端目标规则:
1. `Generic` variant 预编译到 D3D12 / Vulkan / OpenGL
2. backend-specific variant 只预编译自己的 backend
失败策略:
1. 预编译失败不阻断 import
2. artifact 仍然照常生成
3. 运行时保持 fallback 能力
4. 日志明确记录 pass / stage / backend / reason
## 5. builtin shader 接入方式
### 5.1 ResourceManager
`ResourceManager::LoadResource(...)` 在 shader + builtin path 情况下:
1. 解析到真实 builtin shader 资产路径
2. 在当前 project 的 `Library` 中确保对应 artifact
3. 真正加载 artifact
4. 资源对外 path 仍然恢复成 `builtin://...`
### 5.2 AssetDatabase
由于 builtin shader 资产位于 project root 外部:
1. 允许绝对路径解析成相对 project root 的 `../engine/...`
2. 这些外部 source record 不写 `.meta`
3. 使用 synthetic GUID / synthetic meta hash
4. `Refresh()` 时显式扫描 builtin shader 资产,避免下次启动把 builtin artifact 当 orphan 清掉
## 6. 运行时消费
### 6.1 D3D12
1. `D3D12Shader` 增加 `InitializeFromBytecode(...)`
2. `CompileD3D12Shader(...)` 优先命中 `compiledBinaryBackend == D3D12`
3. miss 时才走 `D3DCompile(...)`
4. `CreateShader(...)` 统一复用这条逻辑
### 6.2 Vulkan
1. `CompileSpirvShader(...)` 支持直接消费 `compiledBinaryBackend == Vulkan`
2. `VulkanDevice / VulkanPipelineState` 通过既有 `CompileVulkanShader(...)` 自动命中缓存 payload
### 6.3 OpenGL
1. 若命中 `compiledBinaryBackend == OpenGL`
2. 则把该 payload 作为 SPIR-V 输入
3. 继续走现有 `SPIR-V -> GLSL` 转译路径
4. 原始 HLSL source 仍保留给 sampler/texture 绑定推导逻辑使用
## 7. 验收标准
### 7.1 工程标准
1. `shader_tests` 全绿
2. `XCEditor` 能完整编译通过
### 7.2 行为标准
1. shader artifact roundtrip 后不丢失 backend payload
2. builtin shader 与 project shader 都能命中 `Library/Artifacts`
3. D3D12 / Vulkan / OpenGL 运行时在 payload 存在时不再重复现场编译
### 7.3 性能标准
1. 影响 NanoVDB 首帧的 volumetric shader 不再在每次启动时重编
2. editor 打开主场景时shader stall 不再成为 `SceneReady` 的主瓶颈
## 8. 后续扩展
后续若继续做:
1. import-time completeness 标记
2. 运行时回写 artifact
3. PSO cache blob
4. 更完整的 shader compile telemetry
都必须建立在本方案之上,不允许再引入平行缓存体系。

View File

@@ -26,6 +26,7 @@ Shader "Builtin Gaussian Splat"
float4 scaleReserved; float4 scaleReserved;
}; };
StructuredBuffer<uint> GaussianSplatOrderBuffer;
StructuredBuffer<float3> GaussianSplatPositions; StructuredBuffer<float3> GaussianSplatPositions;
StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther; StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther;
StructuredBuffer<float4> GaussianSplatColor; StructuredBuffer<float4> GaussianSplatColor;
@@ -54,9 +55,10 @@ Shader "Builtin Gaussian Splat"
{ {
VSOutput output; VSOutput output;
const float2 corner = ResolveQuadCorner(vertexId); const float2 corner = ResolveQuadCorner(vertexId);
const float3 localCenter = GaussianSplatPositions[instanceId]; const uint splatIndex = GaussianSplatOrderBuffer[instanceId];
const GaussianSplatOtherData otherData = GaussianSplatOther[instanceId]; const float3 localCenter = GaussianSplatPositions[splatIndex];
const float4 colorOpacity = GaussianSplatColor[instanceId]; const GaussianSplatOtherData otherData = GaussianSplatOther[splatIndex];
const float4 colorOpacity = GaussianSplatColor[splatIndex];
const float3 worldCenter = mul(gModelMatrix, float4(localCenter, 1.0)).xyz; const float3 worldCenter = mul(gModelMatrix, float4(localCenter, 1.0)).xyz;
const float maxAxisScale = const float maxAxisScale =

View File

@@ -16,7 +16,7 @@ namespace Resources {
constexpr Core::uint32 kTextureArtifactSchemaVersion = 1; constexpr Core::uint32 kTextureArtifactSchemaVersion = 1;
constexpr Core::uint32 kMaterialArtifactSchemaVersion = 6; constexpr Core::uint32 kMaterialArtifactSchemaVersion = 6;
constexpr Core::uint32 kMeshArtifactSchemaVersion = 2; constexpr Core::uint32 kMeshArtifactSchemaVersion = 2;
constexpr Core::uint32 kShaderArtifactSchemaVersion = 5; constexpr Core::uint32 kShaderArtifactSchemaVersion = 6;
constexpr Core::uint32 kUIDocumentArtifactSchemaVersion = 2; constexpr Core::uint32 kUIDocumentArtifactSchemaVersion = 2;
constexpr Core::uint32 kVolumeFieldArtifactSchemaVersion = 2; constexpr Core::uint32 kVolumeFieldArtifactSchemaVersion = 2;
constexpr Core::uint32 kModelArtifactSchemaVersion = 1; constexpr Core::uint32 kModelArtifactSchemaVersion = 1;
@@ -104,7 +104,7 @@ struct ModelMaterialBindingArtifact {
}; };
struct ShaderArtifactFileHeader { struct ShaderArtifactFileHeader {
char magic[8] = { 'X', 'C', 'S', 'H', 'D', '0', '5', '\0' }; char magic[8] = { 'X', 'C', 'S', 'H', 'D', '0', '6', '\0' };
Core::uint32 schemaVersion = kShaderArtifactSchemaVersion; Core::uint32 schemaVersion = kShaderArtifactSchemaVersion;
}; };
@@ -138,6 +138,14 @@ struct ShaderVariantArtifactHeader {
Core::uint32 backend = 0; Core::uint32 backend = 0;
Core::uint32 keywordCount = 0; Core::uint32 keywordCount = 0;
Core::uint64 compiledBinarySize = 0; Core::uint64 compiledBinarySize = 0;
Core::uint32 backendCompiledBinaryCount = 0;
Core::uint32 reserved = 0;
};
struct ShaderBackendCompiledBinaryArtifactHeader {
Core::uint32 backend = 0;
Core::uint32 reserved = 0;
Core::uint64 compiledBinarySize = 0;
}; };
struct ShaderKeywordDeclarationArtifactHeader { struct ShaderKeywordDeclarationArtifactHeader {

View File

@@ -30,6 +30,10 @@ public:
const D3D_SHADER_MACRO* macros, const D3D_SHADER_MACRO* macros,
const char* entryPoint, const char* entryPoint,
const char* target); const char* target);
bool InitializeFromBytecode(
const void* bytecodeData,
size_t bytecodeSize,
const char* target = nullptr);
void Shutdown() override; void Shutdown() override;
const D3D12_SHADER_BYTECODE GetD3D12Bytecode() const; const D3D12_SHADER_BYTECODE GetD3D12Bytecode() const;

View File

@@ -21,6 +21,7 @@
namespace XCEngine { namespace XCEngine {
namespace Components { namespace Components {
class GameObject; class GameObject;
class GaussianSplatRendererComponent;
} // namespace Components } // namespace Components
namespace Resources { namespace Resources {
@@ -31,6 +32,11 @@ class Shader;
namespace Rendering { namespace Rendering {
struct VisibleGaussianSplatItem; struct VisibleGaussianSplatItem;
namespace Passes {
namespace Internal {
class BuiltinGaussianSplatPassResources;
} // namespace Internal
} // namespace Passes
namespace Passes { namespace Passes {
@@ -84,16 +90,20 @@ private:
std::vector<BuiltinPassSetLayoutMetadata> setLayouts; std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
PassResourceBindingLocation perObject = {}; PassResourceBindingLocation perObject = {};
PassResourceBindingLocation material = {}; PassResourceBindingLocation material = {};
PassResourceBindingLocation gaussianSplatSortDistanceBuffer = {};
PassResourceBindingLocation gaussianSplatOrderBuffer = {};
PassResourceBindingLocation gaussianSplatPositionBuffer = {}; PassResourceBindingLocation gaussianSplatPositionBuffer = {};
PassResourceBindingLocation gaussianSplatOtherBuffer = {}; PassResourceBindingLocation gaussianSplatOtherBuffer = {};
PassResourceBindingLocation gaussianSplatColorBuffer = {}; PassResourceBindingLocation gaussianSplatColorBuffer = {};
PassResourceBindingLocation gaussianSplatSHBuffer = {}; PassResourceBindingLocation gaussianSplatSHBuffer = {};
PassResourceBindingLocation gaussianSplatViewDataBuffer = {};
}; };
struct DynamicDescriptorSetKey { struct DynamicDescriptorSetKey {
PassLayoutKey passLayout = {}; PassLayoutKey passLayout = {};
Core::uint32 setIndex = 0; Core::uint32 setIndex = 0;
Core::uint64 objectId = 0; Core::uint64 objectId = 0;
const Components::GaussianSplatRendererComponent* gaussianSplatRenderer = nullptr;
const Resources::Material* material = nullptr; const Resources::Material* material = nullptr;
const Resources::GaussianSplat* gaussianSplat = nullptr; const Resources::GaussianSplat* gaussianSplat = nullptr;
@@ -101,6 +111,7 @@ private:
return passLayout == other.passLayout && return passLayout == other.passLayout &&
setIndex == other.setIndex && setIndex == other.setIndex &&
objectId == other.objectId && objectId == other.objectId &&
gaussianSplatRenderer == other.gaussianSplatRenderer &&
material == other.material && material == other.material &&
gaussianSplat == other.gaussianSplat; gaussianSplat == other.gaussianSplat;
} }
@@ -111,6 +122,7 @@ private:
size_t hash = PassLayoutKeyHash()(key.passLayout); size_t hash = PassLayoutKeyHash()(key.passLayout);
hash ^= std::hash<Core::uint32>{}(key.setIndex) + 0x9e3779b9u + (hash << 6) + (hash >> 2); 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 ^= std::hash<Core::uint64>{}(key.objectId) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= reinterpret_cast<size_t>(key.gaussianSplatRenderer) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= reinterpret_cast<size_t>(key.material) + 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); hash ^= reinterpret_cast<size_t>(key.gaussianSplat) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
return hash; return hash;
@@ -120,10 +132,13 @@ private:
struct CachedDescriptorSet { struct CachedDescriptorSet {
OwnedDescriptorSet descriptorSet = {}; OwnedDescriptorSet descriptorSet = {};
Core::uint64 materialVersion = 0; Core::uint64 materialVersion = 0;
RHI::RHIResourceView* sortDistanceView = nullptr;
RHI::RHIResourceView* orderView = nullptr;
RHI::RHIResourceView* positionsView = nullptr; RHI::RHIResourceView* positionsView = nullptr;
RHI::RHIResourceView* otherView = nullptr; RHI::RHIResourceView* otherView = nullptr;
RHI::RHIResourceView* colorView = nullptr; RHI::RHIResourceView* colorView = nullptr;
RHI::RHIResourceView* shView = nullptr; RHI::RHIResourceView* shView = nullptr;
RHI::RHIResourceView* viewDataView = nullptr;
}; };
struct ResolvedShaderPass { struct ResolvedShaderPass {
@@ -171,6 +186,32 @@ private:
} }
}; };
struct ComputePipelineKey {
const Resources::Shader* shader = nullptr;
Containers::String passName;
Containers::String keywordSignature;
bool operator==(const ComputePipelineKey& other) const {
return shader == other.shader &&
passName == other.passName &&
keywordSignature == other.keywordSignature;
}
};
struct ComputePipelineKeyHash {
size_t operator()(const ComputePipelineKey& key) const noexcept {
size_t hash = reinterpret_cast<size_t>(key.shader);
hash ^= std::hash<Containers::String>{}(key.passName) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= std::hash<Containers::String>{}(key.keywordSignature) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
return hash;
}
};
enum class PassLayoutUsage : Core::uint8 {
Draw,
PrepareOrder
};
bool EnsureInitialized(const RenderContext& context); bool EnsureInitialized(const RenderContext& context);
bool CreateResources(const RenderContext& context); bool CreateResources(const RenderContext& context);
void DestroyResources(); void DestroyResources();
@@ -180,14 +221,19 @@ private:
ResolvedShaderPass ResolveGaussianSplatShaderPass( ResolvedShaderPass ResolveGaussianSplatShaderPass(
const RenderSceneData& sceneData, const RenderSceneData& sceneData,
const Resources::Material* material) const; const Resources::Material* material) const;
ResolvedShaderPass ResolvePrepareOrderShaderPass(const RenderSceneData& sceneData) const;
PassResourceLayout* GetOrCreatePassResourceLayout( PassResourceLayout* GetOrCreatePassResourceLayout(
const RenderContext& context, const RenderContext& context,
const ResolvedShaderPass& resolvedShaderPass); const ResolvedShaderPass& resolvedShaderPass,
PassLayoutUsage usage);
RHI::RHIPipelineState* GetOrCreatePipelineState( RHI::RHIPipelineState* GetOrCreatePipelineState(
const RenderContext& context, const RenderContext& context,
const RenderSurface& surface, const RenderSurface& surface,
const RenderSceneData& sceneData, const RenderSceneData& sceneData,
const Resources::Material* material); const Resources::Material* material);
RHI::RHIPipelineState* GetOrCreateComputePipelineState(
const RenderContext& context,
const RenderSceneData& sceneData);
bool CreateOwnedDescriptorSet( bool CreateOwnedDescriptorSet(
const BuiltinPassSetLayoutMetadata& setLayout, const BuiltinPassSetLayoutMetadata& setLayout,
OwnedDescriptorSet& descriptorSet); OwnedDescriptorSet& descriptorSet);
@@ -197,12 +243,20 @@ private:
const BuiltinPassSetLayoutMetadata& setLayout, const BuiltinPassSetLayoutMetadata& setLayout,
Core::uint32 setIndex, Core::uint32 setIndex,
Core::uint64 objectId, Core::uint64 objectId,
const Components::GaussianSplatRendererComponent* gaussianSplatRenderer,
const Resources::Material* material, const Resources::Material* material,
const Resources::GaussianSplat* gaussianSplat, const Resources::GaussianSplat* gaussianSplat,
const MaterialConstantPayloadView& materialConstants, const MaterialConstantPayloadView& materialConstants,
const RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat); const RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat,
RHI::RHIResourceView* sortDistanceView,
RHI::RHIResourceView* orderView,
RHI::RHIResourceView* viewDataView);
void DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet); void DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet);
void DestroyPassResourceLayout(PassResourceLayout& passLayout); void DestroyPassResourceLayout(PassResourceLayout& passLayout);
bool PrepareVisibleGaussianSplat(
const RenderContext& context,
const RenderSceneData& sceneData,
const VisibleGaussianSplatItem& visibleGaussianSplat);
bool DrawVisibleGaussianSplat( bool DrawVisibleGaussianSplat(
const RenderContext& context, const RenderContext& context,
const RenderSurface& surface, const RenderSurface& surface,
@@ -212,11 +266,14 @@ private:
RHI::RHIDevice* m_device = nullptr; RHI::RHIDevice* m_device = nullptr;
RHI::RHIType m_backendType = RHI::RHIType::D3D12; RHI::RHIType m_backendType = RHI::RHIType::D3D12;
Resources::ResourceHandle<Resources::Shader> m_builtinGaussianSplatShader; Resources::ResourceHandle<Resources::Shader> m_builtinGaussianSplatShader;
Resources::ResourceHandle<Resources::Shader> m_builtinGaussianSplatUtilitiesShader;
std::unique_ptr<Resources::Material> m_builtinGaussianSplatMaterial; std::unique_ptr<Resources::Material> m_builtinGaussianSplatMaterial;
RenderResourceCache m_resourceCache; RenderResourceCache m_resourceCache;
Internal::BuiltinGaussianSplatPassResources* m_passResources = nullptr;
std::unordered_map<PassLayoutKey, PassResourceLayout, PassLayoutKeyHash> m_passResourceLayouts; std::unordered_map<PassLayoutKey, PassResourceLayout, PassLayoutKeyHash> m_passResourceLayouts;
std::unordered_map<PipelineStateKey, RHI::RHIPipelineState*, PipelineStateKeyHash> m_pipelineStates; std::unordered_map<PipelineStateKey, RHI::RHIPipelineState*, PipelineStateKeyHash> m_pipelineStates;
std::unordered_map<ComputePipelineKey, RHI::RHIPipelineState*, ComputePipelineKeyHash> m_computePipelineStates;
std::unordered_map<DynamicDescriptorSetKey, CachedDescriptorSet, DynamicDescriptorSetKeyHash> m_dynamicDescriptorSets; std::unordered_map<DynamicDescriptorSetKey, CachedDescriptorSet, DynamicDescriptorSetKeyHash> m_dynamicDescriptorSets;
}; };

View File

@@ -87,6 +87,11 @@ struct ShaderResourceBindingDesc {
Containers::String semantic; Containers::String semantic;
}; };
struct ShaderBackendCompiledBinary {
ShaderBackend backend = ShaderBackend::Generic;
Containers::Array<Core::uint8> payload;
};
struct ShaderStageVariant { struct ShaderStageVariant {
ShaderType stage = ShaderType::Fragment; ShaderType stage = ShaderType::Fragment;
ShaderLanguage language = ShaderLanguage::GLSL; ShaderLanguage language = ShaderLanguage::GLSL;
@@ -96,6 +101,16 @@ struct ShaderStageVariant {
Containers::String profile; Containers::String profile;
Containers::String sourceCode; Containers::String sourceCode;
Containers::Array<Core::uint8> compiledBinary; Containers::Array<Core::uint8> compiledBinary;
Containers::Array<ShaderBackendCompiledBinary> backendCompiledBinaries;
const Containers::Array<Core::uint8>* GetCompiledBinaryForBackend(
ShaderBackend targetBackend) const;
void SetCompiledBinaryForBackend(
ShaderBackend targetBackend,
const Containers::Array<Core::uint8>& binary);
void SetCompiledBinaryForBackend(
ShaderBackend targetBackend,
Containers::Array<Core::uint8>&& binary);
}; };
struct ShaderPass { struct ShaderPass {

View File

@@ -1,23 +1,33 @@
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEngine/Core/Asset/AssetDatabase.h> #include <XCEngine/Core/Asset/AssetDatabase.h>
#include <XCEngine/Core/Asset/ArtifactFormats.h> #include <XCEngine/Core/Asset/ArtifactFormats.h>
#include <XCEngine/Debug/Logger.h> #include <XCEngine/Debug/Logger.h>
#include <XCEngine/RHI/D3D12/D3D12Shader.h>
#include <XCEngine/RHI/ShaderCompiler/SpirvShaderCompiler.h>
#include <XCEngine/RHI/Vulkan/VulkanShaderCompiler.h>
#include <XCEngine/Resources/GaussianSplat/GaussianSplatArtifactIO.h> #include <XCEngine/Resources/GaussianSplat/GaussianSplatArtifactIO.h>
#include <XCEngine/Resources/Material/MaterialLoader.h> #include <XCEngine/Resources/Material/MaterialLoader.h>
#include <XCEngine/Resources/Mesh/MeshImportSettings.h> #include <XCEngine/Resources/Mesh/MeshImportSettings.h>
#include <XCEngine/Resources/Mesh/MeshLoader.h> #include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Model/AssimpModelImporter.h> #include <XCEngine/Resources/Model/AssimpModelImporter.h>
#include <XCEngine/Resources/Model/ModelArtifactIO.h> #include <XCEngine/Resources/Model/ModelArtifactIO.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include "Resources/GaussianSplat/Internal/GaussianSplatPlyImporter.h" #include "Resources/GaussianSplat/Internal/GaussianSplatPlyImporter.h"
#include <XCEngine/Resources/Shader/ShaderLoader.h> #include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <XCEngine/Resources/Texture/TextureLoader.h> #include <XCEngine/Resources/Texture/TextureLoader.h>
#include <XCEngine/Resources/UI/UIDocumentCompiler.h> #include <XCEngine/Resources/UI/UIDocumentCompiler.h>
#include <XCEngine/Resources/Volume/VolumeField.h> #include <XCEngine/Resources/Volume/VolumeField.h>
#include <XCEngine/Resources/Volume/VolumeFieldLoader.h> #include <XCEngine/Resources/Volume/VolumeFieldLoader.h>
#include "Rendering/Internal/ShaderVariantUtils.h"
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <cstring> #include <cstring>
#include <d3dcompiler.h>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
@@ -134,10 +144,294 @@ bool IsCurrentShaderArtifactFile(const fs::path& artifactPath) {
return false; return false;
} }
return std::memcmp(header.magic, "XCSHD05", 7) == 0 && return std::memcmp(header.magic, "XCSHD06", 7) == 0 &&
header.schemaVersion == kShaderArtifactSchemaVersion; header.schemaVersion == kShaderArtifactSchemaVersion;
} }
bool IsManagedProjectAssetPath(const Containers::String& relativePath) {
return relativePath == "Assets" || relativePath.StartsWith("Assets/");
}
bool UsesExternalSyntheticSourceRecord(const Containers::String& relativePath) {
return !relativePath.Empty() && !IsManagedProjectAssetPath(relativePath);
}
Containers::String BuildSyntheticMetaHash(
const Containers::String& relativePath,
const Containers::String& importerName,
Core::uint32 importerVersion) {
Containers::String signature = relativePath;
signature += ":";
signature += importerName;
signature += ":";
signature += Containers::String(std::to_string(importerVersion).c_str());
return HashStringToAssetGUID(signature).ToString();
}
const char* GetShaderStageLabel(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex:
return "Vertex";
case ShaderType::Fragment:
return "Fragment";
case ShaderType::Geometry:
return "Geometry";
case ShaderType::Compute:
return "Compute";
case ShaderType::Hull:
return "Hull";
case ShaderType::Domain:
return "Domain";
default:
return "Unknown";
}
}
const char* GetShaderBackendLabel(ShaderBackend backend) {
switch (backend) {
case ShaderBackend::D3D12:
return "D3D12";
case ShaderBackend::OpenGL:
return "OpenGL";
case ShaderBackend::Vulkan:
return "Vulkan";
case ShaderBackend::Generic:
default:
return "Generic";
}
}
void AppendCompiledPayload(
const std::vector<uint8_t>& source,
Containers::Array<Core::uint8>& destination) {
destination.Resize(source.size());
if (!source.empty()) {
std::memcpy(destination.Data(), source.data(), source.size());
}
}
void AppendCompiledPayload(
const std::vector<uint32_t>& source,
Containers::Array<Core::uint8>& destination) {
destination.Resize(source.size() * sizeof(uint32_t));
if (!source.empty()) {
std::memcpy(destination.Data(), source.data(), destination.Size());
}
}
std::string NarrowAscii(const std::wstring& value) {
std::string result;
result.reserve(value.size());
for (wchar_t ch : value) {
result.push_back(static_cast<char>(ch));
}
return result;
}
bool BuildD3D12MacroTable(const RHI::ShaderCompileDesc& desc,
std::vector<std::string>& outNames,
std::vector<std::string>& outDefinitions,
std::vector<D3D_SHADER_MACRO>& outTable) {
outNames.clear();
outDefinitions.clear();
outTable.clear();
if (desc.macros.empty()) {
return true;
}
outNames.reserve(desc.macros.size());
outDefinitions.reserve(desc.macros.size());
outTable.reserve(desc.macros.size() + 1u);
for (const RHI::ShaderCompileMacro& macro : desc.macros) {
std::string name;
name.reserve(macro.name.size());
for (wchar_t ch : macro.name) {
name.push_back(static_cast<char>(ch));
}
if (name.empty()) {
continue;
}
std::string definition;
definition.reserve(macro.definition.size());
for (wchar_t ch : macro.definition) {
definition.push_back(static_cast<char>(ch));
}
outNames.push_back(std::move(name));
outDefinitions.push_back(std::move(definition));
}
for (size_t macroIndex = 0; macroIndex < outNames.size(); ++macroIndex) {
D3D_SHADER_MACRO macro = {};
macro.Name = outNames[macroIndex].c_str();
macro.Definition =
outDefinitions[macroIndex].empty() ? "1" : outDefinitions[macroIndex].c_str();
outTable.push_back(macro);
}
outTable.push_back({ nullptr, nullptr });
return true;
}
bool TryPrecompileShaderVariantForBackend(const Containers::String& shaderPath,
const ShaderPass& pass,
ShaderStageVariant& variant,
ShaderBackend targetBackend,
Containers::String& outError) {
outError.Clear();
RHI::ShaderCompileDesc compileDesc = {};
Rendering::Internal::ApplyShaderStageVariant(
shaderPath,
pass,
targetBackend,
variant,
compileDesc);
if (targetBackend == ShaderBackend::D3D12) {
if (compileDesc.sourceLanguage != RHI::ShaderLanguage::HLSL) {
outError = "D3D12 precompile requires HLSL input.";
return false;
}
const std::string entryPoint = NarrowAscii(compileDesc.entryPoint);
const std::string profile = NarrowAscii(compileDesc.profile);
std::vector<std::string> macroNames;
std::vector<std::string> macroDefinitions;
std::vector<D3D_SHADER_MACRO> macroTable;
BuildD3D12MacroTable(compileDesc, macroNames, macroDefinitions, macroTable);
const D3D_SHADER_MACRO* macroPtr = macroTable.empty() ? nullptr : macroTable.data();
RHI::D3D12Shader compiledShader;
if (!compiledShader.Compile(
compileDesc.source.data(),
compileDesc.source.size(),
compileDesc.fileName.empty() ? nullptr : compileDesc.fileName.c_str(),
macroPtr,
entryPoint.empty() ? nullptr : entryPoint.c_str(),
profile.empty() ? nullptr : profile.c_str())) {
outError = "FXC compile failed.";
return false;
}
Containers::Array<Core::uint8> payload;
payload.Resize(compiledShader.GetBytecodeSize());
if (compiledShader.GetBytecodeSize() > 0) {
std::memcpy(
payload.Data(),
compiledShader.GetBytecode(),
compiledShader.GetBytecodeSize());
}
variant.SetCompiledBinaryForBackend(targetBackend, std::move(payload));
return true;
}
RHI::CompiledSpirvShader compiledShader = {};
std::string errorMessage;
const bool compiled =
targetBackend == ShaderBackend::Vulkan
? RHI::CompileVulkanShader(compileDesc, compiledShader, &errorMessage)
: RHI::CompileSpirvShader(
compileDesc,
RHI::SpirvTargetEnvironment::OpenGL,
compiledShader,
&errorMessage);
if (!compiled) {
outError = Containers::String(
errorMessage.empty() ? "SPIR-V compile failed." : errorMessage.c_str());
return false;
}
Containers::Array<Core::uint8> payload;
AppendCompiledPayload(compiledShader.spirvWords, payload);
variant.SetCompiledBinaryForBackend(targetBackend, std::move(payload));
return true;
}
void PrecompileShaderVariants(const Containers::String& shaderPath, Shader& shader) {
Containers::Array<Containers::String> passNames;
passNames.Reserve(shader.GetPasses().Size());
for (const ShaderPass& pass : shader.GetPasses()) {
passNames.PushBack(pass.name);
}
for (const Containers::String& passName : passNames) {
ShaderPass* pass = shader.FindPass(passName);
if (pass == nullptr) {
continue;
}
for (ShaderStageVariant& variant : pass->variants) {
Containers::Array<ShaderBackend> targetBackends;
if (variant.backend == ShaderBackend::Generic) {
targetBackends.PushBack(ShaderBackend::D3D12);
targetBackends.PushBack(ShaderBackend::OpenGL);
targetBackends.PushBack(ShaderBackend::Vulkan);
} else {
targetBackends.PushBack(variant.backend);
}
for (ShaderBackend targetBackend : targetBackends) {
Containers::String errorMessage;
if (TryPrecompileShaderVariantForBackend(
shaderPath,
*pass,
variant,
targetBackend,
errorMessage)) {
continue;
}
Debug::Logger::Get().Warning(
Debug::LogCategory::Rendering,
Containers::String("[AssetDatabase] Shader backend precompile skipped path=") +
shaderPath +
" pass=" +
passName +
" stage=" +
GetShaderStageLabel(variant.stage) +
" backend=" +
GetShaderBackendLabel(targetBackend) +
" reason=" +
errorMessage);
}
}
}
}
std::vector<fs::path> CollectBuiltinShaderAssetPaths() {
std::vector<fs::path> paths;
paths.reserve(14u);
Containers::String resolvedPath;
const Containers::String builtinShaderPaths[] = {
GetBuiltinForwardLitShaderPath(),
GetBuiltinUnlitShaderPath(),
GetBuiltinDepthOnlyShaderPath(),
GetBuiltinShadowCasterShaderPath(),
GetBuiltinObjectIdShaderPath(),
GetBuiltinObjectIdOutlineShaderPath(),
GetBuiltinSelectionMaskShaderPath(),
GetBuiltinSelectionOutlineShaderPath(),
GetBuiltinSkyboxShaderPath(),
GetBuiltinGaussianSplatShaderPath(),
GetBuiltinGaussianSplatUtilitiesShaderPath(),
GetBuiltinVolumetricShaderPath(),
GetBuiltinColorScalePostProcessShaderPath(),
GetBuiltinFinalColorShaderPath()
};
for (const Containers::String& builtinPath : builtinShaderPaths) {
if (TryResolveBuiltinShaderAssetPath(builtinPath, resolvedPath) && !resolvedPath.Empty()) {
paths.push_back(fs::path(resolvedPath.CStr()));
}
}
return paths;
}
std::string TrimCopy(const std::string& text) { std::string TrimCopy(const std::string& text) {
const auto begin = std::find_if_not(text.begin(), text.end(), [](unsigned char ch) { const auto begin = std::find_if_not(text.begin(), text.end(), [](unsigned char ch) {
return std::isspace(ch) != 0; return std::isspace(ch) != 0;
@@ -714,6 +1008,8 @@ bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader)
variantHeader.keywordCount = variantHeader.keywordCount =
static_cast<Core::uint32>(variant.requiredKeywords.enabledKeywords.Size()); static_cast<Core::uint32>(variant.requiredKeywords.enabledKeywords.Size());
variantHeader.compiledBinarySize = static_cast<Core::uint64>(variant.compiledBinary.Size()); variantHeader.compiledBinarySize = static_cast<Core::uint64>(variant.compiledBinary.Size());
variantHeader.backendCompiledBinaryCount =
static_cast<Core::uint32>(variant.backendCompiledBinaries.Size());
output.write(reinterpret_cast<const char*>(&variantHeader), sizeof(variantHeader)); output.write(reinterpret_cast<const char*>(&variantHeader), sizeof(variantHeader));
WriteString(output, variant.entryPoint); WriteString(output, variant.entryPoint);
@@ -727,6 +1023,17 @@ bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader)
reinterpret_cast<const char*>(variant.compiledBinary.Data()), reinterpret_cast<const char*>(variant.compiledBinary.Data()),
static_cast<std::streamsize>(variant.compiledBinary.Size())); static_cast<std::streamsize>(variant.compiledBinary.Size()));
} }
for (const ShaderBackendCompiledBinary& record : variant.backendCompiledBinaries) {
ShaderBackendCompiledBinaryArtifactHeader binaryHeader;
binaryHeader.backend = static_cast<Core::uint32>(record.backend);
binaryHeader.compiledBinarySize = static_cast<Core::uint64>(record.payload.Size());
output.write(reinterpret_cast<const char*>(&binaryHeader), sizeof(binaryHeader));
if (!record.payload.Empty()) {
output.write(
reinterpret_cast<const char*>(record.payload.Data()),
static_cast<std::streamsize>(record.payload.Size()));
}
}
} }
} }
@@ -843,12 +1150,7 @@ bool AssetDatabase::ResolvePath(const Containers::String& requestPath,
const fs::path projectRootPath(m_projectRoot.CStr()); const fs::path projectRootPath(m_projectRoot.CStr());
const fs::path relativePath = fs::relative(inputPath, projectRootPath, ec); const fs::path relativePath = fs::relative(inputPath, projectRootPath, ec);
if (!ec) { if (!ec) {
const Containers::String normalizedRelative = NormalizePathString(relativePath); outRelativePath = NormalizePathString(relativePath);
if (normalizedRelative.StartsWith("Assets/") || normalizedRelative == "Assets") {
outRelativePath = normalizedRelative;
} else {
outRelativePath.Clear();
}
} else { } else {
outRelativePath.Clear(); outRelativePath.Clear();
} }
@@ -1300,6 +1602,9 @@ AssetDatabase::MaintenanceStats AssetDatabase::ScanAssets() {
if (fs::exists(assetsRootPath)) { if (fs::exists(assetsRootPath)) {
ScanAssetPath(assetsRootPath, seenPaths); ScanAssetPath(assetsRootPath, seenPaths);
} }
for (const fs::path& builtinShaderPath : CollectBuiltinShaderAssetPaths()) {
ScanAssetPath(builtinShaderPath, seenPaths);
}
RemoveMissingRecords(seenPaths); RemoveMissingRecords(seenPaths);
stats.removedArtifactCount = CleanupOrphanedArtifacts(); stats.removedArtifactCount = CleanupOrphanedArtifacts();
SaveSourceAssetDB(); SaveSourceAssetDB();
@@ -1446,6 +1751,47 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
outRecord.importerName = GetImporterNameForPath(relativePath, isFolder); outRecord.importerName = GetImporterNameForPath(relativePath, isFolder);
outRecord.importerVersion = kCurrentImporterVersion; outRecord.importerVersion = kCurrentImporterVersion;
if (UsesExternalSyntheticSourceRecord(relativePath)) {
if (!outRecord.guid.IsValid()) {
outRecord.guid = HashStringToAssetGUID(NormalizePathString(sourcePath));
}
outRecord.metaPath.Clear();
outRecord.metaHash =
BuildSyntheticMetaHash(relativePath, outRecord.importerName, outRecord.importerVersion);
const auto duplicateGuidIt = m_sourcesByGuid.find(outRecord.guid);
if (duplicateGuidIt != m_sourcesByGuid.end() &&
duplicateGuidIt->second.relativePath != relativePath) {
Containers::String duplicateSignature = NormalizePathString(sourcePath);
duplicateSignature += ":";
duplicateSignature += relativePath;
outRecord.guid = HashStringToAssetGUID(duplicateSignature);
}
if (isFolder) {
outRecord.sourceHash.Clear();
outRecord.sourceFileSize = 0;
outRecord.sourceWriteTime = 0;
} else {
const Core::uint64 fileSize = GetFileSizeValue(sourcePath);
const Core::uint64 writeTime = GetFileWriteTimeValue(sourcePath);
if (existingIt != m_sourcesByPathKey.end() &&
existingIt->second.sourceFileSize == fileSize &&
existingIt->second.sourceWriteTime == writeTime &&
!existingIt->second.sourceHash.Empty()) {
outRecord.sourceHash = existingIt->second.sourceHash;
} else {
outRecord.sourceHash = ComputeFileHash(sourcePath);
}
outRecord.sourceFileSize = fileSize;
outRecord.sourceWriteTime = writeTime;
}
m_sourcesByPathKey[pathKey] = outRecord;
m_sourcesByGuid[outRecord.guid] = outRecord;
return true;
}
const fs::path metaPath(sourcePath.string() + ".meta"); const fs::path metaPath(sourcePath.string() + ".meta");
outRecord.metaPath = NormalizeRelativePath(metaPath); outRecord.metaPath = NormalizeRelativePath(metaPath);
const Containers::String expectedImporterName = GetImporterNameForPath(relativePath, isFolder); const Containers::String expectedImporterName = GetImporterNameForPath(relativePath, isFolder);
@@ -2100,10 +2446,14 @@ bool AssetDatabase::ImportShaderAsset(const SourceAssetRecord& sourceRecord,
NormalizePathString(fs::path(m_projectRoot.CStr()) / sourceRecord.relativePath.CStr()); NormalizePathString(fs::path(m_projectRoot.CStr()) / sourceRecord.relativePath.CStr());
LoadResult result = loader.Load(absolutePath); LoadResult result = loader.Load(absolutePath);
if (!result || result.resource == nullptr) { if (!result || result.resource == nullptr) {
if (!result.errorMessage.Empty()) {
SetLastErrorMessage(result.errorMessage);
}
return false; return false;
} }
Shader* shader = static_cast<Shader*>(result.resource); Shader* shader = static_cast<Shader*>(result.resource);
PrecompileShaderVariants(absolutePath, *shader);
std::vector<ArtifactDependencyRecord> dependencies; std::vector<ArtifactDependencyRecord> dependencies;
if (!CollectShaderDependencies(sourceRecord, dependencies)) { if (!CollectShaderDependencies(sourceRecord, dependencies)) {
delete shader; delete shader;

View File

@@ -3,6 +3,7 @@
#include <XCEngine/Core/Asset/ResourceTypes.h> #include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Core/IO/ResourceFileSystem.h> #include <XCEngine/Core/IO/ResourceFileSystem.h>
#include <XCEngine/Resources/GaussianSplat/GaussianSplatLoader.h> #include <XCEngine/Resources/GaussianSplat/GaussianSplatLoader.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/MaterialLoader.h> #include <XCEngine/Resources/Material/MaterialLoader.h>
#include <XCEngine/Resources/Model/ModelLoader.h> #include <XCEngine/Resources/Model/ModelLoader.h>
#include <XCEngine/Resources/Mesh/MeshLoader.h> #include <XCEngine/Resources/Mesh/MeshLoader.h>
@@ -548,6 +549,7 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
} }
Containers::String loadPath = path; Containers::String loadPath = path;
Containers::String cachePath = path;
AssetImportService::ImportedAsset resolvedAsset; AssetImportService::ImportedAsset resolvedAsset;
ResourceType importableType = ResourceType::Unknown; ResourceType importableType = ResourceType::Unknown;
const bool shouldUseProjectArtifact = const bool shouldUseProjectArtifact =
@@ -560,6 +562,7 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
resolvedAsset.artifactReady) { resolvedAsset.artifactReady) {
m_projectAssetIndex.RememberResolvedPath(resolvedAsset.assetGuid, resolvedAsset.relativePath); m_projectAssetIndex.RememberResolvedPath(resolvedAsset.assetGuid, resolvedAsset.relativePath);
loadPath = resolvedAsset.runtimeLoadPath; loadPath = resolvedAsset.runtimeLoadPath;
cachePath = loadPath;
if (ShouldTraceResourcePath(path)) { if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info( Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem, Debug::LogCategory::FileSystem,
@@ -568,6 +571,35 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
" artifact=" + " artifact=" +
loadPath); loadPath);
} }
} else if (type == ResourceType::Shader && IsBuiltinShaderPath(path)) {
Containers::String builtinShaderAssetPath;
if (TryResolveBuiltinShaderAssetPath(path, builtinShaderAssetPath)) {
ResourceType builtinImportableType = ResourceType::Unknown;
AssetImportService::ImportedAsset builtinResolvedAsset;
if (!m_resourceRoot.Empty() &&
m_assetImportService.TryGetImportableResourceType(
builtinShaderAssetPath,
builtinImportableType) &&
builtinImportableType == type &&
m_assetImportService.EnsureArtifact(
builtinShaderAssetPath,
type,
builtinResolvedAsset) &&
builtinResolvedAsset.artifactReady) {
loadPath = builtinResolvedAsset.runtimeLoadPath;
} else {
loadPath = builtinShaderAssetPath;
}
}
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] LoadResource builtin shader path=") +
path +
" loadPath=" +
loadPath);
}
} else if (ShouldTraceResourcePath(path)) { } else if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info( Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem, Debug::LogCategory::FileSystem,
@@ -577,7 +609,7 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
loadPath); loadPath);
} }
const ResourceGUID guid = ResourceGUID::Generate(loadPath); const ResourceGUID guid = ResourceGUID::Generate(cachePath);
if (IResource* cached = FindInCache(guid)) { if (IResource* cached = FindInCache(guid)) {
if (ShouldTraceResourcePath(path)) { if (ShouldTraceResourcePath(path)) {

View File

@@ -24,6 +24,33 @@ std::string NarrowAscii(const std::wstring& value) {
return result; return result;
} }
ShaderType ResolveShaderTypeFromTarget(const char* target) {
if (target == nullptr) {
return ShaderType::Vertex;
}
if (strstr(target, "vs_")) {
return ShaderType::Vertex;
}
if (strstr(target, "ps_")) {
return ShaderType::Fragment;
}
if (strstr(target, "gs_")) {
return ShaderType::Geometry;
}
if (strstr(target, "cs_")) {
return ShaderType::Compute;
}
if (strstr(target, "hs_")) {
return ShaderType::Hull;
}
if (strstr(target, "ds_")) {
return ShaderType::Domain;
}
return ShaderType::Vertex;
}
class D3D12ShaderIncludeHandler final : public ID3DInclude { class D3D12ShaderIncludeHandler final : public ID3DInclude {
public: public:
explicit D3D12ShaderIncludeHandler(const std::filesystem::path& sourcePath) explicit D3D12ShaderIncludeHandler(const std::filesystem::path& sourcePath)
@@ -127,15 +154,7 @@ bool D3D12Shader::CompileFromFile(const wchar_t* filePath, const char* entryPoin
return false; return false;
} }
if (strstr(target, "vs_")) { m_type = ResolveShaderTypeFromTarget(target);
m_type = ShaderType::Vertex;
} else if (strstr(target, "ps_")) {
m_type = ShaderType::Fragment;
} else if (strstr(target, "gs_")) {
m_type = ShaderType::Geometry;
} else if (strstr(target, "cs_")) {
m_type = ShaderType::Compute;
}
m_uniformsCached = false; m_uniformsCached = false;
return true; return true;
@@ -182,16 +201,29 @@ bool D3D12Shader::Compile(const void* sourceData,
return false; return false;
} }
if (strstr(target, "vs_")) { m_type = ResolveShaderTypeFromTarget(target);
m_type = ShaderType::Vertex;
} else if (strstr(target, "ps_")) { m_uniformsCached = false;
m_type = ShaderType::Fragment; return true;
} else if (strstr(target, "gs_")) { }
m_type = ShaderType::Geometry;
} else if (strstr(target, "cs_")) { bool D3D12Shader::InitializeFromBytecode(const void* bytecodeData,
m_type = ShaderType::Compute; size_t bytecodeSize,
const char* target) {
if (bytecodeData == nullptr || bytecodeSize == 0) {
return false;
} }
Shutdown();
ComPtr<ID3DBlob> blob;
if (FAILED(D3DCreateBlob(bytecodeSize, &blob)) || blob == nullptr) {
return false;
}
std::memcpy(blob->GetBufferPointer(), bytecodeData, bytecodeSize);
m_bytecode = std::move(blob);
m_type = ResolveShaderTypeFromTarget(target);
m_uniformsCached = false; m_uniformsCached = false;
return true; return true;
} }

View File

@@ -272,6 +272,21 @@ bool IsHlslInput(const ShaderCompileDesc& desc) {
return extension == ".hlsl"; return extension == ".hlsl";
} }
bool HasMatchingCompiledSpirvBinary(const ShaderCompileDesc& desc,
SpirvTargetEnvironment targetEnvironment) {
if (desc.compiledBinary.empty()) {
return false;
}
switch (targetEnvironment) {
case SpirvTargetEnvironment::OpenGL:
return desc.compiledBinaryBackend == ShaderBinaryBackend::OpenGL;
case SpirvTargetEnvironment::Vulkan:
default:
return desc.compiledBinaryBackend == ShaderBinaryBackend::Vulkan;
}
}
std::wstring GetEnvironmentVariableValue(const wchar_t* name) { std::wstring GetEnvironmentVariableValue(const wchar_t* name) {
const DWORD length = GetEnvironmentVariableW(name, nullptr, 0); const DWORD length = GetEnvironmentVariableW(name, nullptr, 0);
if (length == 0) { if (length == 0) {
@@ -1021,16 +1036,19 @@ bool CompileSpirvShader(const ShaderCompileDesc& desc,
outShader.entryPoint = "main"; outShader.entryPoint = "main";
} }
if (desc.source.empty() && desc.fileName.empty()) { if (desc.source.empty() && desc.fileName.empty() &&
!HasMatchingCompiledSpirvBinary(desc, targetEnvironment)) {
if (errorMessage != nullptr) { if (errorMessage != nullptr) {
*errorMessage = "No shader source or file name was provided."; *errorMessage = "No shader source or file name was provided.";
} }
return false; return false;
} }
if (IsSpirvInput(desc)) { if (IsSpirvInput(desc) || HasMatchingCompiledSpirvBinary(desc, targetEnvironment)) {
std::vector<uint8_t> bytes; std::vector<uint8_t> bytes;
if (!desc.source.empty()) { if (HasMatchingCompiledSpirvBinary(desc, targetEnvironment)) {
bytes = desc.compiledBinary;
} else if (!desc.source.empty()) {
bytes = desc.source; bytes = desc.source;
} else if (!LoadBinaryFile(std::filesystem::path(desc.fileName), bytes)) { } else if (!LoadBinaryFile(std::filesystem::path(desc.fileName), bytes)) {
if (errorMessage != nullptr) { if (errorMessage != nullptr) {

View File

@@ -39,6 +39,20 @@ inline RHI::ShaderLanguage ToRHIShaderLanguage(Resources::ShaderLanguage languag
} }
} }
inline RHI::ShaderBinaryBackend ToRHIShaderBinaryBackend(Resources::ShaderBackend backend) {
switch (backend) {
case Resources::ShaderBackend::D3D12:
return RHI::ShaderBinaryBackend::D3D12;
case Resources::ShaderBackend::OpenGL:
return RHI::ShaderBinaryBackend::OpenGL;
case Resources::ShaderBackend::Vulkan:
return RHI::ShaderBinaryBackend::Vulkan;
case Resources::ShaderBackend::Generic:
default:
return RHI::ShaderBinaryBackend::Unknown;
}
}
inline std::wstring ToWideAscii(const Containers::String& value) { inline std::wstring ToWideAscii(const Containers::String& value) {
std::wstring wide; std::wstring wide;
wide.reserve(value.Length()); wide.reserve(value.Length());
@@ -360,6 +374,13 @@ inline void ApplyShaderStageVariant(
compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language); compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language);
compileDesc.entryPoint = ToWideAscii(variant.entryPoint); compileDesc.entryPoint = ToWideAscii(variant.entryPoint);
compileDesc.profile = ToWideAscii(variant.profile); compileDesc.profile = ToWideAscii(variant.profile);
compileDesc.compiledBinaryBackend = ToRHIShaderBinaryBackend(variant.backend);
compileDesc.compiledBinary.clear();
if (!variant.compiledBinary.Empty()) {
compileDesc.compiledBinary.assign(
variant.compiledBinary.Data(),
variant.compiledBinary.Data() + variant.compiledBinary.Size());
}
} }
inline std::wstring ResolveRuntimeShaderSourcePath(const Containers::String& shaderPath) { inline std::wstring ResolveRuntimeShaderSourcePath(const Containers::String& shaderPath) {
@@ -398,6 +419,12 @@ inline void ApplyShaderStageVariant(
compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language); compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language);
compileDesc.entryPoint = ToWideAscii(variant.entryPoint); compileDesc.entryPoint = ToWideAscii(variant.entryPoint);
compileDesc.profile = ToWideAscii(variant.profile); compileDesc.profile = ToWideAscii(variant.profile);
compileDesc.compiledBinaryBackend = RHI::ShaderBinaryBackend::Unknown;
compileDesc.compiledBinary.clear();
if (const Containers::Array<Core::uint8>* binary = variant.GetCompiledBinaryForBackend(backend)) {
compileDesc.compiledBinaryBackend = ToRHIShaderBinaryBackend(backend);
compileDesc.compiledBinary.assign(binary->Data(), binary->Data() + binary->Size());
}
InjectShaderBackendMacros(backend, compileDesc); InjectShaderBackendMacros(backend, compileDesc);
} }

View File

@@ -9,6 +9,7 @@
#include "Rendering/FrameData/VisibleGaussianSplatItem.h" #include "Rendering/FrameData/VisibleGaussianSplatItem.h"
#include "Rendering/Internal/RenderSurfacePipelineUtils.h" #include "Rendering/Internal/RenderSurfacePipelineUtils.h"
#include "Rendering/Internal/ShaderVariantUtils.h" #include "Rendering/Internal/ShaderVariantUtils.h"
#include "Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h"
#include "Rendering/RenderSurface.h" #include "Rendering/RenderSurface.h"
#include "Resources/BuiltinResources.h" #include "Resources/BuiltinResources.h"
#include "Resources/GaussianSplat/GaussianSplat.h" #include "Resources/GaussianSplat/GaussianSplat.h"
@@ -49,6 +50,21 @@ const Resources::ShaderPass* FindCompatibleGaussianSplatPass(
return nullptr; return nullptr;
} }
const Resources::ShaderPass* FindCompatibleComputePass(
const Resources::Shader& shader,
const Containers::String& passName,
const Resources::ShaderKeywordSet& keywordSet,
Resources::ShaderBackend backend) {
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
if (shaderPass == nullptr) {
return nullptr;
}
return shader.FindVariant(passName, Resources::ShaderType::Compute, backend, keywordSet) != nullptr
? shaderPass
: nullptr;
}
RHI::GraphicsPipelineDesc CreatePipelineDesc( RHI::GraphicsPipelineDesc CreatePipelineDesc(
RHI::RHIType backendType, RHI::RHIType backendType,
RHI::RHIPipelineLayout* pipelineLayout, RHI::RHIPipelineLayout* pipelineLayout,
@@ -89,6 +105,88 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
return pipelineDesc; return pipelineDesc;
} }
RHI::ComputePipelineDesc CreateComputePipelineDesc(
RHI::RHIType backendType,
RHI::RHIPipelineLayout* pipelineLayout,
const Resources::Shader& shader,
const Resources::ShaderPass& shaderPass,
const Containers::String& passName,
const Resources::ShaderKeywordSet& keywordSet) {
RHI::ComputePipelineDesc pipelineDesc = {};
pipelineDesc.pipelineLayout = pipelineLayout;
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
if (const Resources::ShaderStageVariant* computeVariant =
shader.FindVariant(passName, Resources::ShaderType::Compute, backend, keywordSet)) {
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
shader.GetPath(),
shaderPass,
backend,
*computeVariant,
pipelineDesc.computeShader);
}
return pipelineDesc;
}
const RHI::DescriptorSetLayoutBinding* FindSetLayoutBinding(
const BuiltinPassSetLayoutMetadata& setLayout,
Core::uint32 binding) {
for (const RHI::DescriptorSetLayoutBinding& layoutBinding : setLayout.bindings) {
if (layoutBinding.binding == binding) {
return &layoutBinding;
}
}
return nullptr;
}
RHI::RHIResourceView* ResolveWorkingSetView(
const Internal::BuiltinGaussianSplatPassResources::CachedBufferView& bufferView,
const RHI::DescriptorSetLayoutBinding* layoutBinding) {
if (layoutBinding == nullptr) {
return nullptr;
}
switch (static_cast<RHI::DescriptorType>(layoutBinding->type)) {
case RHI::DescriptorType::UAV:
return bufferView.unorderedAccessView;
case RHI::DescriptorType::SRV:
return bufferView.shaderResourceView;
default:
return nullptr;
}
}
template <typename BindFn>
void BindDescriptorSetRanges(
Core::uint32 firstDescriptorSet,
std::vector<RHI::RHIDescriptorSet*>& descriptorSets,
BindFn&& bindFn) {
const Core::uint32 descriptorSetCount = static_cast<Core::uint32>(descriptorSets.size());
Core::uint32 rangeStart = 0u;
while (rangeStart < descriptorSetCount) {
while (rangeStart < descriptorSetCount && descriptorSets[rangeStart] == nullptr) {
++rangeStart;
}
if (rangeStart >= descriptorSetCount) {
break;
}
Core::uint32 rangeCount = 0u;
while ((rangeStart + rangeCount) < descriptorSetCount &&
descriptorSets[rangeStart + rangeCount] != nullptr) {
++rangeCount;
}
bindFn(
firstDescriptorSet + rangeStart,
rangeCount,
descriptorSets.data() + rangeStart);
rangeStart += rangeCount;
}
}
} // namespace } // namespace
BuiltinGaussianSplatPass::~BuiltinGaussianSplatPass() { BuiltinGaussianSplatPass::~BuiltinGaussianSplatPass() {
@@ -124,6 +222,19 @@ bool BuiltinGaussianSplatPass::PrepareGaussianSplatResources(
cachedGaussianSplat->color.shaderResourceView == nullptr) { cachedGaussianSplat->color.shaderResourceView == nullptr) {
return false; return false;
} }
if (m_passResources == nullptr) {
return false;
}
Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr;
if (!m_passResources->EnsureWorkingSet(m_device, visibleGaussianSplat, workingSet) ||
workingSet == nullptr ||
workingSet->sortDistances.unorderedAccessView == nullptr ||
workingSet->orderIndices.shaderResourceView == nullptr ||
workingSet->orderIndices.unorderedAccessView == nullptr) {
return false;
}
} }
return true; return true;
@@ -177,6 +288,13 @@ bool BuiltinGaussianSplatPass::Execute(const RenderPassContext& context) {
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
for (const VisibleGaussianSplatItem& visibleGaussianSplat : context.sceneData.visibleGaussianSplats) { for (const VisibleGaussianSplatItem& visibleGaussianSplat : context.sceneData.visibleGaussianSplats) {
if (!PrepareVisibleGaussianSplat(
context.renderContext,
context.sceneData,
visibleGaussianSplat)) {
return false;
}
if (!DrawVisibleGaussianSplat( if (!DrawVisibleGaussianSplat(
context.renderContext, context.renderContext,
context.surface, context.surface,
@@ -222,6 +340,16 @@ bool BuiltinGaussianSplatPass::CreateResources(const RenderContext& context) {
return false; return false;
} }
m_builtinGaussianSplatUtilitiesShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
Resources::GetBuiltinGaussianSplatUtilitiesShaderPath());
if (!m_builtinGaussianSplatUtilitiesShader.IsValid()) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinGaussianSplatPass failed to load builtin gaussian splat utilities shader resource");
DestroyResources();
return false;
}
m_builtinGaussianSplatMaterial = std::make_unique<Resources::Material>(); m_builtinGaussianSplatMaterial = std::make_unique<Resources::Material>();
Resources::IResource::ConstructParams params = {}; Resources::IResource::ConstructParams params = {};
params.name = "BuiltinGaussianSplatMaterial"; params.name = "BuiltinGaussianSplatMaterial";
@@ -230,10 +358,17 @@ bool BuiltinGaussianSplatPass::CreateResources(const RenderContext& context) {
m_builtinGaussianSplatMaterial->Initialize(params); m_builtinGaussianSplatMaterial->Initialize(params);
m_builtinGaussianSplatMaterial->SetShader(m_builtinGaussianSplatShader); m_builtinGaussianSplatMaterial->SetShader(m_builtinGaussianSplatShader);
m_builtinGaussianSplatMaterial->SetRenderQueue(Resources::MaterialRenderQueue::Transparent); m_builtinGaussianSplatMaterial->SetRenderQueue(Resources::MaterialRenderQueue::Transparent);
m_passResources = new Internal::BuiltinGaussianSplatPassResources();
return true; return true;
} }
void BuiltinGaussianSplatPass::DestroyResources() { void BuiltinGaussianSplatPass::DestroyResources() {
if (m_passResources != nullptr) {
m_passResources->Shutdown();
delete m_passResources;
m_passResources = nullptr;
}
m_resourceCache.Shutdown(); m_resourceCache.Shutdown();
for (auto& pipelinePair : m_pipelineStates) { for (auto& pipelinePair : m_pipelineStates) {
@@ -244,6 +379,14 @@ void BuiltinGaussianSplatPass::DestroyResources() {
} }
m_pipelineStates.clear(); m_pipelineStates.clear();
for (auto& pipelinePair : m_computePipelineStates) {
if (pipelinePair.second != nullptr) {
pipelinePair.second->Shutdown();
delete pipelinePair.second;
}
}
m_computePipelineStates.clear();
for (auto& descriptorSetPair : m_dynamicDescriptorSets) { for (auto& descriptorSetPair : m_dynamicDescriptorSets) {
DestroyOwnedDescriptorSet(descriptorSetPair.second.descriptorSet); DestroyOwnedDescriptorSet(descriptorSetPair.second.descriptorSet);
} }
@@ -255,6 +398,7 @@ void BuiltinGaussianSplatPass::DestroyResources() {
m_passResourceLayouts.clear(); m_passResourceLayouts.clear();
m_builtinGaussianSplatMaterial.reset(); m_builtinGaussianSplatMaterial.reset();
m_builtinGaussianSplatUtilitiesShader.Reset();
m_builtinGaussianSplatShader.Reset(); m_builtinGaussianSplatShader.Reset();
m_device = nullptr; m_device = nullptr;
m_backendType = RHI::RHIType::D3D12; m_backendType = RHI::RHIType::D3D12;
@@ -301,9 +445,30 @@ BuiltinGaussianSplatPass::ResolvedShaderPass BuiltinGaussianSplatPass::ResolveGa
return resolved; return resolved;
} }
BuiltinGaussianSplatPass::ResolvedShaderPass BuiltinGaussianSplatPass::ResolvePrepareOrderShaderPass(
const RenderSceneData& sceneData) const {
ResolvedShaderPass resolved = {};
if (!m_builtinGaussianSplatUtilitiesShader.IsValid()) {
return resolved;
}
const Resources::Shader* shader = m_builtinGaussianSplatUtilitiesShader.Get();
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType);
const Containers::String passName("GaussianSplatPrepareOrder");
if (const Resources::ShaderPass* shaderPass =
FindCompatibleComputePass(*shader, passName, sceneData.globalShaderKeywords, backend)) {
resolved.shader = shader;
resolved.pass = shaderPass;
resolved.passName = passName;
}
return resolved;
}
BuiltinGaussianSplatPass::PassResourceLayout* BuiltinGaussianSplatPass::GetOrCreatePassResourceLayout( BuiltinGaussianSplatPass::PassResourceLayout* BuiltinGaussianSplatPass::GetOrCreatePassResourceLayout(
const RenderContext& context, const RenderContext& context,
const ResolvedShaderPass& resolvedShaderPass) { const ResolvedShaderPass& resolvedShaderPass,
PassLayoutUsage usage) {
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
return nullptr; return nullptr;
} }
@@ -345,21 +510,36 @@ BuiltinGaussianSplatPass::PassResourceLayout* BuiltinGaussianSplatPass::GetOrCre
passLayout.descriptorSetCount = bindingPlan.descriptorSetCount; passLayout.descriptorSetCount = bindingPlan.descriptorSetCount;
passLayout.perObject = bindingPlan.perObject; passLayout.perObject = bindingPlan.perObject;
passLayout.material = bindingPlan.material; passLayout.material = bindingPlan.material;
passLayout.gaussianSplatSortDistanceBuffer = bindingPlan.gaussianSplatSortDistanceBuffer;
passLayout.gaussianSplatOrderBuffer = bindingPlan.gaussianSplatOrderBuffer;
passLayout.gaussianSplatPositionBuffer = bindingPlan.gaussianSplatPositionBuffer; passLayout.gaussianSplatPositionBuffer = bindingPlan.gaussianSplatPositionBuffer;
passLayout.gaussianSplatOtherBuffer = bindingPlan.gaussianSplatOtherBuffer; passLayout.gaussianSplatOtherBuffer = bindingPlan.gaussianSplatOtherBuffer;
passLayout.gaussianSplatColorBuffer = bindingPlan.gaussianSplatColorBuffer; passLayout.gaussianSplatColorBuffer = bindingPlan.gaussianSplatColorBuffer;
passLayout.gaussianSplatSHBuffer = bindingPlan.gaussianSplatSHBuffer; passLayout.gaussianSplatSHBuffer = bindingPlan.gaussianSplatSHBuffer;
passLayout.gaussianSplatViewDataBuffer = bindingPlan.gaussianSplatViewDataBuffer;
if (!passLayout.perObject.IsValid()) { if (!passLayout.perObject.IsValid()) {
return failLayout("BuiltinGaussianSplatPass requires a PerObject resource binding"); return failLayout("BuiltinGaussianSplatPass requires a PerObject resource binding");
} }
if (usage == PassLayoutUsage::Draw) {
if (!passLayout.material.IsValid()) { if (!passLayout.material.IsValid()) {
return failLayout("BuiltinGaussianSplatPass requires a Material resource binding"); return failLayout("BuiltinGaussianSplatPass requires a Material resource binding");
} }
if (!passLayout.gaussianSplatPositionBuffer.IsValid() || if (!passLayout.gaussianSplatOrderBuffer.IsValid() ||
!passLayout.gaussianSplatPositionBuffer.IsValid() ||
!passLayout.gaussianSplatOtherBuffer.IsValid() || !passLayout.gaussianSplatOtherBuffer.IsValid() ||
!passLayout.gaussianSplatColorBuffer.IsValid()) { !passLayout.gaussianSplatColorBuffer.IsValid()) {
return failLayout("BuiltinGaussianSplatPass requires position, other, and color gaussian splat buffer bindings"); return failLayout(
"BuiltinGaussianSplatPass draw pass requires order, position, other, and color gaussian splat buffer bindings");
}
} else if (usage == PassLayoutUsage::PrepareOrder) {
if (!passLayout.gaussianSplatSortDistanceBuffer.IsValid() ||
!passLayout.gaussianSplatOrderBuffer.IsValid() ||
!passLayout.gaussianSplatPositionBuffer.IsValid()) {
return failLayout(
"BuiltinGaussianSplatPass prepare-order pass requires sort distance, order, and position gaussian splat buffer bindings");
}
} }
std::vector<RHI::DescriptorSetLayoutDesc> nativeSetLayouts(passLayout.setLayouts.size()); std::vector<RHI::DescriptorSetLayoutDesc> nativeSetLayouts(passLayout.setLayouts.size());
@@ -392,7 +572,10 @@ RHI::RHIPipelineState* BuiltinGaussianSplatPass::GetOrCreatePipelineState(
return nullptr; return nullptr;
} }
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(
context,
resolvedShaderPass,
PassLayoutUsage::Draw);
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
return nullptr; return nullptr;
} }
@@ -440,6 +623,54 @@ RHI::RHIPipelineState* BuiltinGaussianSplatPass::GetOrCreatePipelineState(
return pipelineState; return pipelineState;
} }
RHI::RHIPipelineState* BuiltinGaussianSplatPass::GetOrCreateComputePipelineState(
const RenderContext& context,
const RenderSceneData& sceneData) {
const ResolvedShaderPass resolvedShaderPass = ResolvePrepareOrderShaderPass(sceneData);
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
return nullptr;
}
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(
context,
resolvedShaderPass,
PassLayoutUsage::PrepareOrder);
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
return nullptr;
}
ComputePipelineKey pipelineKey = {};
pipelineKey.shader = resolvedShaderPass.shader;
pipelineKey.passName = resolvedShaderPass.passName;
pipelineKey.keywordSignature =
::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(sceneData.globalShaderKeywords);
const auto existing = m_computePipelineStates.find(pipelineKey);
if (existing != m_computePipelineStates.end()) {
return existing->second;
}
const RHI::ComputePipelineDesc pipelineDesc =
CreateComputePipelineDesc(
context.backendType,
passLayout->pipelineLayout,
*resolvedShaderPass.shader,
*resolvedShaderPass.pass,
resolvedShaderPass.passName,
sceneData.globalShaderKeywords);
RHI::RHIPipelineState* pipelineState = context.device->CreateComputePipelineState(pipelineDesc);
if (pipelineState == nullptr || !pipelineState->IsValid()) {
if (pipelineState != nullptr) {
pipelineState->Shutdown();
delete pipelineState;
}
return nullptr;
}
m_computePipelineStates.emplace(pipelineKey, pipelineState);
return pipelineState;
}
bool BuiltinGaussianSplatPass::CreateOwnedDescriptorSet( bool BuiltinGaussianSplatPass::CreateOwnedDescriptorSet(
const BuiltinPassSetLayoutMetadata& setLayout, const BuiltinPassSetLayoutMetadata& setLayout,
OwnedDescriptorSet& descriptorSet) { OwnedDescriptorSet& descriptorSet) {
@@ -468,14 +699,19 @@ BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCr
const BuiltinPassSetLayoutMetadata& setLayout, const BuiltinPassSetLayoutMetadata& setLayout,
Core::uint32 setIndex, Core::uint32 setIndex,
Core::uint64 objectId, Core::uint64 objectId,
const Components::GaussianSplatRendererComponent* gaussianSplatRenderer,
const Resources::Material* material, const Resources::Material* material,
const Resources::GaussianSplat* gaussianSplat, const Resources::GaussianSplat* gaussianSplat,
const MaterialConstantPayloadView& materialConstants, const MaterialConstantPayloadView& materialConstants,
const RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat) { const RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat,
RHI::RHIResourceView* sortDistanceView,
RHI::RHIResourceView* orderView,
RHI::RHIResourceView* viewDataView) {
DynamicDescriptorSetKey key = {}; DynamicDescriptorSetKey key = {};
key.passLayout = passLayoutKey; key.passLayout = passLayoutKey;
key.setIndex = setIndex; key.setIndex = setIndex;
key.objectId = objectId; key.objectId = objectId;
key.gaussianSplatRenderer = gaussianSplatRenderer;
key.material = material; key.material = material;
key.gaussianSplat = gaussianSplat; key.gaussianSplat = gaussianSplat;
@@ -486,6 +722,41 @@ BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCr
} }
} }
const Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet =
(gaussianSplatRenderer != nullptr && m_passResources != nullptr)
? m_passResources->FindWorkingSet(gaussianSplatRenderer)
: nullptr;
RHI::RHIResourceView* resolvedSortDistanceView = sortDistanceView;
RHI::RHIResourceView* resolvedOrderView = orderView;
RHI::RHIResourceView* resolvedViewDataView = viewDataView;
if (setLayout.usesGaussianSplatSortDistanceBuffer && workingSet != nullptr) {
if (const RHI::DescriptorSetLayoutBinding* layoutBinding =
FindSetLayoutBinding(setLayout, passLayout.gaussianSplatSortDistanceBuffer.binding)) {
if (RHI::RHIResourceView* view = ResolveWorkingSetView(workingSet->sortDistances, layoutBinding)) {
resolvedSortDistanceView = view;
}
}
}
if (setLayout.usesGaussianSplatOrderBuffer && workingSet != nullptr) {
if (const RHI::DescriptorSetLayoutBinding* layoutBinding =
FindSetLayoutBinding(setLayout, passLayout.gaussianSplatOrderBuffer.binding)) {
if (RHI::RHIResourceView* view = ResolveWorkingSetView(workingSet->orderIndices, layoutBinding)) {
resolvedOrderView = view;
}
}
}
if (setLayout.usesGaussianSplatViewDataBuffer && workingSet != nullptr) {
if (const RHI::DescriptorSetLayoutBinding* layoutBinding =
FindSetLayoutBinding(setLayout, passLayout.gaussianSplatViewDataBuffer.binding)) {
if (RHI::RHIResourceView* view = ResolveWorkingSetView(workingSet->viewData, layoutBinding)) {
resolvedViewDataView = view;
}
}
}
const Core::uint64 materialVersion = material != nullptr ? material->GetChangeVersion() : 0u; const Core::uint64 materialVersion = material != nullptr ? material->GetChangeVersion() : 0u;
if (setLayout.usesMaterial) { if (setLayout.usesMaterial) {
if (!passLayout.material.IsValid() || if (!passLayout.material.IsValid() ||
@@ -516,6 +787,34 @@ BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCr
} }
} }
if (setLayout.usesGaussianSplatSortDistanceBuffer) {
if (resolvedSortDistanceView == nullptr ||
!passLayout.gaussianSplatSortDistanceBuffer.IsValid() ||
passLayout.gaussianSplatSortDistanceBuffer.set != setIndex) {
return nullptr;
}
if (cachedDescriptorSet.sortDistanceView != resolvedSortDistanceView) {
cachedDescriptorSet.descriptorSet.set->Update(
passLayout.gaussianSplatSortDistanceBuffer.binding,
resolvedSortDistanceView);
}
}
if (setLayout.usesGaussianSplatOrderBuffer) {
if (resolvedOrderView == nullptr ||
!passLayout.gaussianSplatOrderBuffer.IsValid() ||
passLayout.gaussianSplatOrderBuffer.set != setIndex) {
return nullptr;
}
if (cachedDescriptorSet.orderView != resolvedOrderView) {
cachedDescriptorSet.descriptorSet.set->Update(
passLayout.gaussianSplatOrderBuffer.binding,
resolvedOrderView);
}
}
if (setLayout.usesGaussianSplatOtherBuffer) { if (setLayout.usesGaussianSplatOtherBuffer) {
if (cachedGaussianSplat.other.shaderResourceView == nullptr || if (cachedGaussianSplat.other.shaderResourceView == nullptr ||
!passLayout.gaussianSplatOtherBuffer.IsValid() || !passLayout.gaussianSplatOtherBuffer.IsValid() ||
@@ -558,11 +857,28 @@ BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCr
} }
} }
if (setLayout.usesGaussianSplatViewDataBuffer) {
if (resolvedViewDataView == nullptr ||
!passLayout.gaussianSplatViewDataBuffer.IsValid() ||
passLayout.gaussianSplatViewDataBuffer.set != setIndex) {
return nullptr;
}
if (cachedDescriptorSet.viewDataView != resolvedViewDataView) {
cachedDescriptorSet.descriptorSet.set->Update(
passLayout.gaussianSplatViewDataBuffer.binding,
resolvedViewDataView);
}
}
cachedDescriptorSet.materialVersion = materialVersion; cachedDescriptorSet.materialVersion = materialVersion;
cachedDescriptorSet.sortDistanceView = resolvedSortDistanceView;
cachedDescriptorSet.orderView = resolvedOrderView;
cachedDescriptorSet.positionsView = cachedGaussianSplat.positions.shaderResourceView; cachedDescriptorSet.positionsView = cachedGaussianSplat.positions.shaderResourceView;
cachedDescriptorSet.otherView = cachedGaussianSplat.other.shaderResourceView; cachedDescriptorSet.otherView = cachedGaussianSplat.other.shaderResourceView;
cachedDescriptorSet.colorView = cachedGaussianSplat.color.shaderResourceView; cachedDescriptorSet.colorView = cachedGaussianSplat.color.shaderResourceView;
cachedDescriptorSet.shView = cachedGaussianSplat.sh.shaderResourceView; cachedDescriptorSet.shView = cachedGaussianSplat.sh.shaderResourceView;
cachedDescriptorSet.viewDataView = resolvedViewDataView;
return &cachedDescriptorSet; return &cachedDescriptorSet;
} }
@@ -592,10 +908,169 @@ void BuiltinGaussianSplatPass::DestroyPassResourceLayout(PassResourceLayout& pas
passLayout.descriptorSetCount = 0; passLayout.descriptorSetCount = 0;
passLayout.perObject = {}; passLayout.perObject = {};
passLayout.material = {}; passLayout.material = {};
passLayout.gaussianSplatSortDistanceBuffer = {};
passLayout.gaussianSplatOrderBuffer = {};
passLayout.gaussianSplatPositionBuffer = {}; passLayout.gaussianSplatPositionBuffer = {};
passLayout.gaussianSplatOtherBuffer = {}; passLayout.gaussianSplatOtherBuffer = {};
passLayout.gaussianSplatColorBuffer = {}; passLayout.gaussianSplatColorBuffer = {};
passLayout.gaussianSplatSHBuffer = {}; passLayout.gaussianSplatSHBuffer = {};
passLayout.gaussianSplatViewDataBuffer = {};
}
bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
const RenderContext& context,
const RenderSceneData& sceneData,
const VisibleGaussianSplatItem& visibleGaussianSplat) {
if (visibleGaussianSplat.gameObject == nullptr ||
visibleGaussianSplat.gaussianSplat == nullptr ||
!visibleGaussianSplat.gaussianSplat->IsValid() ||
m_passResources == nullptr) {
return false;
}
const RenderResourceCache::CachedGaussianSplat* cachedGaussianSplat =
m_resourceCache.GetOrCreateGaussianSplat(m_device, visibleGaussianSplat.gaussianSplat);
if (cachedGaussianSplat == nullptr || cachedGaussianSplat->positions.shaderResourceView == nullptr) {
return false;
}
Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr;
if (!m_passResources->EnsureWorkingSet(m_device, visibleGaussianSplat, workingSet) ||
workingSet == nullptr ||
workingSet->sortDistances.unorderedAccessView == nullptr ||
workingSet->orderIndices.unorderedAccessView == nullptr ||
workingSet->orderIndices.shaderResourceView == nullptr) {
return false;
}
const ResolvedShaderPass resolvedShaderPass = ResolvePrepareOrderShaderPass(sceneData);
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
return false;
}
PassLayoutKey passLayoutKey = {};
passLayoutKey.shader = resolvedShaderPass.shader;
passLayoutKey.passName = resolvedShaderPass.passName;
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(
context,
resolvedShaderPass,
PassLayoutUsage::PrepareOrder);
RHI::RHIPipelineState* pipelineState = GetOrCreateComputePipelineState(context, sceneData);
if (passLayout == nullptr || pipelineState == nullptr) {
return false;
}
RHI::RHICommandList* commandList = context.commandList;
if (workingSet->sortDistances.currentState != RHI::ResourceStates::UnorderedAccess) {
commandList->TransitionBarrier(
workingSet->sortDistances.unorderedAccessView,
workingSet->sortDistances.currentState,
RHI::ResourceStates::UnorderedAccess);
workingSet->sortDistances.currentState = RHI::ResourceStates::UnorderedAccess;
}
if (workingSet->orderIndices.currentState != RHI::ResourceStates::UnorderedAccess) {
commandList->TransitionBarrier(
workingSet->orderIndices.unorderedAccessView,
workingSet->orderIndices.currentState,
RHI::ResourceStates::UnorderedAccess);
workingSet->orderIndices.currentState = RHI::ResourceStates::UnorderedAccess;
}
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.layout.bindingCount == 0u) {
continue;
}
if (!(setLayout.usesPerObject ||
setLayout.usesGaussianSplatSortDistanceBuffer ||
setLayout.usesGaussianSplatOrderBuffer ||
setLayout.usesGaussianSplatPositionBuffer)) {
return false;
}
const Core::uint64 objectId =
setLayout.usesPerObject ? visibleGaussianSplat.gameObject->GetID() : 0u;
const Resources::GaussianSplat* gaussianSplatKey =
(setLayout.usesGaussianSplatSortDistanceBuffer ||
setLayout.usesGaussianSplatOrderBuffer ||
setLayout.usesGaussianSplatPositionBuffer)
? visibleGaussianSplat.gaussianSplat
: nullptr;
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
passLayoutKey,
*passLayout,
setLayout,
setIndex,
objectId,
visibleGaussianSplat.gaussianSplatRenderer,
nullptr,
gaussianSplatKey,
MaterialConstantPayloadView(),
*cachedGaussianSplat,
workingSet->sortDistances.unorderedAccessView,
workingSet->orderIndices.unorderedAccessView,
workingSet->viewData.unorderedAccessView);
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;
}
BindDescriptorSetRanges(
passLayout->firstDescriptorSet,
descriptorSets,
[commandList, passLayout](Core::uint32 firstSet, Core::uint32 count, RHI::RHIDescriptorSet** sets) {
commandList->SetComputeDescriptorSets(
firstSet,
count,
sets,
passLayout->pipelineLayout);
});
}
commandList->Dispatch((cachedGaussianSplat->splatCount + 63u) / 64u, 1u, 1u);
commandList->TransitionBarrier(
workingSet->orderIndices.shaderResourceView,
RHI::ResourceStates::UnorderedAccess,
RHI::ResourceStates::NonPixelShaderResource);
workingSet->orderIndices.currentState = RHI::ResourceStates::NonPixelShaderResource;
return true;
} }
bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat( bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
@@ -618,6 +1093,17 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
return false; return false;
} }
if (m_passResources == nullptr) {
return false;
}
Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr;
if (!m_passResources->EnsureWorkingSet(m_device, visibleGaussianSplat, workingSet) ||
workingSet == nullptr ||
workingSet->orderIndices.shaderResourceView == nullptr) {
return false;
}
const Resources::Material* material = ResolveGaussianSplatMaterial(visibleGaussianSplat); const Resources::Material* material = ResolveGaussianSplatMaterial(visibleGaussianSplat);
if (material == nullptr) { if (material == nullptr) {
return false; return false;
@@ -632,7 +1118,10 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
passLayoutKey.shader = resolvedShaderPass.shader; passLayoutKey.shader = resolvedShaderPass.shader;
passLayoutKey.passName = resolvedShaderPass.passName; passLayoutKey.passName = resolvedShaderPass.passName;
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(
context,
resolvedShaderPass,
PassLayoutUsage::Draw);
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material); RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material);
if (passLayout == nullptr || pipelineState == nullptr) { if (passLayout == nullptr || pipelineState == nullptr) {
return false; return false;
@@ -663,8 +1152,13 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
} }
const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex]; const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex];
if (setLayout.layout.bindingCount == 0u) {
continue;
}
if (!(setLayout.usesPerObject || if (!(setLayout.usesPerObject ||
setLayout.usesMaterial || setLayout.usesMaterial ||
setLayout.usesGaussianSplatOrderBuffer ||
setLayout.usesGaussianSplatPositionBuffer || setLayout.usesGaussianSplatPositionBuffer ||
setLayout.usesGaussianSplatOtherBuffer || setLayout.usesGaussianSplatOtherBuffer ||
setLayout.usesGaussianSplatColorBuffer || setLayout.usesGaussianSplatColorBuffer ||
@@ -677,7 +1171,8 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
const Resources::Material* materialKey = const Resources::Material* materialKey =
setLayout.usesMaterial ? material : nullptr; setLayout.usesMaterial ? material : nullptr;
const Resources::GaussianSplat* gaussianSplatKey = const Resources::GaussianSplat* gaussianSplatKey =
(setLayout.usesGaussianSplatPositionBuffer || (setLayout.usesGaussianSplatOrderBuffer ||
setLayout.usesGaussianSplatPositionBuffer ||
setLayout.usesGaussianSplatOtherBuffer || setLayout.usesGaussianSplatOtherBuffer ||
setLayout.usesGaussianSplatColorBuffer || setLayout.usesGaussianSplatColorBuffer ||
setLayout.usesGaussianSplatSHBuffer) setLayout.usesGaussianSplatSHBuffer)
@@ -690,10 +1185,14 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
setLayout, setLayout,
setIndex, setIndex,
objectId, objectId,
visibleGaussianSplat.gaussianSplatRenderer,
materialKey, materialKey,
gaussianSplatKey, gaussianSplatKey,
materialConstants, materialConstants,
*cachedGaussianSplat); *cachedGaussianSplat,
nullptr,
workingSet->orderIndices.shaderResourceView,
workingSet->viewData.shaderResourceView);
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
return false; return false;
} }
@@ -713,11 +1212,16 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
descriptorSets[descriptorOffset] = descriptorSet; descriptorSets[descriptorOffset] = descriptorSet;
} }
commandList->SetGraphicsDescriptorSets( BindDescriptorSetRanges(
passLayout->firstDescriptorSet, passLayout->firstDescriptorSet,
passLayout->descriptorSetCount, descriptorSets,
descriptorSets.data(), [commandList, passLayout](Core::uint32 firstSet, Core::uint32 count, RHI::RHIDescriptorSet** sets) {
commandList->SetGraphicsDescriptorSets(
firstSet,
count,
sets,
passLayout->pipelineLayout); passLayout->pipelineLayout);
});
} }
ApplyDynamicRenderState(ResolveEffectiveRenderState(resolvedShaderPass.pass, material), *commandList); ApplyDynamicRenderState(ResolveEffectiveRenderState(resolvedShaderPass.pass, material), *commandList);

View File

@@ -7,6 +7,7 @@
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <utility>
namespace XCEngine { namespace XCEngine {
namespace Resources { namespace Resources {
@@ -64,7 +65,7 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
const std::string magic(fileHeader.magic, fileHeader.magic + 7); const std::string magic(fileHeader.magic, fileHeader.magic + 7);
const bool isCurrentSchema = const bool isCurrentSchema =
magic == "XCSHD05" && fileHeader.schemaVersion == kShaderArtifactSchemaVersion; magic == "XCSHD06" && fileHeader.schemaVersion == kShaderArtifactSchemaVersion;
if (!isCurrentSchema) { if (!isCurrentSchema) {
return LoadResult("Invalid shader artifact header: " + path); return LoadResult("Invalid shader artifact header: " + path);
} }
@@ -187,6 +188,7 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
ShaderStageVariant variant = {}; ShaderStageVariant variant = {};
Core::uint64 compiledBinarySize = 0; Core::uint64 compiledBinarySize = 0;
Core::uint32 keywordCount = 0; Core::uint32 keywordCount = 0;
Core::uint32 backendCompiledBinaryCount = 0;
ShaderVariantArtifactHeader variantHeader = {}; ShaderVariantArtifactHeader variantHeader = {};
if (!ReadShaderArtifactValue(data, offset, variantHeader)) { if (!ReadShaderArtifactValue(data, offset, variantHeader)) {
return LoadResult("Failed to read shader artifact variants: " + path); return LoadResult("Failed to read shader artifact variants: " + path);
@@ -197,6 +199,7 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
variant.backend = static_cast<ShaderBackend>(variantHeader.backend); variant.backend = static_cast<ShaderBackend>(variantHeader.backend);
keywordCount = variantHeader.keywordCount; keywordCount = variantHeader.keywordCount;
compiledBinarySize = variantHeader.compiledBinarySize; compiledBinarySize = variantHeader.compiledBinarySize;
backendCompiledBinaryCount = variantHeader.backendCompiledBinaryCount;
if (!ReadShaderArtifactString(data, offset, variant.entryPoint) || if (!ReadShaderArtifactString(data, offset, variant.entryPoint) ||
!ReadShaderArtifactString(data, offset, variant.profile) || !ReadShaderArtifactString(data, offset, variant.profile) ||
@@ -227,6 +230,32 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
offset += static_cast<size_t>(compiledBinarySize); offset += static_cast<size_t>(compiledBinarySize);
} }
for (Core::uint32 binaryIndex = 0;
binaryIndex < backendCompiledBinaryCount;
++binaryIndex) {
ShaderBackendCompiledBinaryArtifactHeader binaryHeader = {};
if (!ReadShaderArtifactValue(data, offset, binaryHeader)) {
return LoadResult("Failed to read shader artifact backend binaries: " + path);
}
if (offset + binaryHeader.compiledBinarySize > data.Size()) {
return LoadResult(
"Shader artifact backend binary payload is truncated: " + path);
}
ShaderBackendCompiledBinary record = {};
record.backend = static_cast<ShaderBackend>(binaryHeader.backend);
if (binaryHeader.compiledBinarySize > 0) {
record.payload.Resize(static_cast<size_t>(binaryHeader.compiledBinarySize));
std::memcpy(
record.payload.Data(),
data.Data() + offset,
static_cast<size_t>(binaryHeader.compiledBinarySize));
offset += static_cast<size_t>(binaryHeader.compiledBinarySize);
}
variant.backendCompiledBinaries.PushBack(std::move(record));
}
shader->AddPassVariant(passName, variant); shader->AddPassVariant(passName, variant);
} }
} }

View File

@@ -1,5 +1,7 @@
#include <XCEngine/Resources/Shader/Shader.h> #include <XCEngine/Resources/Shader/Shader.h>
#include <utility>
namespace XCEngine { namespace XCEngine {
namespace Resources { namespace Resources {
@@ -40,6 +42,62 @@ Shader::Shader() = default;
Shader::~Shader() = default; Shader::~Shader() = default;
const Containers::Array<Core::uint8>* ShaderStageVariant::GetCompiledBinaryForBackend(
ShaderBackend targetBackend) const {
if ((targetBackend == backend || targetBackend == ShaderBackend::Generic) &&
!compiledBinary.Empty()) {
return &compiledBinary;
}
for (const ShaderBackendCompiledBinary& record : backendCompiledBinaries) {
if (record.backend == targetBackend && !record.payload.Empty()) {
return &record.payload;
}
}
return nullptr;
}
void ShaderStageVariant::SetCompiledBinaryForBackend(
ShaderBackend targetBackend,
const Containers::Array<Core::uint8>& binary) {
if (targetBackend == backend || targetBackend == ShaderBackend::Generic) {
compiledBinary = binary;
return;
}
for (ShaderBackendCompiledBinary& record : backendCompiledBinaries) {
if (record.backend == targetBackend) {
record.payload = binary;
return;
}
}
ShaderBackendCompiledBinary& record = backendCompiledBinaries.EmplaceBack();
record.backend = targetBackend;
record.payload = binary;
}
void ShaderStageVariant::SetCompiledBinaryForBackend(
ShaderBackend targetBackend,
Containers::Array<Core::uint8>&& binary) {
if (targetBackend == backend || targetBackend == ShaderBackend::Generic) {
compiledBinary = std::move(binary);
return;
}
for (ShaderBackendCompiledBinary& record : backendCompiledBinaries) {
if (record.backend == targetBackend) {
record.payload = std::move(binary);
return;
}
}
ShaderBackendCompiledBinary& record = backendCompiledBinaries.EmplaceBack();
record.backend = targetBackend;
record.payload = std::move(binary);
}
void Shader::Release() { void Shader::Release() {
m_uniforms.Clear(); m_uniforms.Clear();
m_attributes.Clear(); m_attributes.Clear();

View File

@@ -885,7 +885,7 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatShaderUsesAuthoringContrac
const ShaderPass* pass = shader->FindPass("GaussianSplat"); const ShaderPass* pass = shader->FindPass("GaussianSplat");
ASSERT_NE(pass, nullptr); ASSERT_NE(pass, nullptr);
EXPECT_EQ(pass->resources.Size(), 5u); EXPECT_EQ(pass->resources.Size(), 6u);
EXPECT_TRUE(pass->hasFixedFunctionState); EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None); EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable); EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
@@ -900,12 +900,22 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatShaderUsesAuthoringContrac
ASSERT_NE(opacityScale, nullptr); ASSERT_NE(opacityScale, nullptr);
EXPECT_EQ(opacityScale->type, ShaderPropertyType::Float); EXPECT_EQ(opacityScale->type, ShaderPropertyType::Float);
const ShaderResourceBindingDesc* order =
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatOrderBuffer");
ASSERT_NE(order, nullptr);
EXPECT_EQ(order->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(order->set, 2u);
EXPECT_EQ(order->binding, 0u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*order),
BuiltinPassResourceSemantic::GaussianSplatOrderBuffer);
const ShaderResourceBindingDesc* positions = const ShaderResourceBindingDesc* positions =
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatPositions"); shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatPositions");
ASSERT_NE(positions, nullptr); ASSERT_NE(positions, nullptr);
EXPECT_EQ(positions->type, ShaderResourceType::StructuredBuffer); EXPECT_EQ(positions->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(positions->set, 2u); EXPECT_EQ(positions->set, 2u);
EXPECT_EQ(positions->binding, 0u); EXPECT_EQ(positions->binding, 1u);
EXPECT_EQ( EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*positions), ResolveBuiltinPassResourceSemantic(*positions),
BuiltinPassResourceSemantic::GaussianSplatPositionBuffer); BuiltinPassResourceSemantic::GaussianSplatPositionBuffer);
@@ -915,7 +925,7 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatShaderUsesAuthoringContrac
ASSERT_NE(other, nullptr); ASSERT_NE(other, nullptr);
EXPECT_EQ(other->type, ShaderResourceType::StructuredBuffer); EXPECT_EQ(other->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(other->set, 2u); EXPECT_EQ(other->set, 2u);
EXPECT_EQ(other->binding, 1u); EXPECT_EQ(other->binding, 2u);
EXPECT_EQ( EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*other), ResolveBuiltinPassResourceSemantic(*other),
BuiltinPassResourceSemantic::GaussianSplatOtherBuffer); BuiltinPassResourceSemantic::GaussianSplatOtherBuffer);
@@ -925,7 +935,7 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatShaderUsesAuthoringContrac
ASSERT_NE(color, nullptr); ASSERT_NE(color, nullptr);
EXPECT_EQ(color->type, ShaderResourceType::StructuredBuffer); EXPECT_EQ(color->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(color->set, 2u); EXPECT_EQ(color->set, 2u);
EXPECT_EQ(color->binding, 2u); EXPECT_EQ(color->binding, 3u);
EXPECT_EQ( EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*color), ResolveBuiltinPassResourceSemantic(*color),
BuiltinPassResourceSemantic::GaussianSplatColorBuffer); BuiltinPassResourceSemantic::GaussianSplatColorBuffer);
@@ -948,21 +958,24 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
BuiltinPassResourceBindingPlan plan = {}; BuiltinPassResourceBindingPlan plan = {};
String error; String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
ASSERT_EQ(plan.bindings.Size(), 5u); ASSERT_EQ(plan.bindings.Size(), 6u);
EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_TRUE(plan.perObject.IsValid());
EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.material.IsValid());
EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatPositionBuffer.IsValid()); EXPECT_TRUE(plan.gaussianSplatPositionBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatOtherBuffer.IsValid()); EXPECT_TRUE(plan.gaussianSplatOtherBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatColorBuffer.IsValid()); EXPECT_TRUE(plan.gaussianSplatColorBuffer.IsValid());
EXPECT_FALSE(plan.gaussianSplatSHBuffer.IsValid()); EXPECT_FALSE(plan.gaussianSplatSHBuffer.IsValid());
EXPECT_EQ(plan.perObject.set, 0u); EXPECT_EQ(plan.perObject.set, 0u);
EXPECT_EQ(plan.material.set, 1u); EXPECT_EQ(plan.material.set, 1u);
EXPECT_EQ(plan.gaussianSplatOrderBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatOrderBuffer.binding, 0u);
EXPECT_EQ(plan.gaussianSplatPositionBuffer.set, 2u); EXPECT_EQ(plan.gaussianSplatPositionBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatPositionBuffer.binding, 0u); EXPECT_EQ(plan.gaussianSplatPositionBuffer.binding, 1u);
EXPECT_EQ(plan.gaussianSplatOtherBuffer.set, 2u); EXPECT_EQ(plan.gaussianSplatOtherBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatOtherBuffer.binding, 1u); EXPECT_EQ(plan.gaussianSplatOtherBuffer.binding, 2u);
EXPECT_EQ(plan.gaussianSplatColorBuffer.set, 2u); EXPECT_EQ(plan.gaussianSplatColorBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatColorBuffer.binding, 2u); EXPECT_EQ(plan.gaussianSplatColorBuffer.binding, 3u);
EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.firstDescriptorSet, 0u);
EXPECT_EQ(plan.descriptorSetCount, 3u); EXPECT_EQ(plan.descriptorSetCount, 3u);
@@ -971,10 +984,11 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
ASSERT_EQ(setLayouts.size(), 3u); ASSERT_EQ(setLayouts.size(), 3u);
EXPECT_TRUE(setLayouts[0].usesPerObject); EXPECT_TRUE(setLayouts[0].usesPerObject);
EXPECT_TRUE(setLayouts[1].usesMaterial); EXPECT_TRUE(setLayouts[1].usesMaterial);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOrderBuffer);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatPositionBuffer); EXPECT_TRUE(setLayouts[2].usesGaussianSplatPositionBuffer);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOtherBuffer); EXPECT_TRUE(setLayouts[2].usesGaussianSplatOtherBuffer);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatColorBuffer); EXPECT_TRUE(setLayouts[2].usesGaussianSplatColorBuffer);
ASSERT_EQ(setLayouts[2].bindings.size(), 3u); ASSERT_EQ(setLayouts[2].bindings.size(), 4u);
EXPECT_EQ( EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[0].type), static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
DescriptorType::SRV); DescriptorType::SRV);
@@ -984,6 +998,9 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
EXPECT_EQ( EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[2].type), static_cast<DescriptorType>(setLayouts[2].bindings[2].type),
DescriptorType::SRV); DescriptorType::SRV);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[3].type),
DescriptorType::SRV);
EXPECT_EQ( EXPECT_EQ(
setLayouts[2].bindings[0].resourceDimension, setLayouts[2].bindings[0].resourceDimension,
ResourceViewDimension::StructuredBuffer); ResourceViewDimension::StructuredBuffer);
@@ -993,6 +1010,9 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
EXPECT_EQ( EXPECT_EQ(
setLayouts[2].bindings[2].resourceDimension, setLayouts[2].bindings[2].resourceDimension,
ResourceViewDimension::StructuredBuffer); ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[2].bindings[3].resourceDimension,
ResourceViewDimension::StructuredBuffer);
delete shader; delete shader;
} }
@@ -1034,16 +1054,20 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringGauss
"register(b1)")); "register(b1)"));
EXPECT_TRUE(SourceContainsRegisterBinding( EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source, d3d12Source,
"StructuredBuffer<float3> GaussianSplatPositions", "StructuredBuffer<uint> GaussianSplatOrderBuffer",
"register(t0)")); "register(t0)"));
EXPECT_TRUE(SourceContainsRegisterBinding( EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source, d3d12Source,
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther", "StructuredBuffer<float3> GaussianSplatPositions",
"register(t1)")); "register(t1)"));
EXPECT_TRUE(SourceContainsRegisterBinding( EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source, d3d12Source,
"StructuredBuffer<float4> GaussianSplatColor", "StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
"register(t2)")); "register(t2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<float4> GaussianSplatColor",
"register(t3)"));
EXPECT_EQ(d3d12Source.find("space0"), std::string::npos); EXPECT_EQ(d3d12Source.find("space0"), std::string::npos);
EXPECT_EQ(d3d12Source.find("space1"), std::string::npos); EXPECT_EQ(d3d12Source.find("space1"), std::string::npos);
EXPECT_EQ(d3d12Source.find("space2"), std::string::npos); EXPECT_EQ(d3d12Source.find("space2"), std::string::npos);
@@ -1073,16 +1097,20 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringGauss
"register(b0, space1)")); "register(b0, space1)"));
EXPECT_TRUE(SourceContainsRegisterBinding( EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource, vulkanSource,
"StructuredBuffer<float3> GaussianSplatPositions", "StructuredBuffer<uint> GaussianSplatOrderBuffer",
"register(t0, space2)")); "register(t0, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding( EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource, vulkanSource,
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther", "StructuredBuffer<float3> GaussianSplatPositions",
"register(t1, space2)")); "register(t1, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding( EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource, vulkanSource,
"StructuredBuffer<float4> GaussianSplatColor", "StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
"register(t2, space2)")); "register(t2, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<float4> GaussianSplatColor",
"register(t3, space2)"));
delete shader; delete shader;
} }
@@ -2410,6 +2438,57 @@ TEST(BuiltinPassLayout_Test, AcceptsRuntimeMaterialRawUavBufferBindings) {
EXPECT_TRUE(setLayouts[4].usesMaterialBuffers); EXPECT_TRUE(setLayouts[4].usesMaterialBuffers);
} }
TEST(BuiltinPassLayout_Test, AcceptsRuntimeMaterialTextureBindingsWithoutBuiltinSemanticMetadata) {
Array<ShaderResourceBindingDesc> bindings;
ShaderResourceBindingDesc perObjectBinding = {};
perObjectBinding.name = "PerObjectConstants";
perObjectBinding.type = ShaderResourceType::ConstantBuffer;
perObjectBinding.set = 0u;
perObjectBinding.binding = 0u;
perObjectBinding.semantic = "PerObject";
bindings.PushBack(perObjectBinding);
ShaderResourceBindingDesc materialBinding = {};
materialBinding.name = "MaterialConstants";
materialBinding.type = ShaderResourceType::ConstantBuffer;
materialBinding.set = 1u;
materialBinding.binding = 0u;
materialBinding.semantic = "Material";
bindings.PushBack(materialBinding);
ShaderResourceBindingDesc textureBinding = {};
textureBinding.name = "_LightMap";
textureBinding.type = ShaderResourceType::Texture2D;
textureBinding.set = 4u;
textureBinding.binding = 1u;
bindings.PushBack(textureBinding);
BuiltinPassResourceBindingPlan plan = {};
String error;
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr();
EXPECT_TRUE(plan.usesMaterialTextures);
ASSERT_EQ(plan.materialTextureBindings.Size(), 1u);
EXPECT_EQ(plan.materialTextureBindings[0].name, "_LightMap");
EXPECT_EQ(plan.materialTextureBindings[0].semantic, BuiltinPassResourceSemantic::MaterialTexture);
EXPECT_EQ(plan.materialTextureBindings[0].resourceType, ShaderResourceType::Texture2D);
EXPECT_EQ(plan.materialTextureBindings[0].location.set, 4u);
EXPECT_EQ(plan.materialTextureBindings[0].location.binding, 1u);
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
ASSERT_EQ(setLayouts.size(), 5u);
EXPECT_TRUE(setLayouts[4].usesTexture);
EXPECT_TRUE(setLayouts[4].usesMaterialTextures);
ASSERT_EQ(setLayouts[4].materialTextureBindings.size(), 1u);
EXPECT_EQ(setLayouts[4].materialTextureBindings[0].name, "_LightMap");
ASSERT_EQ(setLayouts[4].bindings.size(), 1u);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[4].bindings[0].type),
DescriptorType::SRV);
EXPECT_EQ(setLayouts[4].bindings[0].resourceDimension, ResourceViewDimension::Texture2D);
}
TEST(BuiltinDepthOnlyPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { TEST(BuiltinDepthOnlyPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
const InputLayoutDesc inputLayout = BuiltinDepthOnlyPass::BuildInputLayout(); const InputLayoutDesc inputLayout = BuiltinDepthOnlyPass::BuildInputLayout();

View File

@@ -2,6 +2,8 @@
#include <XCEngine/Resources/Shader/Shader.h> #include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Core/Asset/ResourceTypes.h> #include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Core/Containers/Array.h> #include <XCEngine/Core/Containers/Array.h>
#include <XCEngine/RHI/RHITypes.h>
#include "Rendering/Internal/ShaderVariantUtils.h"
using namespace XCEngine::Resources; using namespace XCEngine::Resources;
using namespace XCEngine::Containers; using namespace XCEngine::Containers;
@@ -77,6 +79,69 @@ TEST(Shader, AddGetAttributes) {
EXPECT_EQ(attributes[0].name, "aPosition"); EXPECT_EQ(attributes[0].name, "aPosition");
} }
TEST(Shader, StoresBackendCompiledBinariesPerBackend) {
ShaderStageVariant variant = {};
variant.stage = ShaderType::Fragment;
variant.language = ShaderLanguage::HLSL;
variant.backend = ShaderBackend::Generic;
Array<XCEngine::Core::uint8> d3d12Payload;
d3d12Payload.PushBack(0x01);
d3d12Payload.PushBack(0x02);
variant.SetCompiledBinaryForBackend(ShaderBackend::D3D12, d3d12Payload);
Array<XCEngine::Core::uint8> vulkanPayload;
vulkanPayload.PushBack(0x03);
vulkanPayload.PushBack(0x04);
vulkanPayload.PushBack(0x05);
variant.SetCompiledBinaryForBackend(ShaderBackend::Vulkan, vulkanPayload);
const Array<XCEngine::Core::uint8>* d3d12Binary =
variant.GetCompiledBinaryForBackend(ShaderBackend::D3D12);
ASSERT_NE(d3d12Binary, nullptr);
ASSERT_EQ(d3d12Binary->Size(), 2u);
EXPECT_EQ((*d3d12Binary)[1], 0x02);
const Array<XCEngine::Core::uint8>* vulkanBinary =
variant.GetCompiledBinaryForBackend(ShaderBackend::Vulkan);
ASSERT_NE(vulkanBinary, nullptr);
ASSERT_EQ(vulkanBinary->Size(), 3u);
EXPECT_EQ((*vulkanBinary)[2], 0x05);
EXPECT_EQ(variant.GetCompiledBinaryForBackend(ShaderBackend::OpenGL), nullptr);
}
TEST(Shader, ApplyShaderStageVariantCarriesMatchedBackendCompiledBinary) {
ShaderPass pass = {};
pass.name = "ForwardLit";
ShaderStageVariant variant = {};
variant.stage = ShaderType::Fragment;
variant.language = ShaderLanguage::HLSL;
variant.backend = ShaderBackend::Generic;
variant.entryPoint = "MainPS";
variant.profile = "ps_5_1";
variant.sourceCode = "float4 MainPS() : SV_TARGET { return 1; }";
Array<XCEngine::Core::uint8> vulkanPayload;
vulkanPayload.PushBack(0x07);
vulkanPayload.PushBack(0x08);
vulkanPayload.PushBack(0x09);
variant.SetCompiledBinaryForBackend(ShaderBackend::Vulkan, vulkanPayload);
XCEngine::RHI::ShaderCompileDesc compileDesc = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
pass,
ShaderBackend::Vulkan,
variant,
compileDesc);
EXPECT_EQ(compileDesc.compiledBinaryBackend, XCEngine::RHI::ShaderBinaryBackend::Vulkan);
ASSERT_EQ(compileDesc.compiledBinary.size(), 3u);
EXPECT_EQ(compileDesc.compiledBinary[0], 0x07);
EXPECT_EQ(compileDesc.compiledBinary[2], 0x09);
}
TEST(Shader, FindsBackendSpecificVariantAndFallsBackToGeneric) { TEST(Shader, FindsBackendSpecificVariantAndFallsBackToGeneric) {
Shader shader; Shader shader;