#include "Rendering/Passes/BuiltinVolumetricPass.h" #include "Components/GameObject.h" #include "Core/Asset/ResourceManager.h" #include "Debug/Logger.h" #include "RHI/RHICommandList.h" #include "RHI/RHIDevice.h" #include "Rendering/Builtin/BuiltinPassLayoutUtils.h" #include "Rendering/Internal/RenderSurfacePipelineUtils.h" #include "Rendering/Internal/ShaderVariantUtils.h" #include "Rendering/FrameData/RenderSceneData.h" #include "Rendering/FrameData/VisibleVolumeItem.h" #include "Rendering/RenderSurface.h" #include "Resources/BuiltinResources.h" #include "Resources/Material/Material.h" #include "Resources/Shader/Shader.h" #include "Resources/Volume/VolumeField.h" #include #include #include #include namespace XCEngine { namespace Rendering { namespace Passes { namespace { uint64_t GetVolumeTraceSteadyMs() { using Clock = std::chrono::steady_clock; static const Clock::time_point s_start = Clock::now(); return static_cast(std::chrono::duration_cast( Clock::now() - s_start).count()); } void LogVolumeTraceRendering(const std::string& message) { Containers::String entry("[VolumeTrace] "); entry += message.c_str(); Debug::Logger::Get().Info(Debug::LogCategory::Rendering, entry); } bool IsDepthFormat(RHI::Format format) { return format == RHI::Format::D24_UNorm_S8_UInt || format == RHI::Format::D32_Float; } Resources::ShaderKeywordSet ResolvePassKeywordSet( const RenderSceneData& sceneData, const Resources::Material* material) { return Resources::CombineShaderKeywordSets( sceneData.globalShaderKeywords, material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet()); } const Resources::ShaderPass* FindCompatibleVolumePass( const Resources::Shader& shader, const RenderSceneData& sceneData, const Resources::Material* material, Resources::ShaderBackend backend) { const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material); for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) { if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::Volumetric) && ::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants( shader, shaderPass.name, backend, keywordSet)) { return &shaderPass; } } return nullptr; } RHI::GraphicsPipelineDesc CreatePipelineDesc( RHI::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout, const Resources::Shader& shader, const Resources::ShaderPass& shaderPass, const Containers::String& passName, const Resources::ShaderKeywordSet& keywordSet, const Resources::Material* material, const RenderSurface& surface) { RHI::GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.pipelineLayout = pipelineLayout; pipelineDesc.topologyType = static_cast(RHI::PrimitiveTopologyType::Triangle); ::XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc( surface, pipelineDesc); pipelineDesc.depthStencilFormat = static_cast(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface)); pipelineDesc.inputLayout = BuiltinVolumetricPass::BuildInputLayout(); ApplyResolvedRenderState(&shaderPass, material, pipelineDesc); const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType); if (const Resources::ShaderStageVariant* vertexVariant = shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet)) { ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( shader.GetPath(), shaderPass, backend, *vertexVariant, pipelineDesc.vertexShader); } if (const Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant(passName, Resources::ShaderType::Fragment, backend, keywordSet)) { ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( shader.GetPath(), shaderPass, backend, *fragmentVariant, pipelineDesc.fragmentShader); } return pipelineDesc; } Math::Bounds ResolveVolumeBounds(const Resources::VolumeField* volumeField) { if (volumeField == nullptr) { return Math::Bounds(Math::Vector3::Zero(), Math::Vector3::One()); } const Resources::VolumeIndexBounds& indexBounds = volumeField->GetIndexBounds(); const Math::Vector3 indexMin( static_cast(indexBounds.minX), static_cast(indexBounds.minY), static_cast(indexBounds.minZ)); const Math::Vector3 indexMax( static_cast(indexBounds.maxX), static_cast(indexBounds.maxY), static_cast(indexBounds.maxZ)); const Math::Vector3 indexSize = indexMax - indexMin; if (indexSize.SqrMagnitude() > Math::EPSILON) { Math::Bounds bounds; bounds.SetMinMax(indexMin, indexMax); return bounds; } const Math::Bounds bounds = volumeField->GetBounds(); const Math::Vector3 size = bounds.extents * 2.0f; if (size.SqrMagnitude() <= Math::EPSILON) { return Math::Bounds(Math::Vector3::Zero(), Math::Vector3::One()); } return bounds; } } // namespace BuiltinVolumetricPass::~BuiltinVolumetricPass() { Shutdown(); } const char* BuiltinVolumetricPass::GetName() const { return "BuiltinVolumetricPass"; } BuiltinVolumetricPass::LightingConstants BuiltinVolumetricPass::BuildLightingConstants( const RenderLightingData& lightingData) { LightingConstants lightingConstants = {}; if (!lightingData.HasMainDirectionalLight()) { return lightingConstants; } lightingConstants.mainLightDirectionAndIntensity = Math::Vector4( lightingData.mainDirectionalLight.direction.x, lightingData.mainDirectionalLight.direction.y, lightingData.mainDirectionalLight.direction.z, lightingData.mainDirectionalLight.intensity); lightingConstants.mainLightColorAndFlags = Math::Vector4( lightingData.mainDirectionalLight.color.r, lightingData.mainDirectionalLight.color.g, lightingData.mainDirectionalLight.color.b, 1.0f); return lightingConstants; } RHI::InputLayoutDesc BuiltinVolumetricPass::BuildInputLayout() { RHI::InputLayoutDesc inputLayout = {}; RHI::InputElementDesc position = {}; position.semanticName = "POSITION"; position.semanticIndex = 0; position.format = static_cast(RHI::Format::R32G32B32_Float); position.inputSlot = 0; position.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, position)); inputLayout.elements.push_back(position); RHI::InputElementDesc normal = {}; normal.semanticName = "NORMAL"; normal.semanticIndex = 0; normal.format = static_cast(RHI::Format::R32G32B32_Float); normal.inputSlot = 0; normal.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, normal)); inputLayout.elements.push_back(normal); RHI::InputElementDesc texcoord = {}; texcoord.semanticName = "TEXCOORD"; texcoord.semanticIndex = 0; texcoord.format = static_cast(RHI::Format::R32G32_Float); texcoord.inputSlot = 0; texcoord.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, uv0)); inputLayout.elements.push_back(texcoord); return inputLayout; } bool BuiltinVolumetricPass::Initialize(const RenderContext& context) { return EnsureInitialized(context); } bool BuiltinVolumetricPass::IsActive(const RenderSceneData& sceneData) const { return !sceneData.visibleVolumes.empty(); } bool BuiltinVolumetricPass::Prepare( const RenderContext& context, const RenderSceneData& sceneData) { return PrepareVolumeResources(context, sceneData); } bool BuiltinVolumetricPass::PrepareVolumeResources( const RenderContext& context, const RenderSceneData& sceneData) { if (!EnsureInitialized(context)) { return false; } if (!sceneData.visibleVolumes.empty()) { const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, m_builtinCubeMesh.Get()); if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) { return false; } } for (const VisibleVolumeItem& visibleVolume : sceneData.visibleVolumes) { if (visibleVolume.volumeField == nullptr || visibleVolume.material == nullptr || visibleVolume.volumeField->GetStorageKind() != Resources::VolumeStorageKind::NanoVDB) { continue; } const RenderResourceCache::CachedVolumeField* cachedVolume = m_resourceCache.GetOrCreateVolumeField(m_device, visibleVolume.volumeField); if (cachedVolume == nullptr || cachedVolume->shaderResourceView == nullptr) { return false; } } return true; } bool BuiltinVolumetricPass::Execute(const RenderPassContext& context) { if (!context.renderContext.IsValid()) { return false; } if (context.sceneData.visibleVolumes.empty()) { return true; } const std::vector& colorAttachments = context.surface.GetColorAttachments(); if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(context.surface) || colorAttachments.empty() || colorAttachments[0] == nullptr || context.surface.GetDepthAttachment() == nullptr) { return false; } const Math::RectInt renderArea = context.surface.GetRenderArea(); if (renderArea.width <= 0 || renderArea.height <= 0) { return false; } if (!PrepareVolumeResources(context.renderContext, context.sceneData)) { return false; } RHI::RHICommandList* commandList = context.renderContext.commandList; RHI::RHIResourceView* renderTarget = colorAttachments[0]; commandList->SetRenderTargets(1, &renderTarget, context.surface.GetDepthAttachment()); const RHI::Viewport viewport = { static_cast(renderArea.x), static_cast(renderArea.y), static_cast(renderArea.width), static_cast(renderArea.height), 0.0f, 1.0f }; const RHI::Rect scissorRect = { renderArea.x, renderArea.y, renderArea.x + renderArea.width, renderArea.y + renderArea.height }; commandList->SetViewport(viewport); commandList->SetScissorRect(scissorRect); commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); for (const VisibleVolumeItem& visibleVolume : context.sceneData.visibleVolumes) { DrawVisibleVolume(context.renderContext, context.surface, context.sceneData, visibleVolume); } return true; } void BuiltinVolumetricPass::Shutdown() { DestroyResources(); } bool BuiltinVolumetricPass::EnsureInitialized(const RenderContext& context) { if (!context.IsValid()) { return false; } if (m_device == context.device && m_backendType == context.backendType && m_builtinCubeMesh.IsValid()) { return true; } DestroyResources(); return CreateResources(context); } bool BuiltinVolumetricPass::CreateResources(const RenderContext& context) { m_device = context.device; m_backendType = context.backendType; m_builtinCubeMesh = Resources::ResourceManager::Get().Load( Resources::GetBuiltinPrimitiveMeshPath(Resources::BuiltinPrimitiveType::Cube)); if (!m_builtinCubeMesh.IsValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinVolumetricPass failed to load builtin cube mesh resource"); DestroyResources(); return false; } return true; } void BuiltinVolumetricPass::DestroyResources() { m_resourceCache.Shutdown(); for (auto& pipelinePair : m_pipelineStates) { if (pipelinePair.second != nullptr) { pipelinePair.second->Shutdown(); delete pipelinePair.second; } } m_pipelineStates.clear(); for (auto& descriptorSetPair : m_dynamicDescriptorSets) { DestroyOwnedDescriptorSet(descriptorSetPair.second.descriptorSet); } m_dynamicDescriptorSets.clear(); for (auto& passLayoutPair : m_passResourceLayouts) { DestroyPassResourceLayout(passLayoutPair.second); } m_passResourceLayouts.clear(); m_builtinCubeMesh.Reset(); m_device = nullptr; m_backendType = RHI::RHIType::D3D12; } BuiltinVolumetricPass::ResolvedShaderPass BuiltinVolumetricPass::ResolveVolumeShaderPass( const RenderSceneData& sceneData, const Resources::Material* material) const { ResolvedShaderPass resolved = {}; if (material == nullptr || material->GetShader() == nullptr) { return resolved; } const Resources::Shader* shader = material->GetShader(); const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType); if (const Resources::ShaderPass* shaderPass = FindCompatibleVolumePass(*shader, sceneData, material, backend)) { resolved.shader = shader; resolved.pass = shaderPass; resolved.passName = shaderPass->name; } return resolved; } BuiltinVolumetricPass::PassResourceLayout* BuiltinVolumetricPass::GetOrCreatePassResourceLayout( const RenderContext& context, const ResolvedShaderPass& resolvedShaderPass) { if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { return nullptr; } PassLayoutKey passLayoutKey = {}; passLayoutKey.shader = resolvedShaderPass.shader; passLayoutKey.passName = resolvedShaderPass.passName; const auto existing = m_passResourceLayouts.find(passLayoutKey); if (existing != m_passResourceLayouts.end()) { return &existing->second; } const uint64_t layoutStartMs = GetVolumeTraceSteadyMs(); LogVolumeTraceRendering( "VolumetricPass layout create begin steady_ms=" + std::to_string(layoutStartMs) + " shader=" + std::string(resolvedShaderPass.shader->GetPath().CStr()) + " pass=" + std::string(resolvedShaderPass.passName.CStr())); PassResourceLayout passLayout = {}; auto failLayout = [this, &passLayout](const char* message) -> PassResourceLayout* { Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message); DestroyPassResourceLayout(passLayout); return nullptr; }; BuiltinPassResourceBindingPlan bindingPlan = {}; Containers::String bindingPlanError; if (!TryBuildBuiltinPassResourceBindingPlan(*resolvedShaderPass.pass, bindingPlan, &bindingPlanError)) { const Containers::String contextualError = Containers::String("BuiltinVolumetricPass failed to resolve pass resource bindings for shader='") + resolvedShaderPass.shader->GetPath() + "', pass='" + resolvedShaderPass.passName + "': " + bindingPlanError + ". Bindings: " + DescribeShaderResourceBindings(resolvedShaderPass.pass->resources); return failLayout(contextualError.CStr()); } if (!bindingPlan.perObject.IsValid()) { return failLayout("BuiltinVolumetricPass requires a PerObject resource binding"); } if (!bindingPlan.volumeField.IsValid()) { return failLayout("BuiltinVolumetricPass requires a VolumeField structured-buffer binding"); } Containers::String setLayoutError; if (!TryBuildBuiltinPassSetLayouts(bindingPlan, passLayout.setLayouts, &setLayoutError)) { return failLayout(setLayoutError.CStr()); } passLayout.firstDescriptorSet = bindingPlan.firstDescriptorSet; passLayout.descriptorSetCount = bindingPlan.descriptorSetCount; passLayout.perObject = bindingPlan.perObject; passLayout.lighting = bindingPlan.lighting; passLayout.material = bindingPlan.material; passLayout.volumeField = bindingPlan.volumeField; std::vector nativeSetLayouts(passLayout.setLayouts.size()); for (size_t setIndex = 0; setIndex < passLayout.setLayouts.size(); ++setIndex) { nativeSetLayouts[setIndex] = passLayout.setLayouts[setIndex].layout; } RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; pipelineLayoutDesc.setLayouts = nativeSetLayouts.empty() ? nullptr : nativeSetLayouts.data(); pipelineLayoutDesc.setLayoutCount = static_cast(nativeSetLayouts.size()); passLayout.pipelineLayout = context.device->CreatePipelineLayout(pipelineLayoutDesc); if (passLayout.pipelineLayout == nullptr) { return failLayout("BuiltinVolumetricPass failed to create pipeline layout from shader pass resources"); } const auto result = m_passResourceLayouts.emplace(passLayoutKey, passLayout); PassResourceLayout& storedPassLayout = result.first->second; RefreshBuiltinPassSetLayouts(storedPassLayout.setLayouts); const uint64_t layoutEndMs = GetVolumeTraceSteadyMs(); LogVolumeTraceRendering( "VolumetricPass layout create end steady_ms=" + std::to_string(layoutEndMs) + " total_ms=" + std::to_string(layoutEndMs - layoutStartMs) + " shader=" + std::string(resolvedShaderPass.shader->GetPath().CStr()) + " pass=" + std::string(resolvedShaderPass.passName.CStr())); return &storedPassLayout; } RHI::RHIPipelineState* BuiltinVolumetricPass::GetOrCreatePipelineState( const RenderContext& context, const RenderSurface& surface, const RenderSceneData& sceneData, const Resources::Material* material) { const ResolvedShaderPass resolvedShaderPass = ResolveVolumeShaderPass(sceneData, material); if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { return nullptr; } PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { return nullptr; } const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material); const RHI::Format renderTargetFormat = ::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u); const RHI::Format depthStencilFormat = ::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface); PipelineStateKey pipelineKey = {}; pipelineKey.renderState = BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material)); pipelineKey.shader = resolvedShaderPass.shader; pipelineKey.passName = resolvedShaderPass.passName; pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet); pipelineKey.renderTargetCount = ::XCEngine::Rendering::Internal::HasSingleColorAttachment(surface) ? 1u : 0u; pipelineKey.renderTargetFormat = static_cast(renderTargetFormat); pipelineKey.depthStencilFormat = static_cast(depthStencilFormat); pipelineKey.sampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface); pipelineKey.sampleQuality = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface); const auto existing = m_pipelineStates.find(pipelineKey); if (existing != m_pipelineStates.end()) { return existing->second; } const uint64_t pipelineStartMs = GetVolumeTraceSteadyMs(); LogVolumeTraceRendering( "VolumetricPass pipeline create begin steady_ms=" + std::to_string(pipelineStartMs) + " shader=" + std::string(resolvedShaderPass.shader->GetPath().CStr()) + " pass=" + std::string(resolvedShaderPass.passName.CStr())); const RHI::GraphicsPipelineDesc pipelineDesc = CreatePipelineDesc( context.backendType, passLayout->pipelineLayout, *resolvedShaderPass.shader, *resolvedShaderPass.pass, resolvedShaderPass.passName, keywordSet, material, surface); RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc); if (pipelineState == nullptr || !pipelineState->IsValid()) { if (pipelineState != nullptr) { pipelineState->Shutdown(); delete pipelineState; } LogVolumeTraceRendering( "VolumetricPass pipeline create failed steady_ms=" + std::to_string(GetVolumeTraceSteadyMs()) + " shader=" + std::string(resolvedShaderPass.shader->GetPath().CStr()) + " pass=" + std::string(resolvedShaderPass.passName.CStr())); return nullptr; } m_pipelineStates.emplace(pipelineKey, pipelineState); const uint64_t pipelineEndMs = GetVolumeTraceSteadyMs(); LogVolumeTraceRendering( "VolumetricPass pipeline create end steady_ms=" + std::to_string(pipelineEndMs) + " total_ms=" + std::to_string(pipelineEndMs - pipelineStartMs) + " shader=" + std::string(resolvedShaderPass.shader->GetPath().CStr()) + " pass=" + std::string(resolvedShaderPass.passName.CStr())); return pipelineState; } bool BuiltinVolumetricPass::CreateOwnedDescriptorSet( const BuiltinPassSetLayoutMetadata& setLayout, OwnedDescriptorSet& descriptorSet) { RHI::DescriptorPoolDesc poolDesc = {}; poolDesc.type = setLayout.heapType; poolDesc.descriptorCount = CountBuiltinPassHeapDescriptors(setLayout.heapType, setLayout.bindings); poolDesc.shaderVisible = setLayout.shaderVisible; descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc); if (descriptorSet.pool == nullptr) { return false; } descriptorSet.set = descriptorSet.pool->AllocateSet(setLayout.layout); if (descriptorSet.set == nullptr) { DestroyOwnedDescriptorSet(descriptorSet); return false; } return true; } BuiltinVolumetricPass::CachedDescriptorSet* BuiltinVolumetricPass::GetOrCreateDynamicDescriptorSet( const PassLayoutKey& passLayoutKey, const PassResourceLayout& passLayout, const BuiltinPassSetLayoutMetadata& setLayout, Core::uint32 setIndex, Core::uint64 objectId, const Resources::Material* material, const Resources::VolumeField* volumeField, const MaterialConstantPayloadView& materialConstants, const LightingConstants& lightingConstants, RHI::RHIResourceView* volumeFieldView) { DynamicDescriptorSetKey key = {}; key.passLayout = passLayoutKey; key.setIndex = setIndex; key.objectId = objectId; key.material = material; key.volumeField = volumeField; CachedDescriptorSet& cachedDescriptorSet = m_dynamicDescriptorSets[key]; if (cachedDescriptorSet.descriptorSet.set == nullptr) { if (!CreateOwnedDescriptorSet(setLayout, cachedDescriptorSet.descriptorSet)) { return nullptr; } } const Core::uint64 materialVersion = material != nullptr ? material->GetChangeVersion() : 0u; if (setLayout.usesMaterial) { if (!passLayout.material.IsValid() || passLayout.material.set != setIndex || !materialConstants.IsValid()) { return nullptr; } if (cachedDescriptorSet.materialVersion != materialVersion) { cachedDescriptorSet.descriptorSet.set->WriteConstant( passLayout.material.binding, materialConstants.data, materialConstants.size); } } if (setLayout.usesLighting) { if (!passLayout.lighting.IsValid() || passLayout.lighting.set != setIndex) { return nullptr; } cachedDescriptorSet.descriptorSet.set->WriteConstant( passLayout.lighting.binding, &lightingConstants, sizeof(lightingConstants)); } if (setLayout.usesVolumeField) { if (volumeFieldView == nullptr || !passLayout.volumeField.IsValid() || passLayout.volumeField.set != setIndex) { return nullptr; } if (cachedDescriptorSet.volumeFieldView != volumeFieldView) { cachedDescriptorSet.descriptorSet.set->Update( passLayout.volumeField.binding, volumeFieldView); } } cachedDescriptorSet.materialVersion = materialVersion; cachedDescriptorSet.volumeFieldView = volumeFieldView; return &cachedDescriptorSet; } void BuiltinVolumetricPass::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) { if (descriptorSet.set != nullptr) { descriptorSet.set->Shutdown(); delete descriptorSet.set; descriptorSet.set = nullptr; } if (descriptorSet.pool != nullptr) { descriptorSet.pool->Shutdown(); delete descriptorSet.pool; descriptorSet.pool = nullptr; } } void BuiltinVolumetricPass::DestroyPassResourceLayout(PassResourceLayout& passLayout) { if (passLayout.pipelineLayout != nullptr) { passLayout.pipelineLayout->Shutdown(); delete passLayout.pipelineLayout; passLayout.pipelineLayout = nullptr; } passLayout.setLayouts.clear(); passLayout.firstDescriptorSet = 0; passLayout.descriptorSetCount = 0; passLayout.perObject = {}; passLayout.lighting = {}; passLayout.material = {}; passLayout.volumeField = {}; } bool BuiltinVolumetricPass::DrawVisibleVolume( const RenderContext& context, const RenderSurface& surface, const RenderSceneData& sceneData, const VisibleVolumeItem& visibleVolume) { if (m_builtinCubeMesh.Get() == nullptr || visibleVolume.gameObject == nullptr || visibleVolume.volumeField == nullptr || visibleVolume.material == nullptr || visibleVolume.volumeField->GetStorageKind() != Resources::VolumeStorageKind::NanoVDB) { return false; } const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, m_builtinCubeMesh.Get()); const RenderResourceCache::CachedVolumeField* cachedVolume = m_resourceCache.GetOrCreateVolumeField(m_device, visibleVolume.volumeField); if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr || cachedVolume == nullptr || cachedVolume->shaderResourceView == nullptr) { return false; } const Resources::Material* material = visibleVolume.material; const ResolvedShaderPass resolvedShaderPass = ResolveVolumeShaderPass(sceneData, material); if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { return false; } PassLayoutKey passLayoutKey = {}; passLayoutKey.shader = resolvedShaderPass.shader; passLayoutKey.passName = resolvedShaderPass.passName; PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material); if (passLayout == nullptr || pipelineState == nullptr) { return false; } const Resources::MaterialRenderState effectiveRenderState = ResolveEffectiveRenderState(resolvedShaderPass.pass, material); const MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material); const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting); if (passLayout->material.IsValid() && !materialConstants.IsValid()) { return false; } RHI::RHICommandList* commandList = context.commandList; commandList->SetPipelineState(pipelineState); RHI::RHIResourceView* vertexBuffers[] = { cachedMesh->vertexBufferView }; const uint64_t offsets[] = { 0u }; const uint32_t strides[] = { cachedMesh->vertexStride }; commandList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides); if (cachedMesh->indexBufferView != nullptr) { commandList->SetIndexBuffer(cachedMesh->indexBufferView, 0u); } const Math::Bounds volumeBounds = ResolveVolumeBounds(visibleVolume.volumeField); const PerObjectConstants perObjectConstants = { sceneData.cameraData.projection, sceneData.cameraData.view, visibleVolume.localToWorld.Transpose(), visibleVolume.localToWorld.Inverse().Transpose(), Math::Vector4(sceneData.cameraData.worldPosition, 1.0f), Math::Vector4(volumeBounds.GetMin(), 0.0f), Math::Vector4(volumeBounds.GetMax(), 0.0f) }; if (passLayout->descriptorSetCount > 0u) { std::vector descriptorSets(passLayout->descriptorSetCount, nullptr); for (Core::uint32 descriptorOffset = 0u; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) { const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset; if (setIndex >= passLayout->setLayouts.size()) { return false; } const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex]; if (!(setLayout.usesPerObject || setLayout.usesLighting || setLayout.usesMaterial || setLayout.usesVolumeField)) { return false; } const Core::uint64 objectId = (setLayout.usesPerObject && visibleVolume.gameObject != nullptr) ? visibleVolume.gameObject->GetID() : 0u; const Resources::Material* materialKey = setLayout.usesMaterial ? material : nullptr; const Resources::VolumeField* volumeFieldKey = setLayout.usesVolumeField ? visibleVolume.volumeField : nullptr; CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet( passLayoutKey, *passLayout, setLayout, setIndex, objectId, materialKey, volumeFieldKey, materialConstants, lightingConstants, cachedVolume->shaderResourceView); if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { return false; } RHI::RHIDescriptorSet* descriptorSet = cachedDescriptorSet->descriptorSet.set; if (setLayout.usesPerObject) { if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) { return false; } descriptorSet->WriteConstant( passLayout->perObject.binding, &perObjectConstants, sizeof(perObjectConstants)); } descriptorSets[descriptorOffset] = descriptorSet; } commandList->SetGraphicsDescriptorSets( passLayout->firstDescriptorSet, passLayout->descriptorSetCount, descriptorSets.data(), passLayout->pipelineLayout); } ApplyDynamicRenderState(effectiveRenderState, *commandList); if (cachedMesh->indexBufferView != nullptr && cachedMesh->indexCount > 0u) { commandList->DrawIndexed(cachedMesh->indexCount, 1u, 0u, 0u, 0u); } else if (cachedMesh->vertexCount > 0u) { commandList->Draw(cachedMesh->vertexCount, 1u, 0u, 0u); } return true; } } // namespace Passes } // namespace Rendering } // namespace XCEngine