diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 9d9ad09b..930f2cf8 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -462,6 +462,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/SceneRenderRequestPlanner.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/SceneRenderer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp # Scene ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/Scene.h diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp index caeb961e..7936a7d0 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp @@ -1,21 +1,12 @@ #include "Rendering/Pipelines/BuiltinForwardPipeline.h" -#include "Components/GameObject.h" -#include "Components/MeshFilterComponent.h" -#include "Components/MeshRendererComponent.h" #include "Debug/Logger.h" #include "Core/Asset/ResourceManager.h" #include "RHI/RHICommandList.h" -#include "Rendering/Detail/ShaderVariantUtils.h" #include "Rendering/RenderMaterialUtility.h" #include "Rendering/RenderSurface.h" #include "Resources/BuiltinResources.h" -#include "Resources/Material/Material.h" -#include "Resources/Shader/Shader.h" -#include "Resources/Texture/Texture.h" -#include -#include #include namespace XCEngine { @@ -53,9 +44,6 @@ private: namespace { -constexpr float kForwardAmbientIntensity = 0.28f; -constexpr float kSpotInnerAngleRatio = 0.8f; - bool IsDepthFormat(RHI::Format format) { return format == RHI::Format::D24_UNorm_S8_UInt || format == RHI::Format::D32_Float; @@ -77,82 +65,6 @@ bool TryResolveSurfacePassType( 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::BuiltinForwardPipeline() { @@ -482,652 +394,6 @@ void BuiltinForwardPipeline::DestroyPipelineResources() { m_builtinUnlitShader.Reset(); } -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 diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp new file mode 100644 index 00000000..825530d2 --- /dev/null +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp @@ -0,0 +1,766 @@ +#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