2026-03-26 20:43:17 +08:00
|
|
|
#include "Rendering/Pipelines/BuiltinForwardPipeline.h"
|
|
|
|
|
|
2026-03-27 13:01:17 +08:00
|
|
|
#include "Components/GameObject.h"
|
2026-03-26 20:43:17 +08:00
|
|
|
#include "Components/MeshFilterComponent.h"
|
|
|
|
|
#include "Components/MeshRendererComponent.h"
|
2026-03-27 13:01:17 +08:00
|
|
|
#include "Debug/Logger.h"
|
2026-04-02 19:00:48 +08:00
|
|
|
#include "Core/Asset/ResourceManager.h"
|
|
|
|
|
#include "RHI/RHICommandList.h"
|
2026-04-02 19:17:22 +08:00
|
|
|
#include "Rendering/Detail/ShaderVariantUtils.h"
|
2026-03-27 11:56:23 +08:00
|
|
|
#include "Rendering/RenderMaterialUtility.h"
|
2026-03-26 20:43:17 +08:00
|
|
|
#include "Rendering/RenderSurface.h"
|
2026-04-02 19:00:48 +08:00
|
|
|
#include "Resources/BuiltinResources.h"
|
2026-03-26 20:43:17 +08:00
|
|
|
#include "Resources/Material/Material.h"
|
2026-04-02 19:00:48 +08:00
|
|
|
#include "Resources/Shader/Shader.h"
|
2026-03-26 20:43:17 +08:00
|
|
|
#include "Resources/Texture/Texture.h"
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
#include <algorithm>
|
2026-03-26 20:43:17 +08:00
|
|
|
#include <cstddef>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine {
|
|
|
|
|
namespace Rendering {
|
|
|
|
|
namespace Pipelines {
|
2026-03-30 02:22:17 +08:00
|
|
|
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
|
2026-03-26 20:43:17 +08:00
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
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<RHI::DescriptorSetLayoutBinding>& bindings) {
|
|
|
|
|
for (const RHI::DescriptorSetLayoutBinding& binding : bindings) {
|
|
|
|
|
const RHI::DescriptorType descriptorType = static_cast<RHI::DescriptorType>(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<RHI::DescriptorSetLayoutBinding>& bindings) {
|
|
|
|
|
uint32_t descriptorCount = 0;
|
|
|
|
|
for (const RHI::DescriptorSetLayoutBinding& binding : bindings) {
|
|
|
|
|
const RHI::DescriptorType descriptorType = static_cast<RHI::DescriptorType>(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<RHI::DescriptorSetLayoutBinding>& bindings,
|
|
|
|
|
uint32_t bindingNumber) {
|
|
|
|
|
for (const RHI::DescriptorSetLayoutBinding& binding : bindings) {
|
|
|
|
|
if (binding.binding == bindingNumber) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<Resources::ShaderResourceBindingDesc> BuildLegacyForwardResourceBindings() {
|
|
|
|
|
std::vector<Resources::ShaderResourceBindingDesc> 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;
|
|
|
|
|
}
|
2026-03-26 20:43:17 +08:00
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
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 &&
|
2026-04-02 19:17:22 +08:00
|
|
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, explicitPass->name, backend)) {
|
2026-04-02 19:00:48 +08:00
|
|
|
return explicitPass;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-26 20:43:17 +08:00
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) {
|
|
|
|
|
if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::ForwardLit) &&
|
2026-04-02 19:17:22 +08:00
|
|
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shaderPass.name, backend)) {
|
2026-04-02 19:00:48 +08:00
|
|
|
return &shaderPass;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-26 20:43:17 +08:00
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
const Resources::ShaderPass* defaultPass = shader.FindPass("ForwardLit");
|
|
|
|
|
if (defaultPass != nullptr &&
|
2026-04-02 19:17:22 +08:00
|
|
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, defaultPass->name, backend)) {
|
2026-04-02 19:00:48 +08:00
|
|
|
return defaultPass;
|
2026-04-01 00:41:56 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
defaultPass = shader.FindPass("Default");
|
|
|
|
|
if (defaultPass != nullptr &&
|
2026-04-02 19:17:22 +08:00
|
|
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, defaultPass->name, backend)) {
|
2026-04-02 19:00:48 +08:00
|
|
|
return defaultPass;
|
|
|
|
|
}
|
2026-04-02 04:00:58 +08:00
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
if (shader.GetPassCount() > 0 &&
|
2026-04-02 19:17:22 +08:00
|
|
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) {
|
2026-04-02 19:00:48 +08:00
|
|
|
return &shader.GetPasses()[0];
|
|
|
|
|
}
|
2026-04-02 04:00:58 +08:00
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
return nullptr;
|
2026-04-02 04:00:58 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-27 12:18:04 +08:00
|
|
|
RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
|
|
|
|
RHI::RHIType backendType,
|
|
|
|
|
RHI::RHIPipelineLayout* pipelineLayout,
|
2026-04-02 19:00:48 +08:00
|
|
|
const Resources::Shader& shader,
|
|
|
|
|
const Containers::String& passName,
|
2026-03-27 12:18:04 +08:00
|
|
|
const Resources::Material* material) {
|
2026-03-26 20:43:17 +08:00
|
|
|
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);
|
2026-03-26 22:28:11 +08:00
|
|
|
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::D24_UNorm_S8_UInt);
|
2026-03-26 20:43:17 +08:00
|
|
|
pipelineDesc.sampleCount = 1;
|
2026-03-27 12:18:04 +08:00
|
|
|
ApplyMaterialRenderState(material, pipelineDesc);
|
2026-03-26 20:43:17 +08:00
|
|
|
|
2026-03-30 02:22:17 +08:00
|
|
|
pipelineDesc.inputLayout = BuiltinForwardPipeline::BuildInputLayout();
|
2026-03-26 20:43:17 +08:00
|
|
|
|
2026-04-02 19:17:22 +08:00
|
|
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
|
2026-04-02 19:00:48 +08:00
|
|
|
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) {
|
2026-04-02 19:17:22 +08:00
|
|
|
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader);
|
2026-04-02 19:00:48 +08:00
|
|
|
}
|
|
|
|
|
if (fragmentVariant != nullptr) {
|
2026-04-02 19:17:22 +08:00
|
|
|
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader);
|
2026-03-26 20:43:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pipelineDesc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2026-03-30 02:22:17 +08:00
|
|
|
BuiltinForwardPipeline::BuiltinForwardPipeline() {
|
|
|
|
|
m_passSequence.AddPass(std::make_unique<Detail::BuiltinForwardOpaquePass>(*this));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 20:43:17 +08:00
|
|
|
BuiltinForwardPipeline::~BuiltinForwardPipeline() {
|
|
|
|
|
Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 00:56:48 +08:00
|
|
|
std::unique_ptr<RenderPipeline> BuiltinForwardPipelineAsset::CreatePipeline() const {
|
|
|
|
|
return std::make_unique<BuiltinForwardPipeline>();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 02:22:17 +08:00
|
|
|
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);
|
|
|
|
|
|
2026-04-01 00:41:56 +08:00
|
|
|
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);
|
|
|
|
|
|
2026-03-30 02:22:17 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 20:43:17 +08:00
|
|
|
bool BuiltinForwardPipeline::Initialize(const RenderContext& context) {
|
2026-03-30 02:22:17 +08:00
|
|
|
return m_passSequence.Initialize(context);
|
2026-03-26 20:43:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BuiltinForwardPipeline::Shutdown() {
|
2026-03-30 02:22:17 +08:00
|
|
|
m_passSequence.Shutdown();
|
2026-03-26 20:43:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinForwardPipeline::Render(
|
|
|
|
|
const RenderContext& context,
|
|
|
|
|
const RenderSurface& surface,
|
|
|
|
|
const RenderSceneData& sceneData) {
|
2026-03-30 02:22:17 +08:00
|
|
|
if (!Initialize(context)) {
|
2026-03-26 20:43:17 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 02:22:17 +08:00
|
|
|
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;
|
|
|
|
|
|
2026-03-26 20:43:17 +08:00
|
|
|
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());
|
|
|
|
|
|
2026-04-01 13:01:11 +08:00
|
|
|
const Math::RectInt renderArea = surface.GetRenderArea();
|
|
|
|
|
if (renderArea.width <= 0 || renderArea.height <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 20:43:17 +08:00
|
|
|
const RHI::Viewport viewport = {
|
2026-04-01 13:01:11 +08:00
|
|
|
static_cast<float>(renderArea.x),
|
|
|
|
|
static_cast<float>(renderArea.y),
|
|
|
|
|
static_cast<float>(renderArea.width),
|
|
|
|
|
static_cast<float>(renderArea.height),
|
2026-03-26 20:43:17 +08:00
|
|
|
0.0f,
|
|
|
|
|
1.0f
|
|
|
|
|
};
|
|
|
|
|
const RHI::Rect scissorRect = {
|
2026-04-01 13:01:11 +08:00
|
|
|
renderArea.x,
|
|
|
|
|
renderArea.y,
|
|
|
|
|
renderArea.x + renderArea.width,
|
|
|
|
|
renderArea.y + renderArea.height
|
2026-03-26 20:43:17 +08:00
|
|
|
};
|
2026-04-01 15:16:25 +08:00
|
|
|
const RHI::Rect clearRects[] = { scissorRect };
|
2026-03-26 20:43:17 +08:00
|
|
|
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 };
|
2026-03-27 17:46:47 +08:00
|
|
|
if (HasRenderClearFlag(sceneData.cameraData.clearFlags, RenderClearFlags::Color)) {
|
|
|
|
|
for (RHI::RHIResourceView* renderTarget : renderTargets) {
|
|
|
|
|
if (renderTarget != nullptr) {
|
2026-04-01 15:16:25 +08:00
|
|
|
commandList->ClearRenderTarget(renderTarget, clearValues, 1, clearRects);
|
2026-03-27 17:46:47 +08:00
|
|
|
}
|
2026-03-26 20:43:17 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-27 17:46:47 +08:00
|
|
|
if (surface.GetDepthAttachment() != nullptr &&
|
|
|
|
|
HasRenderClearFlag(sceneData.cameraData.clearFlags, RenderClearFlags::Depth)) {
|
2026-04-01 15:16:25 +08:00
|
|
|
commandList->ClearDepthStencil(surface.GetDepthAttachment(), 1.0f, 0, 1, clearRects);
|
2026-03-26 20:43:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
|
|
|
|
|
2026-03-27 12:18:04 +08:00
|
|
|
RHI::RHIPipelineState* currentPipelineState = nullptr;
|
2026-03-27 11:56:23 +08:00
|
|
|
for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) {
|
2026-03-27 12:18:04 +08:00
|
|
|
const Resources::Material* material = ResolveMaterial(visibleItem);
|
2026-04-02 16:26:20 +08:00
|
|
|
if (!MatchesBuiltinPass(material, BuiltinMaterialPass::ForwardLit)) {
|
2026-03-27 12:18:04 +08:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, material);
|
|
|
|
|
if (pipelineState == nullptr) {
|
2026-03-27 11:56:23 +08:00
|
|
|
continue;
|
|
|
|
|
}
|
2026-03-27 12:18:04 +08:00
|
|
|
if (pipelineState != currentPipelineState) {
|
|
|
|
|
commandList->SetPipelineState(pipelineState);
|
|
|
|
|
currentPipelineState = pipelineState;
|
|
|
|
|
}
|
2026-03-27 11:56:23 +08:00
|
|
|
|
|
|
|
|
DrawVisibleItem(context, sceneData, visibleItem);
|
2026-03-26 20:43:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
2026-04-02 19:00:48 +08:00
|
|
|
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-03-26 20:43:17 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
2026-03-27 12:18:04 +08:00
|
|
|
for (auto& pipelinePair : m_pipelineStates) {
|
|
|
|
|
if (pipelinePair.second != nullptr) {
|
|
|
|
|
pipelinePair.second->Shutdown();
|
|
|
|
|
delete pipelinePair.second;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_pipelineStates.clear();
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
for (auto& descriptorSetPair : m_dynamicDescriptorSets) {
|
|
|
|
|
DestroyOwnedDescriptorSet(descriptorSetPair.second.descriptorSet);
|
2026-03-27 13:01:17 +08:00
|
|
|
}
|
2026-04-03 11:51:01 +08:00
|
|
|
m_dynamicDescriptorSets.clear();
|
2026-03-27 13:01:17 +08:00
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
for (auto& passLayoutPair : m_passResourceLayouts) {
|
|
|
|
|
DestroyPassResourceLayout(passLayoutPair.second);
|
2026-03-27 13:01:17 +08:00
|
|
|
}
|
2026-04-03 11:51:01 +08:00
|
|
|
m_passResourceLayouts.clear();
|
2026-03-27 13:01:17 +08:00
|
|
|
|
2026-03-26 20:43:17 +08:00
|
|
|
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;
|
2026-04-02 19:00:48 +08:00
|
|
|
m_builtinForwardShader.Reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveForwardShaderPass(
|
|
|
|
|
const Resources::Material* material) const {
|
|
|
|
|
ResolvedShaderPass resolved = {};
|
2026-04-02 19:17:22 +08:00
|
|
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType);
|
2026-04-02 19:00:48 +08:00
|
|
|
|
|
|
|
|
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;
|
2026-03-26 20:43:17 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
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<Resources::ShaderResourceBindingDesc> 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<size_t>(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<uint32_t>(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<uint32_t>(RHI::DescriptorType::CBV),
|
|
|
|
|
1,
|
|
|
|
|
0
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (usesTextures) {
|
|
|
|
|
compatibilitySet.bindings.push_back({
|
|
|
|
|
0,
|
|
|
|
|
static_cast<uint32_t>(RHI::DescriptorType::SRV),
|
|
|
|
|
1,
|
|
|
|
|
0
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (usesSamplers) {
|
|
|
|
|
compatibilitySet.bindings.push_back({
|
|
|
|
|
0,
|
|
|
|
|
static_cast<uint32_t>(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<uint32_t>(setLayout.bindings.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
for (PassSetLayoutMetadata& setLayout : storedPassLayout.setLayouts) {
|
|
|
|
|
setLayout.layout.bindings = setLayout.bindings.empty() ? nullptr : setLayout.bindings.data();
|
|
|
|
|
setLayout.layout.bindingCount = static_cast<uint32_t>(setLayout.bindings.size());
|
|
|
|
|
}
|
|
|
|
|
return &storedPassLayout;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-27 12:18:04 +08:00
|
|
|
RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState(
|
|
|
|
|
const RenderContext& context,
|
|
|
|
|
const Resources::Material* material) {
|
2026-04-02 19:00:48 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass);
|
|
|
|
|
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
PipelineStateKey pipelineKey = {};
|
|
|
|
|
pipelineKey.renderState =
|
2026-03-27 12:18:04 +08:00
|
|
|
material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState();
|
2026-04-02 19:00:48 +08:00
|
|
|
pipelineKey.shader = resolvedShaderPass.shader;
|
|
|
|
|
pipelineKey.passName = resolvedShaderPass.passName;
|
2026-03-27 12:18:04 +08:00
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
const auto existing = m_pipelineStates.find(pipelineKey);
|
2026-03-27 12:18:04 +08:00
|
|
|
if (existing != m_pipelineStates.end()) {
|
|
|
|
|
return existing->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RHI::GraphicsPipelineDesc pipelineDesc =
|
2026-04-02 19:00:48 +08:00
|
|
|
CreatePipelineDesc(
|
|
|
|
|
context.backendType,
|
2026-04-03 11:51:01 +08:00
|
|
|
passLayout->pipelineLayout,
|
2026-04-02 19:00:48 +08:00
|
|
|
*resolvedShaderPass.shader,
|
|
|
|
|
resolvedShaderPass.passName,
|
|
|
|
|
material);
|
2026-03-27 12:18:04 +08:00
|
|
|
RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc);
|
|
|
|
|
if (pipelineState == nullptr || !pipelineState->IsValid()) {
|
2026-03-27 13:01:17 +08:00
|
|
|
Debug::Logger::Get().Error(
|
|
|
|
|
Debug::LogCategory::Rendering,
|
|
|
|
|
"BuiltinForwardPipeline failed to create pipeline state");
|
2026-03-27 12:18:04 +08:00
|
|
|
if (pipelineState != nullptr) {
|
|
|
|
|
pipelineState->Shutdown();
|
|
|
|
|
delete pipelineState;
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
m_pipelineStates.emplace(pipelineKey, pipelineState);
|
2026-03-27 12:18:04 +08:00
|
|
|
return pipelineState;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
bool BuiltinForwardPipeline::CreateOwnedDescriptorSet(
|
|
|
|
|
const PassSetLayoutMetadata& setLayout,
|
|
|
|
|
OwnedDescriptorSet& descriptorSet) {
|
2026-03-27 13:01:17 +08:00
|
|
|
RHI::DescriptorPoolDesc poolDesc = {};
|
2026-04-03 11:51:01 +08:00
|
|
|
poolDesc.type = setLayout.heapType;
|
|
|
|
|
poolDesc.descriptorCount = CountHeapDescriptors(setLayout.heapType, setLayout.bindings);
|
|
|
|
|
poolDesc.shaderVisible = setLayout.shaderVisible;
|
2026-03-27 13:01:17 +08:00
|
|
|
|
|
|
|
|
descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc);
|
|
|
|
|
if (descriptorSet.pool == nullptr) {
|
2026-04-03 11:51:01 +08:00
|
|
|
return false;
|
2026-03-27 13:01:17 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
descriptorSet.set = descriptorSet.pool->AllocateSet(setLayout.layout);
|
2026-03-27 13:01:17 +08:00
|
|
|
if (descriptorSet.set == nullptr) {
|
|
|
|
|
DestroyOwnedDescriptorSet(descriptorSet);
|
2026-04-03 11:51:01 +08:00
|
|
|
return false;
|
2026-03-27 13:01:17 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
return true;
|
2026-03-27 13:01:17 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
RHI::RHIDescriptorSet* BuiltinForwardPipeline::GetOrCreateStaticDescriptorSet(
|
|
|
|
|
PassResourceLayout& passLayout,
|
|
|
|
|
Core::uint32 setIndex) {
|
|
|
|
|
if (setIndex >= passLayout.setLayouts.size() ||
|
|
|
|
|
setIndex >= passLayout.staticDescriptorSets.size()) {
|
2026-03-27 13:01:17 +08:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
OwnedDescriptorSet& descriptorSet = passLayout.staticDescriptorSets[setIndex];
|
|
|
|
|
if (descriptorSet.set == nullptr) {
|
|
|
|
|
if (!CreateOwnedDescriptorSet(passLayout.setLayouts[setIndex], descriptorSet)) {
|
2026-04-02 17:13:53 +08:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
if (passLayout.setLayouts[setIndex].usesSampler) {
|
|
|
|
|
if (m_sampler == nullptr ||
|
|
|
|
|
!passLayout.linearClampSampler.IsValid() ||
|
|
|
|
|
passLayout.linearClampSampler.set != setIndex) {
|
|
|
|
|
DestroyOwnedDescriptorSet(descriptorSet);
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2026-04-02 17:13:53 +08:00
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
descriptorSet.set->UpdateSampler(passLayout.linearClampSampler.binding, m_sampler);
|
2026-04-02 17:13:53 +08:00
|
|
|
}
|
2026-03-27 13:01:17 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
return descriptorSet.set;
|
|
|
|
|
}
|
2026-04-02 17:13:53 +08:00
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
BuiltinForwardPipeline::CachedDescriptorSet* BuiltinForwardPipeline::GetOrCreateDynamicDescriptorSet(
|
|
|
|
|
const PassLayoutKey& passLayoutKey,
|
|
|
|
|
const PassResourceLayout& passLayout,
|
|
|
|
|
const PassSetLayoutMetadata& setLayout,
|
|
|
|
|
Core::uint32 setIndex,
|
|
|
|
|
Core::uint64 objectId,
|
|
|
|
|
const Resources::Material* material,
|
2026-04-03 16:49:30 +08:00
|
|
|
const MaterialConstantPayloadView& materialConstants,
|
2026-04-03 11:51:01 +08:00
|
|
|
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)) {
|
2026-04-02 17:13:53 +08:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
2026-03-27 13:01:17 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 17:13:53 +08:00
|
|
|
const Core::uint64 materialVersion = material != nullptr ? material->GetChangeVersion() : 0;
|
2026-04-03 11:51:01 +08:00
|
|
|
if ((setLayout.usesMaterial || setLayout.usesTexture) &&
|
|
|
|
|
(cachedDescriptorSet.materialVersion != materialVersion ||
|
|
|
|
|
cachedDescriptorSet.textureView != textureView)) {
|
|
|
|
|
if (setLayout.usesMaterial) {
|
|
|
|
|
if (!passLayout.material.IsValid() || passLayout.material.set != setIndex) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2026-04-03 16:49:30 +08:00
|
|
|
if (!materialConstants.IsValid()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2026-04-03 11:51:01 +08:00
|
|
|
cachedDescriptorSet.descriptorSet.set->WriteConstant(
|
|
|
|
|
passLayout.material.binding,
|
2026-04-03 16:49:30 +08:00
|
|
|
materialConstants.data,
|
|
|
|
|
materialConstants.size);
|
2026-04-03 11:51:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (setLayout.usesTexture) {
|
|
|
|
|
if (textureView == nullptr ||
|
|
|
|
|
!passLayout.baseColorTexture.IsValid() ||
|
|
|
|
|
passLayout.baseColorTexture.set != setIndex) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->Update(passLayout.baseColorTexture.binding, textureView);
|
|
|
|
|
}
|
2026-03-27 13:01:17 +08:00
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
cachedDescriptorSet.materialVersion = materialVersion;
|
|
|
|
|
cachedDescriptorSet.textureView = textureView;
|
2026-03-27 13:01:17 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
return &cachedDescriptorSet;
|
2026-03-27 13:01:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
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 = {};
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 20:43:17 +08:00
|
|
|
const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources::Material* material) const {
|
2026-04-02 17:13:53 +08:00
|
|
|
return ResolveBuiltinBaseColorTexture(material);
|
2026-03-26 20:43:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView(
|
2026-03-27 11:56:23 +08:00
|
|
|
const VisibleRenderItem& visibleItem) {
|
|
|
|
|
const Resources::Material* material = ResolveMaterial(visibleItem);
|
2026-03-26 20:43:17 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-27 11:56:23 +08:00
|
|
|
bool BuiltinForwardPipeline::DrawVisibleItem(
|
2026-03-26 20:43:17 +08:00
|
|
|
const RenderContext& context,
|
|
|
|
|
const RenderSceneData& sceneData,
|
2026-03-27 11:56:23 +08:00
|
|
|
const VisibleRenderItem& visibleItem) {
|
|
|
|
|
const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleItem.mesh);
|
2026-03-26 20:43:17 +08:00
|
|
|
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,
|
2026-04-01 00:41:56 +08:00
|
|
|
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-03-26 20:43:17 +08:00
|
|
|
};
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
const Resources::Material* material = ResolveMaterial(visibleItem);
|
|
|
|
|
const ResolvedShaderPass resolvedShaderPass = ResolveForwardShaderPass(material);
|
|
|
|
|
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
2026-03-26 20:43:17 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
PassLayoutKey passLayoutKey = {};
|
|
|
|
|
passLayoutKey.shader = resolvedShaderPass.shader;
|
|
|
|
|
passLayoutKey.passName = resolvedShaderPass.passName;
|
|
|
|
|
|
|
|
|
|
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass);
|
|
|
|
|
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
|
2026-03-27 13:01:17 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
RHI::RHIResourceView* textureView = ResolveTextureView(visibleItem);
|
|
|
|
|
if (passLayout->baseColorTexture.IsValid() && textureView == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 16:49:30 +08:00
|
|
|
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)
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-04-03 11:51:01 +08:00
|
|
|
|
|
|
|
|
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 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);
|
|
|
|
|
}
|
2026-03-26 20:43:17 +08:00
|
|
|
|
2026-03-27 11:56:23 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 20:43:17 +08:00
|
|
|
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
|