#include "Rendering/Pipelines/BuiltinForwardPipeline.h" #include "Components/GameObject.h" #include "Debug/Logger.h" #include "RHI/RHICommandList.h" #include "RHI/RHIDevice.h" #include "Rendering/FrameData/RendererListUtils.h" #include "Rendering/Internal/RenderSurfacePipelineUtils.h" #include "Rendering/Internal/ShaderVariantUtils.h" #include "Rendering/Materials/RenderMaterialResolve.h" #include "Rendering/Materials/RenderMaterialStateUtils.h" #include "Resources/Material/Material.h" #include "Resources/Shader/Shader.h" #include "Resources/Texture/Texture.h" #include #include #include namespace XCEngine { namespace Rendering { namespace Pipelines { namespace { constexpr float kForwardAmbientIntensity = 0.28f; constexpr float kSpotInnerAngleRatio = 0.8f; Resources::ShaderKeywordSet ResolvePassKeywordSet( const RenderSceneData& sceneData, const Resources::Material* material) { return Resources::CombineShaderKeywordSets( sceneData.globalShaderKeywords, material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet()); } const Resources::ShaderPass* FindCompatibleSurfacePass( const Resources::Shader& shader, const RenderSceneData& sceneData, const Resources::Material* material, BuiltinMaterialPass pass, Resources::ShaderBackend backend) { const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material); for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) { if (ShaderPassMatchesBuiltinPass(shaderPass, pass) && ::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::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc); ApplyResolvedRenderState(&shaderPass, material, pipelineDesc); pipelineDesc.inputLayout = BuiltinForwardPipeline::BuildInputLayout(); const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType); const Resources::ShaderStageVariant* vertexVariant = shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet); const Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant(passName, Resources::ShaderType::Fragment, backend, keywordSet); if (vertexVariant != nullptr) { ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( shader.GetPath(), shaderPass, backend, *vertexVariant, pipelineDesc.vertexShader); } if (fragmentVariant != nullptr) { ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( shader.GetPath(), shaderPass, backend, *fragmentVariant, pipelineDesc.fragmentShader); } return pipelineDesc; } bool UsesDynamicSurfaceDescriptorSet(const BuiltinPassSetLayoutMetadata& setLayout) { return setLayout.usesPerObject || setLayout.usesLighting || setLayout.usesMaterial || setLayout.usesShadowReceiver || setLayout.usesTexture || setLayout.usesMaterialBuffers; } } // namespace RHI::InputLayoutDesc BuiltinForwardPipeline::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 = 0; 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); RHI::InputElementDesc backTexcoord = {}; backTexcoord.semanticName = "TEXCOORD"; backTexcoord.semanticIndex = 1; backTexcoord.format = static_cast(RHI::Format::R32G32_Float); backTexcoord.inputSlot = 0; backTexcoord.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, uv1)); inputLayout.elements.push_back(backTexcoord); RHI::InputElementDesc tangent = {}; tangent.semanticName = "TEXCOORD"; tangent.semanticIndex = 2; tangent.format = static_cast(RHI::Format::R32G32B32_Float); tangent.inputSlot = 0; tangent.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, tangent)); inputLayout.elements.push_back(tangent); RHI::InputElementDesc bitangent = {}; bitangent.semanticName = "TEXCOORD"; bitangent.semanticIndex = 3; bitangent.format = static_cast(RHI::Format::R32G32B32_Float); bitangent.inputSlot = 0; bitangent.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, bitangent)); inputLayout.elements.push_back(bitangent); RHI::InputElementDesc color = {}; color.semanticName = "COLOR"; color.semanticIndex = 0; color.format = static_cast(RHI::Format::R32G32B32A32_Float); color.inputSlot = 0; color.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, color)); inputLayout.elements.push_back(color); return inputLayout; } bool BuiltinForwardPipeline::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; } BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfaceShaderPass( const RenderSceneData& sceneData, const Resources::Material* material) const { ResolvedShaderPass resolved = {}; BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit; if (!TryResolveSurfacePassType(material, pass)) { return resolved; } const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType); if (material != nullptr && material->GetShader() != nullptr) { const Resources::Shader* materialShader = material->GetShader(); if (const Resources::ShaderPass* shaderPass = FindCompatibleSurfacePass(*materialShader, sceneData, 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, sceneData, material, pass, backend)) { resolved.shader = builtinShader; resolved.pass = shaderPass; resolved.passName = shaderPass->name; } } return resolved; } RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( const RenderContext& context, const RenderSurface& surface, const RenderSceneData& sceneData, const Resources::Material* material) { const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material); const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, 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 = 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::ResolveSurfaceColorAttachmentCount(surface); pipelineKey.renderTargetFormats = ::XCEngine::Rendering::Internal::ResolveSurfaceColorFormats(surface); pipelineKey.depthStencilFormat = static_cast(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface)); 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 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()) { 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; } const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources::Material* material) const { return ResolveBuiltinBaseColorTexture(material); } RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView( const Resources::Texture* texture) { if (texture == nullptr) { return nullptr; } const RenderResourceCache::CachedTexture* cachedTexture = m_resourceCache.GetOrCreateTexture(m_device, texture); if (cachedTexture != nullptr && cachedTexture->shaderResourceView != nullptr) { return cachedTexture->shaderResourceView; } return nullptr; } RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView( const VisibleRenderItem& visibleItem) { const Resources::Material* material = ResolveMaterial(visibleItem); const Resources::Texture* texture = ResolveTexture(material); RHI::RHIResourceView* textureView = ResolveTextureView(texture); return textureView != nullptr ? textureView : m_fallbackTexture2DView; } 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::ExecuteForwardOpaquePass( const ScenePhaseExecutionContext& executionContext) { return DrawVisibleItems( executionContext.frameContext, BuildDrawSettings(executionContext.scenePhase)); } bool BuiltinForwardPipeline::ExecuteForwardTransparentPass( const ScenePhaseExecutionContext& executionContext) { return DrawVisibleItems( executionContext.frameContext, BuildDrawSettings(executionContext.scenePhase)); } bool BuiltinForwardPipeline::DrawVisibleItem( const FrameExecutionContext& executionContext, const VisibleRenderItem& visibleItem) { const RenderContext& context = executionContext.renderContext; const RenderSceneData& sceneData = executionContext.sceneData; 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); ShadowReceiverConstants shadowReceiverConstants = {}; if (sceneData.lighting.HasMainDirectionalShadow()) { shadowReceiverConstants.worldToShadow = sceneData.lighting.mainDirectionalShadow.viewProjection; shadowReceiverConstants.shadowMapMetrics = sceneData.lighting.mainDirectionalShadow.mapMetrics; shadowReceiverConstants.shadowSampling = sceneData.lighting.mainDirectionalShadow.sampling; } const Resources::Material* material = ResolveMaterial(visibleItem); const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material); if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { return false; } const Resources::MaterialRenderState effectiveRenderState = ResolveEffectiveRenderState(resolvedShaderPass.pass, material); 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_fallbackTexture2DView; if (passLayout->shadowMapTexture.IsValid() && shadowMapTextureView == nullptr) { return false; } MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material); if (passLayout->material.IsValid() && !materialConstants.IsValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinForwardPipeline requires a schema-backed material constant payload"); return false; } 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 (UsesDynamicSurfaceDescriptorSet(setLayout)) { const Core::uint64 objectId = (setLayout.usesPerObject && visibleItem.gameObject != nullptr) ? visibleItem.gameObject->GetID() : 0; const Resources::Material* materialKey = (setLayout.usesMaterial || setLayout.usesBaseColorTexture || setLayout.usesMaterialBuffers || setLayout.usesMaterialTextures) ? 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); } ApplyDynamicRenderState(effectiveRenderState, *commandList); 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; } bool BuiltinForwardPipeline::DrawVisibleItems( const FrameExecutionContext& executionContext, const DrawSettings& drawSettings) { const RenderContext& context = executionContext.renderContext; const RenderSurface& surface = executionContext.surface; const RenderSceneData& sceneData = executionContext.sceneData; RHI::RHICommandList* commandList = context.commandList; RHI::RHIPipelineState* currentPipelineState = nullptr; bool drawFailed = false; auto drawVisibleItem = [&](const VisibleRenderItem& visibleItem) { if (drawFailed) { return; } const Resources::Material* material = ResolveMaterial(visibleItem); BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit; if (!TryResolveSurfacePassType(material, pass)) { return; } RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material); if (pipelineState == nullptr) { drawFailed = true; return; } if (pipelineState != currentPipelineState) { commandList->SetPipelineState(pipelineState); currentPipelineState = pipelineState; } if (!DrawVisibleItem(executionContext, visibleItem)) { drawFailed = true; } }; VisitRendererListVisibleItems( sceneData, drawSettings.rendererListDesc, drawVisibleItem); return !drawFailed; } } // namespace Pipelines } // namespace Rendering } // namespace XCEngine