#include "Rendering/Pipelines/BuiltinForwardPipeline.h" #include "Components/GameObject.h" #include "Components/MeshFilterComponent.h" #include "Components/MeshRendererComponent.h" #include "RHI/RHICommandList.h" #include "Debug/Logger.h" #include "Rendering/RenderMaterialUtility.h" #include "Rendering/RenderSurface.h" #include "Resources/Material/Material.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 { constexpr uint32_t kDescriptorFirstSet = 1; constexpr uint32_t kDescriptorSetCount = 4; const char kBuiltinForwardHlsl[] = R"( Texture2D gBaseColorTexture : register(t1); SamplerState gLinearSampler : register(s1); cbuffer PerObjectConstants : register(b1) { float4x4 gProjectionMatrix; float4x4 gViewMatrix; float4x4 gModelMatrix; }; struct VSInput { float3 position : POSITION; float2 texcoord : TEXCOORD0; }; struct PSInput { float4 position : SV_POSITION; float2 texcoord : TEXCOORD0; }; PSInput MainVS(VSInput input) { PSInput output; float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); float4 positionVS = mul(gViewMatrix, positionWS); output.position = mul(gProjectionMatrix, positionVS); output.texcoord = input.texcoord; return output; } float4 MainPS(PSInput input) : SV_TARGET { return gBaseColorTexture.Sample(gLinearSampler, input.texcoord); } )"; const char kBuiltinForwardVertexShader[] = R"(#version 430 layout(location = 0) in vec3 aPosition; layout(location = 1) in vec2 aTexCoord; layout(std140, binding = 1) uniform PerObjectConstants { mat4 gProjectionMatrix; mat4 gViewMatrix; mat4 gModelMatrix; }; out vec2 vTexCoord; void main() { vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); vec4 positionVS = gViewMatrix * positionWS; gl_Position = gProjectionMatrix * positionVS; vTexCoord = aTexCoord; } )"; const char kBuiltinForwardFragmentShader[] = R"(#version 430 layout(binding = 1) uniform sampler2D uBaseColorTexture; in vec2 vTexCoord; layout(location = 0) out vec4 fragColor; void main() { fragColor = texture(uBaseColorTexture, vTexCoord); } )"; RHI::GraphicsPipelineDesc CreatePipelineDesc( RHI::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout, 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(); if (backendType == RHI::RHIType::D3D12) { pipelineDesc.vertexShader.source.assign( kBuiltinForwardHlsl, kBuiltinForwardHlsl + std::strlen(kBuiltinForwardHlsl)); pipelineDesc.vertexShader.sourceLanguage = RHI::ShaderLanguage::HLSL; pipelineDesc.vertexShader.entryPoint = L"MainVS"; pipelineDesc.vertexShader.profile = L"vs_5_0"; pipelineDesc.fragmentShader.source.assign( kBuiltinForwardHlsl, kBuiltinForwardHlsl + std::strlen(kBuiltinForwardHlsl)); pipelineDesc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::HLSL; pipelineDesc.fragmentShader.entryPoint = L"MainPS"; pipelineDesc.fragmentShader.profile = L"ps_5_0"; } else { pipelineDesc.vertexShader.source.assign( kBuiltinForwardVertexShader, kBuiltinForwardVertexShader + std::strlen(kBuiltinForwardVertexShader)); pipelineDesc.vertexShader.sourceLanguage = RHI::ShaderLanguage::GLSL; pipelineDesc.vertexShader.profile = L"vs_4_30"; pipelineDesc.fragmentShader.source.assign( kBuiltinForwardFragmentShader, kBuiltinForwardFragmentShader + std::strlen(kBuiltinForwardFragmentShader)); pipelineDesc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::GLSL; pipelineDesc.fragmentShader.profile = L"fs_4_30"; } return pipelineDesc; } const Resources::Texture* FindMaterialTexture(const Resources::Material& material) { static const char* kTextureNames[] = { "baseColorTexture", "_BaseColorTexture", "_MainTex", "albedoTexture", "mainTexture", "texture" }; for (const char* textureName : kTextureNames) { const Resources::ResourceHandle textureHandle = material.GetTexture(textureName); if (textureHandle.Get() != nullptr && textureHandle->IsValid()) { return textureHandle.Get(); } } return nullptr; } } // namespace BuiltinForwardPipeline::BuiltinForwardPipeline() { m_passSequence.AddPass(std::make_unique(*this)); } BuiltinForwardPipeline::~BuiltinForwardPipeline() { Shutdown(); } 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 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 RHI::Viewport viewport = { 0.0f, 0.0f, static_cast(surface.GetWidth()), static_cast(surface.GetHeight()), 0.0f, 1.0f }; const RHI::Rect scissorRect = { 0, 0, static_cast(surface.GetWidth()), static_cast(surface.GetHeight()) }; 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); } } } if (surface.GetDepthAttachment() != nullptr && HasRenderClearFlag(sceneData.cameraData.clearFlags, RenderClearFlags::Depth)) { commandList->ClearDepthStencil(surface.GetDepthAttachment(), 1.0f, 0); } 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::Forward)) { 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) { RHI::DescriptorSetLayoutBinding constantBinding = {}; constantBinding.binding = 0; constantBinding.type = static_cast(RHI::DescriptorType::CBV); constantBinding.count = 1; RHI::DescriptorSetLayoutDesc constantLayout = {}; constantLayout.bindings = &constantBinding; constantLayout.bindingCount = 1; RHI::DescriptorSetLayoutBinding textureBinding = {}; textureBinding.binding = 0; textureBinding.type = static_cast(RHI::DescriptorType::SRV); textureBinding.count = 1; RHI::DescriptorSetLayoutDesc textureLayout = {}; textureLayout.bindings = &textureBinding; textureLayout.bindingCount = 1; RHI::DescriptorPoolDesc samplerPoolDesc = {}; samplerPoolDesc.type = RHI::DescriptorHeapType::Sampler; samplerPoolDesc.descriptorCount = 1; samplerPoolDesc.shaderVisible = true; m_samplerPool = context.device->CreateDescriptorPool(samplerPoolDesc); if (m_samplerPool == nullptr) { return false; } RHI::DescriptorSetLayoutBinding samplerBinding = {}; samplerBinding.binding = 0; samplerBinding.type = static_cast(RHI::DescriptorType::Sampler); samplerBinding.count = 1; RHI::DescriptorSetLayoutDesc samplerLayout = {}; samplerLayout.bindings = &samplerBinding; samplerLayout.bindingCount = 1; m_samplerSet = m_samplerPool->AllocateSet(samplerLayout); if (m_samplerSet == nullptr) { return false; } RHI::DescriptorSetLayoutBinding reservedBindings[3] = {}; reservedBindings[0].binding = 0; reservedBindings[0].type = static_cast(RHI::DescriptorType::CBV); reservedBindings[0].count = 1; reservedBindings[1].binding = 0; reservedBindings[1].type = static_cast(RHI::DescriptorType::SRV); reservedBindings[1].count = 1; reservedBindings[2].binding = 0; reservedBindings[2].type = static_cast(RHI::DescriptorType::Sampler); reservedBindings[2].count = 1; RHI::DescriptorSetLayoutDesc reservedLayout = {}; reservedLayout.bindings = reservedBindings; reservedLayout.bindingCount = 3; RHI::DescriptorSetLayoutDesc setLayouts[kDescriptorSetCount] = {}; setLayouts[0] = reservedLayout; setLayouts[1] = constantLayout; setLayouts[2] = textureLayout; setLayouts[3] = samplerLayout; RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; pipelineLayoutDesc.setLayouts = setLayouts; pipelineLayoutDesc.setLayoutCount = kDescriptorSetCount; m_pipelineLayout = context.device->CreatePipelineLayout(pipelineLayoutDesc); if (m_pipelineLayout == nullptr) { 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; } m_samplerSet->UpdateSampler(0, m_sampler); 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& perObjectSetPair : m_perObjectSets) { DestroyOwnedDescriptorSet(perObjectSetPair.second); } m_perObjectSets.clear(); for (auto& textureSetPair : m_textureSets) { DestroyOwnedDescriptorSet(textureSetPair.second); } m_textureSets.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; } if (m_pipelineLayout != nullptr) { m_pipelineLayout->Shutdown(); delete m_pipelineLayout; m_pipelineLayout = nullptr; } if (m_samplerSet != nullptr) { m_samplerSet->Shutdown(); delete m_samplerSet; m_samplerSet = nullptr; } if (m_samplerPool != nullptr) { m_samplerPool->Shutdown(); delete m_samplerPool; m_samplerPool = nullptr; } m_device = nullptr; m_initialized = false; } RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( const RenderContext& context, const Resources::Material* material) { const Resources::MaterialRenderState renderState = material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState(); const auto existing = m_pipelineStates.find(renderState); if (existing != m_pipelineStates.end()) { return existing->second; } const RHI::GraphicsPipelineDesc pipelineDesc = CreatePipelineDesc(context.backendType, m_pipelineLayout, 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(renderState, pipelineState); return pipelineState; } RHI::RHIDescriptorSet* BuiltinForwardPipeline::GetOrCreatePerObjectSet(Core::uint64 objectId) { const auto existing = m_perObjectSets.find(objectId); if (existing != m_perObjectSets.end()) { return existing->second.set; } RHI::DescriptorPoolDesc poolDesc = {}; poolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV; poolDesc.descriptorCount = 1; poolDesc.shaderVisible = false; OwnedDescriptorSet descriptorSet = {}; descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc); if (descriptorSet.pool == nullptr) { return nullptr; } RHI::DescriptorSetLayoutBinding binding = {}; binding.binding = 0; binding.type = static_cast(RHI::DescriptorType::CBV); binding.count = 1; RHI::DescriptorSetLayoutDesc layout = {}; layout.bindings = &binding; layout.bindingCount = 1; descriptorSet.set = descriptorSet.pool->AllocateSet(layout); if (descriptorSet.set == nullptr) { DestroyOwnedDescriptorSet(descriptorSet); return nullptr; } const auto result = m_perObjectSets.emplace(objectId, descriptorSet); return result.first->second.set; } RHI::RHIDescriptorSet* BuiltinForwardPipeline::GetOrCreateTextureSet(RHI::RHIResourceView* textureView) { if (textureView == nullptr) { return nullptr; } const auto existing = m_textureSets.find(textureView); if (existing != m_textureSets.end()) { return existing->second.set; } RHI::DescriptorPoolDesc poolDesc = {}; poolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV; poolDesc.descriptorCount = 1; poolDesc.shaderVisible = true; OwnedDescriptorSet descriptorSet = {}; descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc); if (descriptorSet.pool == nullptr) { return nullptr; } RHI::DescriptorSetLayoutBinding binding = {}; binding.binding = 0; binding.type = static_cast(RHI::DescriptorType::SRV); binding.count = 1; RHI::DescriptorSetLayoutDesc layout = {}; layout.bindings = &binding; layout.bindingCount = 1; descriptorSet.set = descriptorSet.pool->AllocateSet(layout); if (descriptorSet.set == nullptr) { DestroyOwnedDescriptorSet(descriptorSet); return nullptr; } descriptorSet.set->Update(0, textureView); const auto result = m_textureSets.emplace(textureView, descriptorSet); return result.first->second.set; } 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; } } const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources::Material* material) const { return material != nullptr ? FindMaterialTexture(*material) : nullptr; } 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() }; RHI::RHIResourceView* textureView = ResolveTextureView(visibleItem); if (textureView == nullptr) { return false; } RHI::RHIDescriptorSet* constantSet = GetOrCreatePerObjectSet( visibleItem.gameObject != nullptr ? visibleItem.gameObject->GetID() : 0); RHI::RHIDescriptorSet* textureSet = GetOrCreateTextureSet(textureView); if (constantSet == nullptr || textureSet == nullptr || m_samplerSet == nullptr) { return false; } constantSet->WriteConstant(0, &constants, sizeof(constants)); RHI::RHIDescriptorSet* descriptorSets[] = { constantSet, textureSet, m_samplerSet }; commandList->SetGraphicsDescriptorSets(kDescriptorFirstSet, 3, descriptorSets, m_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