Files
XCEngine/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp

1071 lines
39 KiB
C++
Raw Normal View History

#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 <algorithm>
#include <cstddef>
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 {
2026-04-03 17:18:46 +08:00
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,
2026-04-03 17:18:46 +08:00
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()) {
2026-04-03 17:18:46 +08:00
if (ShaderPassMatchesBuiltinPass(shaderPass, pass) &&
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shaderPass.name, backend)) {
return &shaderPass;
}
}
2026-04-03 17:18:46 +08:00
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<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
pipelineDesc.renderTargetCount = 1;
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(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<Detail::BuiltinForwardOpaquePass>(*this));
}
BuiltinForwardPipeline::~BuiltinForwardPipeline() {
Shutdown();
}
std::unique_ptr<RenderPipeline> BuiltinForwardPipelineAsset::CreatePipeline() const {
return std::make_unique<BuiltinForwardPipeline>();
}
RHI::InputLayoutDesc BuiltinForwardPipeline::BuildInputLayout() {
RHI::InputLayoutDesc inputLayout = {};
RHI::InputElementDesc position = {};
position.semanticName = "POSITION";
position.semanticIndex = 0;
position.format = static_cast<uint32_t>(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<uint32_t>(RHI::Format::R32G32B32_Float);
normal.inputSlot = 0;
normal.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, normal));
inputLayout.elements.push_back(normal);
RHI::InputElementDesc texcoord = {};
texcoord.semanticName = "TEXCOORD";
texcoord.semanticIndex = 0;
texcoord.format = static_cast<uint32_t>(RHI::Format::R32G32_Float);
texcoord.inputSlot = 0;
texcoord.alignedByteOffset = static_cast<uint32_t>(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<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
if (colorAttachments.empty()) {
return false;
}
std::vector<RHI::RHIResourceView*> 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<uint32_t>(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<float>(renderArea.x),
static_cast<float>(renderArea.y),
static_cast<float>(renderArea.width),
static_cast<float>(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);
2026-04-04 23:01:34 +08:00
if (sceneData.lighting.HasMainDirectionalShadow()) {
commandList->TransitionBarrier(
sceneData.lighting.mainDirectionalShadow.shadowMap,
RHI::ResourceStates::DepthWrite,
RHI::ResourceStates::PixelShaderResource);
}
RHI::RHIPipelineState* currentPipelineState = nullptr;
for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) {
const Resources::Material* material = ResolveMaterial(visibleItem);
2026-04-03 17:18:46 +08:00
BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit;
if (!TryResolveSurfacePassType(material, pass)) {
continue;
}
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, material);
if (pipelineState == nullptr) {
continue;
}
if (pipelineState != currentPipelineState) {
commandList->SetPipelineState(pipelineState);
currentPipelineState = pipelineState;
}
DrawVisibleItem(context, sceneData, visibleItem);
}
2026-04-04 23:01:34 +08:00
if (sceneData.lighting.HasMainDirectionalShadow()) {
commandList->TransitionBarrier(
sceneData.lighting.mainDirectionalShadow.shadowMap,
RHI::ResourceStates::PixelShaderResource,
RHI::ResourceStates::DepthWrite);
}
if (surface.IsAutoTransitionEnabled()) {
commandList->EndRenderPass();
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::Shader>(
Resources::GetBuiltinForwardLitShaderPath());
if (!m_builtinForwardShader.IsValid()) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline failed to load builtin forward shader resource");
return false;
}
2026-04-03 17:18:46 +08:00
m_builtinUnlitShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
Resources::GetBuiltinUnlitShaderPath());
if (!m_builtinUnlitShader.IsValid()) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline failed to load builtin unlit shader resource");
return false;
}
RHI::SamplerDesc samplerDesc = {};
samplerDesc.filter = static_cast<uint32_t>(RHI::FilterMode::Linear);
samplerDesc.addressU = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
samplerDesc.addressV = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
samplerDesc.addressW = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
samplerDesc.mipLodBias = 0.0f;
samplerDesc.maxAnisotropy = 1;
samplerDesc.comparisonFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
samplerDesc.minLod = 0.0f;
samplerDesc.maxLod = 1000.0f;
m_sampler = context.device->CreateSampler(samplerDesc);
if (m_sampler == nullptr) {
return false;
}
2026-04-04 23:01:34 +08:00
RHI::SamplerDesc shadowSamplerDesc = samplerDesc;
shadowSamplerDesc.filter = static_cast<uint32_t>(RHI::FilterMode::Point);
m_shadowSampler = context.device->CreateSampler(shadowSamplerDesc);
if (m_shadowSampler == 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<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
textureDesc.textureType = static_cast<uint32_t>(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<uint32_t>(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;
}
2026-04-04 23:01:34 +08:00
if (m_shadowSampler != nullptr) {
m_shadowSampler->Shutdown();
delete m_shadowSampler;
m_shadowSampler = nullptr;
}
m_device = nullptr;
m_initialized = false;
m_builtinForwardShader.Reset();
2026-04-03 17:18:46 +08:00
m_builtinUnlitShader.Reset();
}
2026-04-03 17:18:46 +08:00
BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfaceShaderPass(
const Resources::Material* material) const {
ResolvedShaderPass resolved = {};
2026-04-03 17:18:46 +08:00
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 =
2026-04-03 17:18:46 +08:00
FindCompatibleSurfacePass(*materialShader, material, pass, backend)) {
resolved.shader = materialShader;
resolved.pass = shaderPass;
resolved.passName = shaderPass->name;
return resolved;
}
}
2026-04-03 17:18:46 +08:00
const Resources::ResourceHandle<Resources::Shader>* builtinShaderHandle =
pass == BuiltinMaterialPass::Unlit ? &m_builtinUnlitShader : &m_builtinForwardShader;
if (builtinShaderHandle->IsValid()) {
const Resources::Shader* builtinShader = builtinShaderHandle->Get();
if (const Resources::ShaderPass* shaderPass =
2026-04-03 17:18:46 +08:00
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;
};
Containers::Array<Resources::ShaderResourceBindingDesc> resourceBindings = resolvedShaderPass.pass->resources;
if (resourceBindings.Empty()) {
resourceBindings = BuildLegacyBuiltinForwardPassResourceBindings();
}
BuiltinPassResourceBindingPlan bindingPlan = {};
Containers::String bindingPlanError;
if (!TryBuildBuiltinPassResourceBindingPlan(resourceBindings, bindingPlan, &bindingPlanError)) {
return failLayout(bindingPlanError.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;
2026-04-04 23:01:34 +08:00
passLayout.shadowReceiver = bindingPlan.shadowReceiver;
passLayout.baseColorTexture = bindingPlan.baseColorTexture;
passLayout.linearClampSampler = bindingPlan.linearClampSampler;
2026-04-04 23:01:34 +08:00
passLayout.shadowMapTexture = bindingPlan.shadowMapTexture;
passLayout.shadowMapSampler = bindingPlan.shadowMapSampler;
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()) {
BuiltinPassSetLayoutMetadata& compatibilitySet = passLayout.setLayouts[0];
if (bindingPlan.usesConstantBuffers) {
compatibilitySet.bindings.push_back({
0,
static_cast<uint32_t>(RHI::DescriptorType::CBV),
1,
0
});
}
if (bindingPlan.usesTextures) {
compatibilitySet.bindings.push_back({
0,
static_cast<uint32_t>(RHI::DescriptorType::SRV),
1,
0
});
}
if (bindingPlan.usesSamplers) {
compatibilitySet.bindings.push_back({
0,
static_cast<uint32_t>(RHI::DescriptorType::Sampler),
1,
0
});
}
RefreshBuiltinPassSetLayoutMetadata(compatibilitySet);
}
std::vector<RHI::DescriptorSetLayoutDesc> 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<uint32_t>(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) {
2026-04-03 17:18:46 +08:00
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material);
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
2026-04-03 17:18:46 +08:00
"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) {
2026-04-04 23:01:34 +08:00
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;
}
2026-04-04 23:01:34 +08:00
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,
2026-04-04 23:01:34 +08:00
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;
2026-04-04 23:01:34 +08:00
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);
}
2026-04-04 23:01:34 +08:00
}
2026-04-04 23:01:34 +08:00
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;
}
2026-04-04 23:01:34 +08:00
if (cachedDescriptorSet.baseColorTextureView != baseColorTextureView) {
cachedDescriptorSet.descriptorSet.set->Update(
passLayout.baseColorTexture.binding,
baseColorTextureView);
}
}
2026-04-04 23:01:34 +08:00
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);
}
}
2026-04-04 23:01:34 +08:00
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 = {};
2026-04-04 23:01:34 +08:00
passLayout.shadowReceiver = {};
passLayout.baseColorTexture = {};
passLayout.linearClampSampler = {};
2026-04-04 23:01:34 +08:00
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;
}
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()
};
2026-04-04 23:01:34 +08:00
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);
2026-04-03 17:18:46 +08:00
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;
}
2026-04-04 23:01:34 +08:00
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<RHI::RHIDescriptorSet*> 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;
2026-04-04 23:01:34 +08:00
if (setLayout.usesPerObject ||
setLayout.usesMaterial ||
setLayout.usesShadowReceiver ||
setLayout.usesTexture) {
const Core::uint64 objectId =
(setLayout.usesPerObject && visibleItem.gameObject != nullptr)
? visibleItem.gameObject->GetID()
: 0;
const Resources::Material* materialKey =
2026-04-04 23:01:34 +08:00
(setLayout.usesMaterial || setLayout.usesBaseColorTexture) ? material : nullptr;
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
passLayoutKey,
*passLayout,
setLayout,
setIndex,
objectId,
materialKey,
materialConstants,
2026-04-04 23:01:34 +08:00
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<Resources::MeshSection>& 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