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

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

View File

@@ -16,7 +16,7 @@ namespace Resources {
constexpr Core::uint32 kTextureArtifactSchemaVersion = 1;
constexpr Core::uint32 kMaterialArtifactSchemaVersion = 6;
constexpr Core::uint32 kMeshArtifactSchemaVersion = 2;
constexpr Core::uint32 kShaderArtifactSchemaVersion = 5;
constexpr Core::uint32 kShaderArtifactSchemaVersion = 6;
constexpr Core::uint32 kUIDocumentArtifactSchemaVersion = 2;
constexpr Core::uint32 kVolumeFieldArtifactSchemaVersion = 2;
constexpr Core::uint32 kModelArtifactSchemaVersion = 1;
@@ -104,7 +104,7 @@ struct ModelMaterialBindingArtifact {
};
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;
};
@@ -138,6 +138,14 @@ struct ShaderVariantArtifactHeader {
Core::uint32 backend = 0;
Core::uint32 keywordCount = 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 {

View File

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

View File

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

View File

@@ -87,6 +87,11 @@ struct ShaderResourceBindingDesc {
Containers::String semantic;
};
struct ShaderBackendCompiledBinary {
ShaderBackend backend = ShaderBackend::Generic;
Containers::Array<Core::uint8> payload;
};
struct ShaderStageVariant {
ShaderType stage = ShaderType::Fragment;
ShaderLanguage language = ShaderLanguage::GLSL;
@@ -96,6 +101,16 @@ struct ShaderStageVariant {
Containers::String profile;
Containers::String sourceCode;
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 {

View File

@@ -1,23 +1,33 @@
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEngine/Core/Asset/AssetDatabase.h>
#include <XCEngine/Core/Asset/ArtifactFormats.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/Material/MaterialLoader.h>
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Model/AssimpModelImporter.h>
#include <XCEngine/Resources/Model/ModelArtifactIO.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include "Resources/GaussianSplat/Internal/GaussianSplatPlyImporter.h"
#include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <XCEngine/Resources/Texture/TextureLoader.h>
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
#include <XCEngine/Resources/Volume/VolumeField.h>
#include <XCEngine/Resources/Volume/VolumeFieldLoader.h>
#include "Rendering/Internal/ShaderVariantUtils.h"
#include <algorithm>
#include <cctype>
#include <cstring>
#include <d3dcompiler.h>
#include <filesystem>
#include <fstream>
#include <sstream>
@@ -134,10 +144,294 @@ bool IsCurrentShaderArtifactFile(const fs::path& artifactPath) {
return false;
}
return std::memcmp(header.magic, "XCSHD05", 7) == 0 &&
return std::memcmp(header.magic, "XCSHD06", 7) == 0 &&
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) {
const auto begin = std::find_if_not(text.begin(), text.end(), [](unsigned char ch) {
return std::isspace(ch) != 0;
@@ -714,6 +1008,8 @@ bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader)
variantHeader.keywordCount =
static_cast<Core::uint32>(variant.requiredKeywords.enabledKeywords.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));
WriteString(output, variant.entryPoint);
@@ -727,6 +1023,17 @@ bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader)
reinterpret_cast<const char*>(variant.compiledBinary.Data()),
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 relativePath = fs::relative(inputPath, projectRootPath, ec);
if (!ec) {
const Containers::String normalizedRelative = NormalizePathString(relativePath);
if (normalizedRelative.StartsWith("Assets/") || normalizedRelative == "Assets") {
outRelativePath = normalizedRelative;
} else {
outRelativePath.Clear();
}
outRelativePath = NormalizePathString(relativePath);
} else {
outRelativePath.Clear();
}
@@ -1300,6 +1602,9 @@ AssetDatabase::MaintenanceStats AssetDatabase::ScanAssets() {
if (fs::exists(assetsRootPath)) {
ScanAssetPath(assetsRootPath, seenPaths);
}
for (const fs::path& builtinShaderPath : CollectBuiltinShaderAssetPaths()) {
ScanAssetPath(builtinShaderPath, seenPaths);
}
RemoveMissingRecords(seenPaths);
stats.removedArtifactCount = CleanupOrphanedArtifacts();
SaveSourceAssetDB();
@@ -1446,6 +1751,47 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
outRecord.importerName = GetImporterNameForPath(relativePath, isFolder);
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");
outRecord.metaPath = NormalizeRelativePath(metaPath);
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());
LoadResult result = loader.Load(absolutePath);
if (!result || result.resource == nullptr) {
if (!result.errorMessage.Empty()) {
SetLastErrorMessage(result.errorMessage);
}
return false;
}
Shader* shader = static_cast<Shader*>(result.resource);
PrecompileShaderVariants(absolutePath, *shader);
std::vector<ArtifactDependencyRecord> dependencies;
if (!CollectShaderDependencies(sourceRecord, dependencies)) {
delete shader;

View File

@@ -3,6 +3,7 @@
#include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Core/IO/ResourceFileSystem.h>
#include <XCEngine/Resources/GaussianSplat/GaussianSplatLoader.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/MaterialLoader.h>
#include <XCEngine/Resources/Model/ModelLoader.h>
#include <XCEngine/Resources/Mesh/MeshLoader.h>
@@ -548,6 +549,7 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
}
Containers::String loadPath = path;
Containers::String cachePath = path;
AssetImportService::ImportedAsset resolvedAsset;
ResourceType importableType = ResourceType::Unknown;
const bool shouldUseProjectArtifact =
@@ -560,6 +562,7 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
resolvedAsset.artifactReady) {
m_projectAssetIndex.RememberResolvedPath(resolvedAsset.assetGuid, resolvedAsset.relativePath);
loadPath = resolvedAsset.runtimeLoadPath;
cachePath = loadPath;
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
@@ -568,6 +571,35 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
" artifact=" +
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)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
@@ -577,7 +609,7 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
loadPath);
}
const ResourceGUID guid = ResourceGUID::Generate(loadPath);
const ResourceGUID guid = ResourceGUID::Generate(cachePath);
if (IResource* cached = FindInCache(guid)) {
if (ShouldTraceResourcePath(path)) {

View File

@@ -24,6 +24,33 @@ std::string NarrowAscii(const std::wstring& value) {
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 {
public:
explicit D3D12ShaderIncludeHandler(const std::filesystem::path& sourcePath)
@@ -127,15 +154,7 @@ bool D3D12Shader::CompileFromFile(const wchar_t* filePath, const char* entryPoin
return false;
}
if (strstr(target, "vs_")) {
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_type = ResolveShaderTypeFromTarget(target);
m_uniformsCached = false;
return true;
@@ -182,16 +201,29 @@ bool D3D12Shader::Compile(const void* sourceData,
return false;
}
if (strstr(target, "vs_")) {
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_type = ResolveShaderTypeFromTarget(target);
m_uniformsCached = false;
return true;
}
bool D3D12Shader::InitializeFromBytecode(const void* bytecodeData,
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;
return true;
}

View File

@@ -272,6 +272,21 @@ bool IsHlslInput(const ShaderCompileDesc& desc) {
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) {
const DWORD length = GetEnvironmentVariableW(name, nullptr, 0);
if (length == 0) {
@@ -1021,16 +1036,19 @@ bool CompileSpirvShader(const ShaderCompileDesc& desc,
outShader.entryPoint = "main";
}
if (desc.source.empty() && desc.fileName.empty()) {
if (desc.source.empty() && desc.fileName.empty() &&
!HasMatchingCompiledSpirvBinary(desc, targetEnvironment)) {
if (errorMessage != nullptr) {
*errorMessage = "No shader source or file name was provided.";
}
return false;
}
if (IsSpirvInput(desc)) {
if (IsSpirvInput(desc) || HasMatchingCompiledSpirvBinary(desc, targetEnvironment)) {
std::vector<uint8_t> bytes;
if (!desc.source.empty()) {
if (HasMatchingCompiledSpirvBinary(desc, targetEnvironment)) {
bytes = desc.compiledBinary;
} else if (!desc.source.empty()) {
bytes = desc.source;
} else if (!LoadBinaryFile(std::filesystem::path(desc.fileName), bytes)) {
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) {
std::wstring wide;
wide.reserve(value.Length());
@@ -360,6 +374,13 @@ inline void ApplyShaderStageVariant(
compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language);
compileDesc.entryPoint = ToWideAscii(variant.entryPoint);
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) {
@@ -398,6 +419,12 @@ inline void ApplyShaderStageVariant(
compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language);
compileDesc.entryPoint = ToWideAscii(variant.entryPoint);
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);
}

View File

@@ -9,6 +9,7 @@
#include "Rendering/FrameData/VisibleGaussianSplatItem.h"
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
#include "Rendering/Internal/ShaderVariantUtils.h"
#include "Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h"
#include "Rendering/RenderSurface.h"
#include "Resources/BuiltinResources.h"
#include "Resources/GaussianSplat/GaussianSplat.h"
@@ -49,6 +50,21 @@ const Resources::ShaderPass* FindCompatibleGaussianSplatPass(
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::RHIType backendType,
RHI::RHIPipelineLayout* pipelineLayout,
@@ -89,6 +105,88 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
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
BuiltinGaussianSplatPass::~BuiltinGaussianSplatPass() {
@@ -124,6 +222,19 @@ bool BuiltinGaussianSplatPass::PrepareGaussianSplatResources(
cachedGaussianSplat->color.shaderResourceView == nullptr) {
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;
@@ -177,6 +288,13 @@ bool BuiltinGaussianSplatPass::Execute(const RenderPassContext& context) {
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
for (const VisibleGaussianSplatItem& visibleGaussianSplat : context.sceneData.visibleGaussianSplats) {
if (!PrepareVisibleGaussianSplat(
context.renderContext,
context.sceneData,
visibleGaussianSplat)) {
return false;
}
if (!DrawVisibleGaussianSplat(
context.renderContext,
context.surface,
@@ -222,6 +340,16 @@ bool BuiltinGaussianSplatPass::CreateResources(const RenderContext& context) {
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>();
Resources::IResource::ConstructParams params = {};
params.name = "BuiltinGaussianSplatMaterial";
@@ -230,10 +358,17 @@ bool BuiltinGaussianSplatPass::CreateResources(const RenderContext& context) {
m_builtinGaussianSplatMaterial->Initialize(params);
m_builtinGaussianSplatMaterial->SetShader(m_builtinGaussianSplatShader);
m_builtinGaussianSplatMaterial->SetRenderQueue(Resources::MaterialRenderQueue::Transparent);
m_passResources = new Internal::BuiltinGaussianSplatPassResources();
return true;
}
void BuiltinGaussianSplatPass::DestroyResources() {
if (m_passResources != nullptr) {
m_passResources->Shutdown();
delete m_passResources;
m_passResources = nullptr;
}
m_resourceCache.Shutdown();
for (auto& pipelinePair : m_pipelineStates) {
@@ -244,6 +379,14 @@ void BuiltinGaussianSplatPass::DestroyResources() {
}
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) {
DestroyOwnedDescriptorSet(descriptorSetPair.second.descriptorSet);
}
@@ -255,6 +398,7 @@ void BuiltinGaussianSplatPass::DestroyResources() {
m_passResourceLayouts.clear();
m_builtinGaussianSplatMaterial.reset();
m_builtinGaussianSplatUtilitiesShader.Reset();
m_builtinGaussianSplatShader.Reset();
m_device = nullptr;
m_backendType = RHI::RHIType::D3D12;
@@ -301,9 +445,30 @@ BuiltinGaussianSplatPass::ResolvedShaderPass BuiltinGaussianSplatPass::ResolveGa
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(
const RenderContext& context,
const ResolvedShaderPass& resolvedShaderPass) {
const ResolvedShaderPass& resolvedShaderPass,
PassLayoutUsage usage) {
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
return nullptr;
}
@@ -345,21 +510,36 @@ BuiltinGaussianSplatPass::PassResourceLayout* BuiltinGaussianSplatPass::GetOrCre
passLayout.descriptorSetCount = bindingPlan.descriptorSetCount;
passLayout.perObject = bindingPlan.perObject;
passLayout.material = bindingPlan.material;
passLayout.gaussianSplatSortDistanceBuffer = bindingPlan.gaussianSplatSortDistanceBuffer;
passLayout.gaussianSplatOrderBuffer = bindingPlan.gaussianSplatOrderBuffer;
passLayout.gaussianSplatPositionBuffer = bindingPlan.gaussianSplatPositionBuffer;
passLayout.gaussianSplatOtherBuffer = bindingPlan.gaussianSplatOtherBuffer;
passLayout.gaussianSplatColorBuffer = bindingPlan.gaussianSplatColorBuffer;
passLayout.gaussianSplatSHBuffer = bindingPlan.gaussianSplatSHBuffer;
passLayout.gaussianSplatViewDataBuffer = bindingPlan.gaussianSplatViewDataBuffer;
if (!passLayout.perObject.IsValid()) {
return failLayout("BuiltinGaussianSplatPass requires a PerObject resource binding");
}
if (!passLayout.material.IsValid()) {
return failLayout("BuiltinGaussianSplatPass requires a Material resource binding");
}
if (!passLayout.gaussianSplatPositionBuffer.IsValid() ||
!passLayout.gaussianSplatOtherBuffer.IsValid() ||
!passLayout.gaussianSplatColorBuffer.IsValid()) {
return failLayout("BuiltinGaussianSplatPass requires position, other, and color gaussian splat buffer bindings");
if (usage == PassLayoutUsage::Draw) {
if (!passLayout.material.IsValid()) {
return failLayout("BuiltinGaussianSplatPass requires a Material resource binding");
}
if (!passLayout.gaussianSplatOrderBuffer.IsValid() ||
!passLayout.gaussianSplatPositionBuffer.IsValid() ||
!passLayout.gaussianSplatOtherBuffer.IsValid() ||
!passLayout.gaussianSplatColorBuffer.IsValid()) {
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());
@@ -392,7 +572,10 @@ RHI::RHIPipelineState* BuiltinGaussianSplatPass::GetOrCreatePipelineState(
return nullptr;
}
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass);
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(
context,
resolvedShaderPass,
PassLayoutUsage::Draw);
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
return nullptr;
}
@@ -440,6 +623,54 @@ RHI::RHIPipelineState* BuiltinGaussianSplatPass::GetOrCreatePipelineState(
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(
const BuiltinPassSetLayoutMetadata& setLayout,
OwnedDescriptorSet& descriptorSet) {
@@ -468,14 +699,19 @@ BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCr
const BuiltinPassSetLayoutMetadata& setLayout,
Core::uint32 setIndex,
Core::uint64 objectId,
const Components::GaussianSplatRendererComponent* gaussianSplatRenderer,
const Resources::Material* material,
const Resources::GaussianSplat* gaussianSplat,
const MaterialConstantPayloadView& materialConstants,
const RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat) {
const RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat,
RHI::RHIResourceView* sortDistanceView,
RHI::RHIResourceView* orderView,
RHI::RHIResourceView* viewDataView) {
DynamicDescriptorSetKey key = {};
key.passLayout = passLayoutKey;
key.setIndex = setIndex;
key.objectId = objectId;
key.gaussianSplatRenderer = gaussianSplatRenderer;
key.material = material;
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;
if (setLayout.usesMaterial) {
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 (cachedGaussianSplat.other.shaderResourceView == nullptr ||
!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.sortDistanceView = resolvedSortDistanceView;
cachedDescriptorSet.orderView = resolvedOrderView;
cachedDescriptorSet.positionsView = cachedGaussianSplat.positions.shaderResourceView;
cachedDescriptorSet.otherView = cachedGaussianSplat.other.shaderResourceView;
cachedDescriptorSet.colorView = cachedGaussianSplat.color.shaderResourceView;
cachedDescriptorSet.shView = cachedGaussianSplat.sh.shaderResourceView;
cachedDescriptorSet.viewDataView = resolvedViewDataView;
return &cachedDescriptorSet;
}
@@ -592,10 +908,169 @@ void BuiltinGaussianSplatPass::DestroyPassResourceLayout(PassResourceLayout& pas
passLayout.descriptorSetCount = 0;
passLayout.perObject = {};
passLayout.material = {};
passLayout.gaussianSplatSortDistanceBuffer = {};
passLayout.gaussianSplatOrderBuffer = {};
passLayout.gaussianSplatPositionBuffer = {};
passLayout.gaussianSplatOtherBuffer = {};
passLayout.gaussianSplatColorBuffer = {};
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(
@@ -618,6 +1093,17 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
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);
if (material == nullptr) {
return false;
@@ -632,7 +1118,10 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
passLayoutKey.shader = resolvedShaderPass.shader;
passLayoutKey.passName = resolvedShaderPass.passName;
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass);
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(
context,
resolvedShaderPass,
PassLayoutUsage::Draw);
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material);
if (passLayout == nullptr || pipelineState == nullptr) {
return false;
@@ -663,8 +1152,13 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
}
const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex];
if (setLayout.layout.bindingCount == 0u) {
continue;
}
if (!(setLayout.usesPerObject ||
setLayout.usesMaterial ||
setLayout.usesGaussianSplatOrderBuffer ||
setLayout.usesGaussianSplatPositionBuffer ||
setLayout.usesGaussianSplatOtherBuffer ||
setLayout.usesGaussianSplatColorBuffer ||
@@ -677,7 +1171,8 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
const Resources::Material* materialKey =
setLayout.usesMaterial ? material : nullptr;
const Resources::GaussianSplat* gaussianSplatKey =
(setLayout.usesGaussianSplatPositionBuffer ||
(setLayout.usesGaussianSplatOrderBuffer ||
setLayout.usesGaussianSplatPositionBuffer ||
setLayout.usesGaussianSplatOtherBuffer ||
setLayout.usesGaussianSplatColorBuffer ||
setLayout.usesGaussianSplatSHBuffer)
@@ -690,10 +1185,14 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
setLayout,
setIndex,
objectId,
visibleGaussianSplat.gaussianSplatRenderer,
materialKey,
gaussianSplatKey,
materialConstants,
*cachedGaussianSplat);
*cachedGaussianSplat,
nullptr,
workingSet->orderIndices.shaderResourceView,
workingSet->viewData.shaderResourceView);
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
return false;
}
@@ -713,11 +1212,16 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
descriptorSets[descriptorOffset] = descriptorSet;
}
commandList->SetGraphicsDescriptorSets(
BindDescriptorSetRanges(
passLayout->firstDescriptorSet,
passLayout->descriptorSetCount,
descriptorSets.data(),
passLayout->pipelineLayout);
descriptorSets,
[commandList, passLayout](Core::uint32 firstSet, Core::uint32 count, RHI::RHIDescriptorSet** sets) {
commandList->SetGraphicsDescriptorSets(
firstSet,
count,
sets,
passLayout->pipelineLayout);
});
}
ApplyDynamicRenderState(ResolveEffectiveRenderState(resolvedShaderPass.pass, material), *commandList);

View File

@@ -7,6 +7,7 @@
#include <cstring>
#include <memory>
#include <utility>
namespace XCEngine {
namespace Resources {
@@ -64,7 +65,7 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
const std::string magic(fileHeader.magic, fileHeader.magic + 7);
const bool isCurrentSchema =
magic == "XCSHD05" && fileHeader.schemaVersion == kShaderArtifactSchemaVersion;
magic == "XCSHD06" && fileHeader.schemaVersion == kShaderArtifactSchemaVersion;
if (!isCurrentSchema) {
return LoadResult("Invalid shader artifact header: " + path);
}
@@ -187,6 +188,7 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
ShaderStageVariant variant = {};
Core::uint64 compiledBinarySize = 0;
Core::uint32 keywordCount = 0;
Core::uint32 backendCompiledBinaryCount = 0;
ShaderVariantArtifactHeader variantHeader = {};
if (!ReadShaderArtifactValue(data, offset, variantHeader)) {
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);
keywordCount = variantHeader.keywordCount;
compiledBinarySize = variantHeader.compiledBinarySize;
backendCompiledBinaryCount = variantHeader.backendCompiledBinaryCount;
if (!ReadShaderArtifactString(data, offset, variant.entryPoint) ||
!ReadShaderArtifactString(data, offset, variant.profile) ||
@@ -227,6 +230,32 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
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);
}
}

View File

@@ -1,5 +1,7 @@
#include <XCEngine/Resources/Shader/Shader.h>
#include <utility>
namespace XCEngine {
namespace Resources {
@@ -40,6 +42,62 @@ 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() {
m_uniforms.Clear();
m_attributes.Clear();