#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 namespace XCEngine { namespace Rendering { namespace Pipelines { namespace Detail { class BuiltinForwardOpaquePass final : public RenderPass { public: explicit BuiltinForwardOpaquePass(BuiltinForwardPipeline& pipeline) : m_pipeline(pipeline) { } const char* GetName() const override { return "BuiltinForwardOpaquePass"; } bool Initialize(const RenderContext& context) override { return m_pipeline.EnsureInitialized(context); } void Shutdown() override { m_pipeline.DestroyPipelineResources(); } bool Execute(const RenderPassContext& context) override { return m_pipeline.ExecuteForwardOpaquePass(context); } private: BuiltinForwardPipeline& m_pipeline; }; } // namespace Detail namespace { enum class ForwardPassSemantic : uint8_t { Unknown = 0, PerObject, Material, BaseColorTexture, LinearClampSampler }; ForwardPassSemantic ResolveForwardPassSemantic(const Resources::ShaderResourceBindingDesc& binding) { Containers::String semantic = NormalizeBuiltinPassMetadataValue(binding.semantic); if (semantic.Empty()) { semantic = NormalizeBuiltinPassMetadataValue(binding.name); } if (semantic == Containers::String("perobject") || semantic == Containers::String("perobjectconstants")) { return ForwardPassSemantic::PerObject; } if (semantic == Containers::String("material") || semantic == Containers::String("materialconstants")) { return ForwardPassSemantic::Material; } if (semantic == Containers::String("basecolortexture") || semantic == Containers::String("maintex")) { return ForwardPassSemantic::BaseColorTexture; } if (semantic == Containers::String("linearclampsampler")) { return ForwardPassSemantic::LinearClampSampler; } return ForwardPassSemantic::Unknown; } RHI::DescriptorType ToDescriptorType(Resources::ShaderResourceType type) { switch (type) { case Resources::ShaderResourceType::ConstantBuffer: return RHI::DescriptorType::CBV; case Resources::ShaderResourceType::Texture2D: case Resources::ShaderResourceType::TextureCube: return RHI::DescriptorType::SRV; case Resources::ShaderResourceType::Sampler: return RHI::DescriptorType::Sampler; default: return RHI::DescriptorType::CBV; } } RHI::DescriptorHeapType ResolveDescriptorHeapType(Resources::ShaderResourceType type) { return type == Resources::ShaderResourceType::Sampler ? RHI::DescriptorHeapType::Sampler : RHI::DescriptorHeapType::CBV_SRV_UAV; } bool IsShaderVisibleSet(const std::vector& bindings) { for (const RHI::DescriptorSetLayoutBinding& binding : bindings) { const RHI::DescriptorType descriptorType = static_cast(binding.type); if (descriptorType == RHI::DescriptorType::SRV || descriptorType == RHI::DescriptorType::UAV || descriptorType == RHI::DescriptorType::Sampler) { return true; } } return false; } uint32_t CountHeapDescriptors( RHI::DescriptorHeapType heapType, const std::vector& bindings) { uint32_t descriptorCount = 0; for (const RHI::DescriptorSetLayoutBinding& binding : bindings) { const RHI::DescriptorType descriptorType = static_cast(binding.type); if (heapType == RHI::DescriptorHeapType::Sampler) { if (descriptorType == RHI::DescriptorType::Sampler) { descriptorCount += binding.count > 0 ? binding.count : 1u; } continue; } if (descriptorType != RHI::DescriptorType::Sampler) { descriptorCount += binding.count > 0 ? binding.count : 1u; } } return descriptorCount > 0 ? descriptorCount : 1u; } bool BindingNumberExists( const std::vector& bindings, uint32_t bindingNumber) { for (const RHI::DescriptorSetLayoutBinding& binding : bindings) { if (binding.binding == bindingNumber) { return true; } } return false; } std::vector BuildLegacyForwardResourceBindings() { std::vector bindings(4); bindings[0].name = "PerObjectConstants"; bindings[0].type = Resources::ShaderResourceType::ConstantBuffer; bindings[0].set = 1; bindings[0].binding = 0; bindings[0].semantic = "PerObject"; bindings[1].name = "MaterialConstants"; bindings[1].type = Resources::ShaderResourceType::ConstantBuffer; bindings[1].set = 2; bindings[1].binding = 0; bindings[1].semantic = "Material"; bindings[2].name = "BaseColorTexture"; bindings[2].type = Resources::ShaderResourceType::Texture2D; bindings[2].set = 3; bindings[2].binding = 0; bindings[2].semantic = "BaseColorTexture"; bindings[3].name = "LinearClampSampler"; bindings[3].type = Resources::ShaderResourceType::Sampler; bindings[3].set = 4; bindings[3].binding = 0; bindings[3].semantic = "LinearClampSampler"; return bindings; } const Resources::ShaderPass* FindForwardCompatiblePass( const Resources::Shader& shader, const Resources::Material* material, 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, BuiltinMaterialPass::ForwardLit) && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shaderPass.name, backend)) { return &shaderPass; } } 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() { m_passSequence.AddPass(std::make_unique(*this)); } BuiltinForwardPipeline::~BuiltinForwardPipeline() { Shutdown(); } std::unique_ptr BuiltinForwardPipelineAsset::CreatePipeline() const { return std::make_unique(); } 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); return inputLayout; } bool BuiltinForwardPipeline::Initialize(const RenderContext& context) { return m_passSequence.Initialize(context); } void BuiltinForwardPipeline::Shutdown() { m_passSequence.Shutdown(); } bool BuiltinForwardPipeline::Render( const RenderContext& context, const RenderSurface& surface, const RenderSceneData& sceneData) { if (!Initialize(context)) { return false; } const RenderPassContext passContext = { context, surface, sceneData }; return m_passSequence.Execute(passContext); } bool BuiltinForwardPipeline::ExecuteForwardOpaquePass(const RenderPassContext& passContext) { const RenderContext& context = passContext.renderContext; const RenderSurface& surface = passContext.surface; const RenderSceneData& sceneData = passContext.sceneData; const std::vector& colorAttachments = surface.GetColorAttachments(); if (colorAttachments.empty()) { return false; } std::vector renderTargets = colorAttachments; RHI::RHICommandList* commandList = context.commandList; if (surface.IsAutoTransitionEnabled()) { for (RHI::RHIResourceView* renderTarget : renderTargets) { if (renderTarget != nullptr) { commandList->TransitionBarrier( renderTarget, surface.GetColorStateBefore(), RHI::ResourceStates::RenderTarget); } } } commandList->SetRenderTargets( static_cast(renderTargets.size()), renderTargets.data(), surface.GetDepthAttachment()); const Math::RectInt renderArea = surface.GetRenderArea(); if (renderArea.width <= 0 || renderArea.height <= 0) { return false; } const RHI::Viewport viewport = { static_cast(renderArea.x), static_cast(renderArea.y), static_cast(renderArea.width), static_cast(renderArea.height), 0.0f, 1.0f }; const RHI::Rect scissorRect = { renderArea.x, renderArea.y, renderArea.x + renderArea.width, renderArea.y + renderArea.height }; const RHI::Rect clearRects[] = { scissorRect }; commandList->SetViewport(viewport); commandList->SetScissorRect(scissorRect); const Math::Color clearColor = surface.HasClearColorOverride() ? surface.GetClearColorOverride() : sceneData.cameraData.clearColor; const float clearValues[4] = { clearColor.r, clearColor.g, clearColor.b, clearColor.a }; if (HasRenderClearFlag(sceneData.cameraData.clearFlags, RenderClearFlags::Color)) { for (RHI::RHIResourceView* renderTarget : renderTargets) { if (renderTarget != nullptr) { commandList->ClearRenderTarget(renderTarget, clearValues, 1, clearRects); } } } if (surface.GetDepthAttachment() != nullptr && HasRenderClearFlag(sceneData.cameraData.clearFlags, RenderClearFlags::Depth)) { commandList->ClearDepthStencil(surface.GetDepthAttachment(), 1.0f, 0, 1, clearRects); } commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); RHI::RHIPipelineState* currentPipelineState = nullptr; for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) { const Resources::Material* material = ResolveMaterial(visibleItem); if (!MatchesBuiltinPass(material, BuiltinMaterialPass::ForwardLit)) { continue; } RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, material); if (pipelineState == nullptr) { continue; } if (pipelineState != currentPipelineState) { commandList->SetPipelineState(pipelineState); currentPipelineState = pipelineState; } DrawVisibleItem(context, sceneData, visibleItem); } if (surface.IsAutoTransitionEnabled()) { for (RHI::RHIResourceView* renderTarget : renderTargets) { if (renderTarget != nullptr) { commandList->TransitionBarrier( renderTarget, RHI::ResourceStates::RenderTarget, surface.GetColorStateAfter()); } } } return true; } bool BuiltinForwardPipeline::EnsureInitialized(const RenderContext& context) { if (!context.IsValid()) { return false; } if (m_initialized && m_device == context.device && m_backendType == context.backendType) { return true; } DestroyPipelineResources(); m_device = context.device; m_backendType = context.backendType; m_initialized = CreatePipelineResources(context); return m_initialized; } bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& context) { m_builtinForwardShader = Resources::ResourceManager::Get().Load( Resources::GetBuiltinForwardLitShaderPath()); if (!m_builtinForwardShader.IsValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinForwardPipeline failed to load builtin forward shader resource"); return false; } RHI::SamplerDesc samplerDesc = {}; samplerDesc.filter = static_cast(RHI::FilterMode::Linear); samplerDesc.addressU = static_cast(RHI::TextureAddressMode::Clamp); samplerDesc.addressV = static_cast(RHI::TextureAddressMode::Clamp); samplerDesc.addressW = static_cast(RHI::TextureAddressMode::Clamp); samplerDesc.mipLodBias = 0.0f; samplerDesc.maxAnisotropy = 1; samplerDesc.comparisonFunc = static_cast(RHI::ComparisonFunc::Always); samplerDesc.minLod = 0.0f; samplerDesc.maxLod = 1000.0f; m_sampler = context.device->CreateSampler(samplerDesc); if (m_sampler == nullptr) { return false; } const unsigned char whitePixel[4] = { 255, 255, 255, 255 }; RHI::TextureDesc textureDesc = {}; textureDesc.width = 1; textureDesc.height = 1; textureDesc.depth = 1; textureDesc.mipLevels = 1; textureDesc.arraySize = 1; textureDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); textureDesc.textureType = static_cast(RHI::TextureType::Texture2D); textureDesc.sampleCount = 1; textureDesc.sampleQuality = 0; textureDesc.flags = 0; m_fallbackTexture = context.device->CreateTexture(textureDesc, whitePixel, sizeof(whitePixel), 4); if (m_fallbackTexture == nullptr) { return false; } RHI::ResourceViewDesc textureViewDesc = {}; textureViewDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); textureViewDesc.dimension = RHI::ResourceViewDimension::Texture2D; textureViewDesc.mipLevel = 0; m_fallbackTextureView = context.device->CreateShaderResourceView(m_fallbackTexture, textureViewDesc); if (m_fallbackTextureView == nullptr) { return false; } return true; } void BuiltinForwardPipeline::DestroyPipelineResources() { m_resourceCache.Shutdown(); for (auto& pipelinePair : m_pipelineStates) { if (pipelinePair.second != nullptr) { pipelinePair.second->Shutdown(); delete pipelinePair.second; } } m_pipelineStates.clear(); for (auto& descriptorSetPair : m_dynamicDescriptorSets) { DestroyOwnedDescriptorSet(descriptorSetPair.second.descriptorSet); } m_dynamicDescriptorSets.clear(); for (auto& passLayoutPair : m_passResourceLayouts) { DestroyPassResourceLayout(passLayoutPair.second); } m_passResourceLayouts.clear(); if (m_fallbackTextureView != nullptr) { m_fallbackTextureView->Shutdown(); delete m_fallbackTextureView; m_fallbackTextureView = nullptr; } if (m_fallbackTexture != nullptr) { m_fallbackTexture->Shutdown(); delete m_fallbackTexture; m_fallbackTexture = nullptr; } if (m_sampler != nullptr) { m_sampler->Shutdown(); delete m_sampler; m_sampler = nullptr; } m_device = nullptr; m_initialized = false; m_builtinForwardShader.Reset(); } BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveForwardShaderPass( const Resources::Material* material) const { ResolvedShaderPass 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 = FindForwardCompatiblePass(*materialShader, material, backend)) { resolved.shader = materialShader; resolved.pass = shaderPass; resolved.passName = shaderPass->name; return resolved; } } if (m_builtinForwardShader.IsValid()) { const Resources::Shader* builtinShader = m_builtinForwardShader.Get(); if (const Resources::ShaderPass* shaderPass = FindForwardCompatiblePass(*builtinShader, nullptr, 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; } std::vector resourceBindings; if (resolvedShaderPass.pass->resources.Empty()) { resourceBindings = BuildLegacyForwardResourceBindings(); } else { resourceBindings.reserve(resolvedShaderPass.pass->resources.Size()); for (const Resources::ShaderResourceBindingDesc& binding : resolvedShaderPass.pass->resources) { resourceBindings.push_back(binding); } } PassResourceLayout passLayout = {}; auto failLayout = [this, &passLayout](const char* message) -> PassResourceLayout* { Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message); DestroyPassResourceLayout(passLayout); return nullptr; }; Core::uint32 minBoundSet = UINT32_MAX; Core::uint32 maxBoundSet = 0; Core::uint32 maxSetIndex = 0; bool hasAnyResource = false; bool usesConstantBuffers = false; bool usesTextures = false; bool usesSamplers = false; for (const Resources::ShaderResourceBindingDesc& binding : resourceBindings) { maxSetIndex = std::max(maxSetIndex, binding.set); hasAnyResource = true; minBoundSet = std::min(minBoundSet, binding.set); maxBoundSet = std::max(maxBoundSet, binding.set); switch (binding.type) { case Resources::ShaderResourceType::ConstantBuffer: usesConstantBuffers = true; break; case Resources::ShaderResourceType::Texture2D: case Resources::ShaderResourceType::TextureCube: usesTextures = true; break; case Resources::ShaderResourceType::Sampler: usesSamplers = true; break; default: break; } } if (hasAnyResource) { passLayout.setLayouts.resize(static_cast(maxSetIndex) + 1u); passLayout.staticDescriptorSets.resize(passLayout.setLayouts.size()); passLayout.firstDescriptorSet = minBoundSet; passLayout.descriptorSetCount = maxBoundSet - minBoundSet + 1u; } for (const Resources::ShaderResourceBindingDesc& binding : resourceBindings) { ForwardPassSemantic semantic = ResolveForwardPassSemantic(binding); if (semantic == ForwardPassSemantic::Unknown) { return failLayout("BuiltinForwardPipeline encountered an unsupported forward shader resource semantic"); } if (binding.set >= passLayout.setLayouts.size()) { return failLayout("BuiltinForwardPipeline encountered an invalid forward shader resource set"); } const RHI::DescriptorType descriptorType = ToDescriptorType(binding.type); const RHI::DescriptorHeapType heapType = ResolveDescriptorHeapType(binding.type); PassSetLayoutMetadata& setLayout = passLayout.setLayouts[binding.set]; if (!setLayout.bindings.empty() && setLayout.heapType != heapType) { return failLayout("BuiltinForwardPipeline does not support mixing sampler and non-sampler bindings in one set"); } if (BindingNumberExists(setLayout.bindings, binding.binding)) { return failLayout("BuiltinForwardPipeline encountered duplicate bindings inside one descriptor set"); } if (setLayout.bindings.empty()) { setLayout.heapType = heapType; } RHI::DescriptorSetLayoutBinding layoutBinding = {}; layoutBinding.binding = binding.binding; layoutBinding.type = static_cast(descriptorType); layoutBinding.count = 1; setLayout.bindings.push_back(layoutBinding); switch (semantic) { case ForwardPassSemantic::PerObject: if (binding.type != Resources::ShaderResourceType::ConstantBuffer || passLayout.perObject.IsValid()) { return failLayout("BuiltinForwardPipeline requires a single constant-buffer PerObject resource"); } passLayout.perObject = { binding.set, binding.binding }; setLayout.usesPerObject = true; break; case ForwardPassSemantic::Material: if (binding.type != Resources::ShaderResourceType::ConstantBuffer || passLayout.material.IsValid()) { return failLayout("BuiltinForwardPipeline requires a single constant-buffer Material resource"); } passLayout.material = { binding.set, binding.binding }; setLayout.usesMaterial = true; break; case ForwardPassSemantic::BaseColorTexture: if ((binding.type != Resources::ShaderResourceType::Texture2D && binding.type != Resources::ShaderResourceType::TextureCube) || passLayout.baseColorTexture.IsValid()) { return failLayout("BuiltinForwardPipeline requires a single texture BaseColorTexture resource"); } passLayout.baseColorTexture = { binding.set, binding.binding }; setLayout.usesTexture = true; break; case ForwardPassSemantic::LinearClampSampler: if (binding.type != Resources::ShaderResourceType::Sampler || passLayout.linearClampSampler.IsValid()) { return failLayout("BuiltinForwardPipeline requires a single sampler LinearClampSampler resource"); } passLayout.linearClampSampler = { binding.set, binding.binding }; setLayout.usesSampler = true; break; default: return failLayout("BuiltinForwardPipeline encountered an unsupported forward shader resource semantic"); } } if (!passLayout.perObject.IsValid()) { return failLayout("BuiltinForwardPipeline requires a PerObject resource binding"); } if (hasAnyResource && passLayout.firstDescriptorSet > 0 && !passLayout.setLayouts.empty() && passLayout.setLayouts[0].bindings.empty()) { PassSetLayoutMetadata& compatibilitySet = passLayout.setLayouts[0]; if (usesConstantBuffers) { compatibilitySet.bindings.push_back({ 0, static_cast(RHI::DescriptorType::CBV), 1, 0 }); } if (usesTextures) { compatibilitySet.bindings.push_back({ 0, static_cast(RHI::DescriptorType::SRV), 1, 0 }); } if (usesSamplers) { compatibilitySet.bindings.push_back({ 0, static_cast(RHI::DescriptorType::Sampler), 1, 0 }); } compatibilitySet.shaderVisible = true; } for (Core::uint32 setIndex = 0; setIndex < passLayout.setLayouts.size(); ++setIndex) { PassSetLayoutMetadata& setLayout = passLayout.setLayouts[setIndex]; std::sort( setLayout.bindings.begin(), setLayout.bindings.end(), [](const RHI::DescriptorSetLayoutBinding& left, const RHI::DescriptorSetLayoutBinding& right) { return left.binding < right.binding; }); setLayout.shaderVisible = IsShaderVisibleSet(setLayout.bindings); setLayout.layout.bindings = setLayout.bindings.empty() ? nullptr : setLayout.bindings.data(); setLayout.layout.bindingCount = static_cast(setLayout.bindings.size()); } 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; for (PassSetLayoutMetadata& setLayout : storedPassLayout.setLayouts) { setLayout.layout.bindings = setLayout.bindings.empty() ? nullptr : setLayout.bindings.data(); setLayout.layout.bindingCount = static_cast(setLayout.bindings.size()); } return &storedPassLayout; } RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( const RenderContext& context, const Resources::Material* material) { const ResolvedShaderPass resolvedShaderPass = ResolveForwardShaderPass(material); if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinForwardPipeline could not resolve a valid ForwardLit 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 PassSetLayoutMetadata& setLayout, OwnedDescriptorSet& descriptorSet) { RHI::DescriptorPoolDesc poolDesc = {}; poolDesc.type = setLayout.heapType; poolDesc.descriptorCount = CountHeapDescriptors(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) { if (m_sampler == nullptr || !passLayout.linearClampSampler.IsValid() || passLayout.linearClampSampler.set != setIndex) { DestroyOwnedDescriptorSet(descriptorSet); return nullptr; } descriptorSet.set->UpdateSampler(passLayout.linearClampSampler.binding, m_sampler); } } return descriptorSet.set; } BuiltinForwardPipeline::CachedDescriptorSet* BuiltinForwardPipeline::GetOrCreateDynamicDescriptorSet( const PassLayoutKey& passLayoutKey, const PassResourceLayout& passLayout, const PassSetLayoutMetadata& setLayout, Core::uint32 setIndex, Core::uint64 objectId, const Resources::Material* material, const MaterialConstantPayloadView& materialConstants, RHI::RHIResourceView* textureView) { 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 || setLayout.usesTexture) && (cachedDescriptorSet.materialVersion != materialVersion || cachedDescriptorSet.textureView != textureView)) { if (setLayout.usesMaterial) { if (!passLayout.material.IsValid() || passLayout.material.set != setIndex) { return nullptr; } if (!materialConstants.IsValid()) { return nullptr; } cachedDescriptorSet.descriptorSet.set->WriteConstant( passLayout.material.binding, materialConstants.data, materialConstants.size); } if (setLayout.usesTexture) { if (textureView == nullptr || !passLayout.baseColorTexture.IsValid() || passLayout.baseColorTexture.set != setIndex) { return nullptr; } cachedDescriptorSet.descriptorSet.set->Update(passLayout.baseColorTexture.binding, textureView); } cachedDescriptorSet.materialVersion = materialVersion; cachedDescriptorSet.textureView = textureView; } 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.baseColorTexture = {}; passLayout.linearClampSampler = {}; } 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; } 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(), sceneData.lighting.HasMainDirectionalLight() ? Math::Vector4( sceneData.lighting.mainDirectionalLight.direction.x, sceneData.lighting.mainDirectionalLight.direction.y, sceneData.lighting.mainDirectionalLight.direction.z, sceneData.lighting.mainDirectionalLight.intensity) : Math::Vector4::Zero(), sceneData.lighting.HasMainDirectionalLight() ? Math::Vector4( sceneData.lighting.mainDirectionalLight.color.r, sceneData.lighting.mainDirectionalLight.color.g, sceneData.lighting.mainDirectionalLight.color.b, 1.0f) : Math::Vector4::Zero() }; const Resources::Material* material = ResolveMaterial(visibleItem); const ResolvedShaderPass resolvedShaderPass = ResolveForwardShaderPass(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* textureView = ResolveTextureView(visibleItem); if (passLayout->baseColorTexture.IsValid() && textureView == 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 PassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex]; RHI::RHIDescriptorSet* descriptorSet = nullptr; if (setLayout.usesPerObject || setLayout.usesMaterial || setLayout.usesTexture) { const Core::uint64 objectId = (setLayout.usesPerObject && visibleItem.gameObject != nullptr) ? visibleItem.gameObject->GetID() : 0; const Resources::Material* materialKey = (setLayout.usesMaterial || setLayout.usesTexture) ? material : nullptr; CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet( passLayoutKey, *passLayout, setLayout, setIndex, objectId, materialKey, materialConstants, textureView); 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