#include "Rendering/Pipelines/BuiltinForwardPipeline.h" #include "Components/GameObject.h" #include "Debug/Logger.h" #include "RHI/RHICommandList.h" #include "RHI/RHIDevice.h" #include "Rendering/Detail/ShaderVariantUtils.h" #include "Rendering/RenderMaterialUtility.h" #include "Resources/Material/Material.h" #include "Resources/Shader/Shader.h" #include "Resources/Texture/Texture.h" #include #include namespace XCEngine { namespace Rendering { namespace Pipelines { namespace { constexpr float kForwardAmbientIntensity = 0.28f; constexpr float kSpotInnerAngleRatio = 0.8f; bool TryResolveSurfacePassType( const Resources::Material* material, BuiltinMaterialPass& outPass) { if (MatchesBuiltinPass(material, BuiltinMaterialPass::Unlit)) { outPass = BuiltinMaterialPass::Unlit; return true; } if (MatchesBuiltinPass(material, BuiltinMaterialPass::ForwardLit)) { outPass = BuiltinMaterialPass::ForwardLit; return true; } return false; } const Resources::ShaderPass* FindCompatibleSurfacePass( const Resources::Shader& shader, const Resources::Material* material, BuiltinMaterialPass pass, Resources::ShaderBackend backend) { if (material != nullptr && !material->GetShaderPass().Empty()) { const Resources::ShaderPass* explicitPass = shader.FindPass(material->GetShaderPass()); if (explicitPass != nullptr && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, explicitPass->name, backend)) { return explicitPass; } } for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) { if (ShaderPassMatchesBuiltinPass(shaderPass, pass) && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shaderPass.name, backend)) { return &shaderPass; } } if (pass != BuiltinMaterialPass::ForwardLit) { return nullptr; } const Resources::ShaderPass* defaultPass = shader.FindPass("ForwardLit"); if (defaultPass != nullptr && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, defaultPass->name, backend)) { return defaultPass; } defaultPass = shader.FindPass("Default"); if (defaultPass != nullptr && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, defaultPass->name, backend)) { return defaultPass; } if (shader.GetPassCount() > 0 && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) { return &shader.GetPasses()[0]; } return nullptr; } RHI::GraphicsPipelineDesc CreatePipelineDesc( RHI::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout, const Resources::Shader& shader, const Containers::String& passName, const Resources::Material* material) { RHI::GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.pipelineLayout = pipelineLayout; pipelineDesc.topologyType = static_cast(RHI::PrimitiveTopologyType::Triangle); pipelineDesc.renderTargetCount = 1; pipelineDesc.renderTargetFormats[0] = static_cast(RHI::Format::R8G8B8A8_UNorm); pipelineDesc.depthStencilFormat = static_cast(RHI::Format::D24_UNorm_S8_UInt); pipelineDesc.sampleCount = 1; ApplyMaterialRenderState(material, pipelineDesc); pipelineDesc.inputLayout = BuiltinForwardPipeline::BuildInputLayout(); const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType); const Resources::ShaderStageVariant* vertexVariant = shader.FindVariant(passName, Resources::ShaderType::Vertex, backend); const Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant(passName, Resources::ShaderType::Fragment, backend); if (vertexVariant != nullptr) { ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader); } if (fragmentVariant != nullptr) { ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader); } return pipelineDesc; } } // namespace BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfaceShaderPass( const Resources::Material* material) const { ResolvedShaderPass resolved = {}; BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit; if (!TryResolveSurfacePassType(material, pass)) { return resolved; } const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType); if (material != nullptr && material->GetShader() != nullptr) { const Resources::Shader* materialShader = material->GetShader(); if (const Resources::ShaderPass* shaderPass = FindCompatibleSurfacePass(*materialShader, material, pass, backend)) { resolved.shader = materialShader; resolved.pass = shaderPass; resolved.passName = shaderPass->name; return resolved; } } const Resources::ResourceHandle* builtinShaderHandle = pass == BuiltinMaterialPass::Unlit ? &m_builtinUnlitShader : &m_builtinForwardShader; if (builtinShaderHandle->IsValid()) { const Resources::Shader* builtinShader = builtinShaderHandle->Get(); if (const Resources::ShaderPass* shaderPass = FindCompatibleSurfacePass(*builtinShader, nullptr, pass, backend)) { resolved.shader = builtinShader; resolved.pass = shaderPass; resolved.passName = shaderPass->name; } } return resolved; } BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreatePassResourceLayout( const RenderContext& context, const ResolvedShaderPass& resolvedShaderPass) { if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { return nullptr; } PassLayoutKey passLayoutKey = {}; passLayoutKey.shader = resolvedShaderPass.shader; passLayoutKey.passName = resolvedShaderPass.passName; const auto existing = m_passResourceLayouts.find(passLayoutKey); if (existing != m_passResourceLayouts.end()) { return &existing->second; } PassResourceLayout passLayout = {}; auto failLayout = [this, &passLayout](const char* message) -> PassResourceLayout* { Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message); DestroyPassResourceLayout(passLayout); return nullptr; }; const Containers::Array& resourceBindings = resolvedShaderPass.pass->resources; if (resourceBindings.Empty()) { return failLayout("BuiltinForwardPipeline requires explicit resource bindings on the resolved shader pass"); } BuiltinPassResourceBindingPlan bindingPlan = {}; Containers::String bindingPlanError; if (!TryBuildBuiltinPassResourceBindingPlan(resourceBindings, bindingPlan, &bindingPlanError)) { const Containers::String contextualError = Containers::String("BuiltinForwardPipeline failed to resolve pass resource bindings for shader='") + resolvedShaderPass.shader->GetPath() + "', pass='" + resolvedShaderPass.passName + "': " + bindingPlanError + ". Bindings: " + DescribeShaderResourceBindings(resourceBindings); return failLayout(contextualError.CStr()); } const bool hasAnyResource = !bindingPlan.bindings.Empty(); if (hasAnyResource) { Containers::String setLayoutError; if (!TryBuildBuiltinPassSetLayouts(bindingPlan, passLayout.setLayouts, &setLayoutError)) { return failLayout(setLayoutError.CStr()); } passLayout.staticDescriptorSets.resize(passLayout.setLayouts.size()); passLayout.firstDescriptorSet = bindingPlan.firstDescriptorSet; passLayout.descriptorSetCount = bindingPlan.descriptorSetCount; } passLayout.perObject = bindingPlan.perObject; passLayout.material = bindingPlan.material; passLayout.lighting = bindingPlan.lighting; passLayout.shadowReceiver = bindingPlan.shadowReceiver; passLayout.baseColorTexture = bindingPlan.baseColorTexture; passLayout.linearClampSampler = bindingPlan.linearClampSampler; passLayout.shadowMapTexture = bindingPlan.shadowMapTexture; passLayout.shadowMapSampler = bindingPlan.shadowMapSampler; if (!passLayout.perObject.IsValid()) { return failLayout("BuiltinForwardPipeline requires a PerObject resource binding"); } if (ShaderPassMatchesBuiltinPass(*resolvedShaderPass.pass, BuiltinMaterialPass::ForwardLit) && !passLayout.lighting.IsValid()) { return failLayout("BuiltinForwardPipeline forward-lit pass requires a Lighting resource binding"); } std::vector nativeSetLayouts(passLayout.setLayouts.size()); for (size_t i = 0; i < passLayout.setLayouts.size(); ++i) { nativeSetLayouts[i] = passLayout.setLayouts[i].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("BuiltinForwardPipeline failed to create a pipeline layout from shader pass resources"); } const auto result = m_passResourceLayouts.emplace(passLayoutKey, passLayout); PassResourceLayout& storedPassLayout = result.first->second; RefreshBuiltinPassSetLayouts(storedPassLayout.setLayouts); return &storedPassLayout; } RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( const RenderContext& context, const Resources::Material* material) { const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material); if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinForwardPipeline could not resolve a valid surface shader pass"); return nullptr; } PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { return nullptr; } PipelineStateKey pipelineKey = {}; pipelineKey.renderState = material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState(); pipelineKey.shader = resolvedShaderPass.shader; pipelineKey.passName = resolvedShaderPass.passName; const auto existing = m_pipelineStates.find(pipelineKey); if (existing != m_pipelineStates.end()) { return existing->second; } const RHI::GraphicsPipelineDesc pipelineDesc = CreatePipelineDesc( context.backendType, passLayout->pipelineLayout, *resolvedShaderPass.shader, resolvedShaderPass.passName, material); RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc); if (pipelineState == nullptr || !pipelineState->IsValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinForwardPipeline failed to create pipeline state"); if (pipelineState != nullptr) { pipelineState->Shutdown(); delete pipelineState; } return nullptr; } m_pipelineStates.emplace(pipelineKey, pipelineState); return pipelineState; } bool BuiltinForwardPipeline::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; } RHI::RHIDescriptorSet* BuiltinForwardPipeline::GetOrCreateStaticDescriptorSet( PassResourceLayout& passLayout, Core::uint32 setIndex) { if (setIndex >= passLayout.setLayouts.size() || setIndex >= passLayout.staticDescriptorSets.size()) { return nullptr; } OwnedDescriptorSet& descriptorSet = passLayout.staticDescriptorSets[setIndex]; if (descriptorSet.set == nullptr) { if (!CreateOwnedDescriptorSet(passLayout.setLayouts[setIndex], descriptorSet)) { return nullptr; } if (passLayout.setLayouts[setIndex].usesSampler) { RHI::RHISampler* sampler = nullptr; Core::uint32 binding = 0; if (passLayout.setLayouts[setIndex].usesLinearClampSampler) { sampler = m_sampler; if (!passLayout.linearClampSampler.IsValid() || passLayout.linearClampSampler.set != setIndex) { DestroyOwnedDescriptorSet(descriptorSet); return nullptr; } binding = passLayout.linearClampSampler.binding; } else if (passLayout.setLayouts[setIndex].usesShadowMapSampler) { sampler = m_shadowSampler; if (!passLayout.shadowMapSampler.IsValid() || passLayout.shadowMapSampler.set != setIndex) { DestroyOwnedDescriptorSet(descriptorSet); return nullptr; } binding = passLayout.shadowMapSampler.binding; } if (sampler == nullptr) { DestroyOwnedDescriptorSet(descriptorSet); return nullptr; } descriptorSet.set->UpdateSampler(binding, sampler); } } return descriptorSet.set; } BuiltinForwardPipeline::CachedDescriptorSet* BuiltinForwardPipeline::GetOrCreateDynamicDescriptorSet( const PassLayoutKey& passLayoutKey, const PassResourceLayout& passLayout, const BuiltinPassSetLayoutMetadata& setLayout, Core::uint32 setIndex, Core::uint64 objectId, const Resources::Material* material, const MaterialConstantPayloadView& materialConstants, const LightingConstants& lightingConstants, const ShadowReceiverConstants& shadowReceiverConstants, RHI::RHIResourceView* baseColorTextureView, RHI::RHIResourceView* shadowMapTextureView) { DynamicDescriptorSetKey key = {}; key.passLayout = passLayoutKey; key.setIndex = setIndex; key.objectId = objectId; key.material = material; 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() : 0; if (setLayout.usesMaterial) { if (!passLayout.material.IsValid() || passLayout.material.set != setIndex) { return nullptr; } if (!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.usesShadowReceiver) { if (!passLayout.shadowReceiver.IsValid() || passLayout.shadowReceiver.set != setIndex) { return nullptr; } cachedDescriptorSet.descriptorSet.set->WriteConstant( passLayout.shadowReceiver.binding, &shadowReceiverConstants, sizeof(shadowReceiverConstants)); } if (setLayout.usesBaseColorTexture) { if (baseColorTextureView == nullptr || !passLayout.baseColorTexture.IsValid() || passLayout.baseColorTexture.set != setIndex) { return nullptr; } if (cachedDescriptorSet.baseColorTextureView != baseColorTextureView) { cachedDescriptorSet.descriptorSet.set->Update( passLayout.baseColorTexture.binding, baseColorTextureView); } } if (setLayout.usesShadowMapTexture) { if (shadowMapTextureView == nullptr || !passLayout.shadowMapTexture.IsValid() || passLayout.shadowMapTexture.set != setIndex) { return nullptr; } if (cachedDescriptorSet.shadowMapTextureView != shadowMapTextureView) { cachedDescriptorSet.descriptorSet.set->Update( passLayout.shadowMapTexture.binding, shadowMapTextureView); } } cachedDescriptorSet.materialVersion = materialVersion; cachedDescriptorSet.baseColorTextureView = baseColorTextureView; cachedDescriptorSet.shadowMapTextureView = shadowMapTextureView; return &cachedDescriptorSet; } void BuiltinForwardPipeline::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 BuiltinForwardPipeline::DestroyPassResourceLayout(PassResourceLayout& passLayout) { for (OwnedDescriptorSet& descriptorSet : passLayout.staticDescriptorSets) { DestroyOwnedDescriptorSet(descriptorSet); } passLayout.staticDescriptorSets.clear(); if (passLayout.pipelineLayout != nullptr) { passLayout.pipelineLayout->Shutdown(); delete passLayout.pipelineLayout; passLayout.pipelineLayout = nullptr; } passLayout.setLayouts.clear(); passLayout.firstDescriptorSet = 0; passLayout.descriptorSetCount = 0; passLayout.perObject = {}; passLayout.material = {}; passLayout.lighting = {}; passLayout.shadowReceiver = {}; passLayout.baseColorTexture = {}; passLayout.linearClampSampler = {}; passLayout.shadowMapTexture = {}; passLayout.shadowMapSampler = {}; } const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources::Material* material) const { return ResolveBuiltinBaseColorTexture(material); } RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView( const VisibleRenderItem& visibleItem) { const Resources::Material* material = ResolveMaterial(visibleItem); const Resources::Texture* texture = ResolveTexture(material); if (texture != nullptr) { const RenderResourceCache::CachedTexture* cachedTexture = m_resourceCache.GetOrCreateTexture(m_device, texture); if (cachedTexture != nullptr && cachedTexture->shaderResourceView != nullptr) { return cachedTexture->shaderResourceView; } } return m_fallbackTextureView; } BuiltinForwardPipeline::LightingConstants BuiltinForwardPipeline::BuildLightingConstants( const RenderLightingData& lightingData) { LightingConstants lightingConstants = {}; if (lightingData.HasMainDirectionalLight()) { 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); } const Core::uint32 additionalLightCount = std::min( lightingData.additionalLightCount, kMaxLightingAdditionalLightCount); lightingConstants.lightingParams = Math::Vector4( static_cast(additionalLightCount), kForwardAmbientIntensity, 0.0f, 0.0f); for (Core::uint32 index = 0; index < additionalLightCount; ++index) { lightingConstants.additionalLights[index] = BuildAdditionalLightConstants(lightingData.additionalLights[index]); } return lightingConstants; } BuiltinForwardPipeline::AdditionalLightConstants BuiltinForwardPipeline::BuildAdditionalLightConstants( const RenderAdditionalLightData& lightData) { AdditionalLightConstants constants = {}; constants.colorAndIntensity = Math::Vector4( lightData.color.r, lightData.color.g, lightData.color.b, lightData.intensity); constants.positionAndRange = Math::Vector4( lightData.position.x, lightData.position.y, lightData.position.z, lightData.range); constants.directionAndType = Math::Vector4( lightData.direction.x, lightData.direction.y, lightData.direction.z, static_cast(lightData.type)); if (lightData.type == RenderLightType::Spot) { const float outerHalfAngleRadians = Math::Radians(lightData.spotAngle * 0.5f); const float innerHalfAngleRadians = outerHalfAngleRadians * kSpotInnerAngleRatio; constants.spotAnglesAndFlags = Math::Vector4( std::cos(outerHalfAngleRadians), std::cos(innerHalfAngleRadians), lightData.castsShadows ? 1.0f : 0.0f, 0.0f); } else { constants.spotAnglesAndFlags = Math::Vector4( 0.0f, 0.0f, lightData.castsShadows ? 1.0f : 0.0f, 0.0f); } return constants; } bool BuiltinForwardPipeline::DrawVisibleItem( const RenderContext& context, const RenderSceneData& sceneData, const VisibleRenderItem& visibleItem) { const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleItem.mesh); if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) { return false; } RHI::RHICommandList* commandList = context.commandList; RHI::RHIResourceView* vertexBuffers[] = { cachedMesh->vertexBufferView }; const uint64_t offsets[] = { 0 }; const uint32_t strides[] = { cachedMesh->vertexStride }; commandList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides); if (cachedMesh->indexBufferView != nullptr) { commandList->SetIndexBuffer(cachedMesh->indexBufferView, 0); } const PerObjectConstants constants = { sceneData.cameraData.projection, sceneData.cameraData.view, visibleItem.localToWorld.Transpose(), visibleItem.localToWorld.Inverse() }; const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting); const ShadowReceiverConstants shadowReceiverConstants = { sceneData.lighting.HasMainDirectionalShadow() ? sceneData.lighting.mainDirectionalShadow.viewProjection : Math::Matrix4x4::Identity(), sceneData.lighting.HasMainDirectionalShadow() ? sceneData.lighting.mainDirectionalShadow.shadowParams : Math::Vector4::Zero(), sceneData.lighting.HasMainDirectionalShadow() ? Math::Vector4(1.0f, 0.0f, 0.0f, 0.0f) : Math::Vector4::Zero() }; const Resources::Material* material = ResolveMaterial(visibleItem); const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(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); if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { return false; } RHI::RHIResourceView* baseColorTextureView = ResolveTextureView(visibleItem); if (passLayout->baseColorTexture.IsValid() && baseColorTextureView == nullptr) { return false; } RHI::RHIResourceView* shadowMapTextureView = sceneData.lighting.HasMainDirectionalShadow() ? sceneData.lighting.mainDirectionalShadow.shadowMap : m_fallbackTextureView; if (passLayout->shadowMapTexture.IsValid() && shadowMapTextureView == nullptr) { return false; } MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material); FallbackPerMaterialConstants fallbackMaterialConstants = {}; if (!materialConstants.IsValid()) { const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material); fallbackMaterialConstants.baseColorFactor = materialData.baseColorFactor; static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantField = { Containers::String("baseColorFactor"), Resources::MaterialPropertyType::Float4, 0, sizeof(FallbackPerMaterialConstants), sizeof(FallbackPerMaterialConstants) }; materialConstants.data = &fallbackMaterialConstants; materialConstants.size = sizeof(fallbackMaterialConstants); materialConstants.layout = { &kFallbackMaterialConstantField, 1, sizeof(fallbackMaterialConstants) }; } if (passLayout->descriptorSetCount > 0) { std::vector descriptorSets(passLayout->descriptorSetCount, nullptr); for (Core::uint32 descriptorOffset = 0; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) { const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset; if (setIndex >= passLayout->setLayouts.size()) { return false; } const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex]; RHI::RHIDescriptorSet* descriptorSet = nullptr; if (setLayout.usesPerObject || setLayout.usesLighting || setLayout.usesMaterial || setLayout.usesShadowReceiver || setLayout.usesTexture) { const Core::uint64 objectId = (setLayout.usesPerObject && visibleItem.gameObject != nullptr) ? visibleItem.gameObject->GetID() : 0; const Resources::Material* materialKey = (setLayout.usesMaterial || setLayout.usesBaseColorTexture) ? material : nullptr; CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet( passLayoutKey, *passLayout, setLayout, setIndex, objectId, materialKey, materialConstants, lightingConstants, shadowReceiverConstants, baseColorTextureView, shadowMapTextureView); if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { return false; } descriptorSet = cachedDescriptorSet->descriptorSet.set; if (setLayout.usesPerObject) { if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) { return false; } descriptorSet->WriteConstant( passLayout->perObject.binding, &constants, sizeof(constants)); } } else { descriptorSet = GetOrCreateStaticDescriptorSet(*passLayout, setIndex); if (descriptorSet == nullptr) { return false; } } descriptorSets[descriptorOffset] = descriptorSet; } commandList->SetGraphicsDescriptorSets( passLayout->firstDescriptorSet, passLayout->descriptorSetCount, descriptorSets.data(), passLayout->pipelineLayout); } if (visibleItem.hasSection) { const Containers::Array& sections = visibleItem.mesh->GetSections(); if (visibleItem.sectionIndex >= sections.Size()) { return false; } const Resources::MeshSection& section = sections[visibleItem.sectionIndex]; if (cachedMesh->indexBufferView != nullptr && section.indexCount > 0) { // MeshLoader flattens section indices into a single global index buffer. commandList->DrawIndexed(section.indexCount, 1, section.startIndex, 0, 0); } else if (section.vertexCount > 0) { commandList->Draw(section.vertexCount, 1, section.baseVertex, 0); } return true; } if (cachedMesh->indexBufferView != nullptr && cachedMesh->indexCount > 0) { commandList->DrawIndexed(cachedMesh->indexCount, 1, 0, 0, 0); } else if (cachedMesh->vertexCount > 0) { commandList->Draw(cachedMesh->vertexCount, 1, 0, 0); } return true; } } // namespace Pipelines } // namespace Rendering } // namespace XCEngine