2026-04-01 17:47:49 +08:00
|
|
|
#include "Rendering/Passes/BuiltinObjectIdOutlinePass.h"
|
2026-03-29 15:12:38 +08:00
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
#include "Core/Asset/ResourceManager.h"
|
|
|
|
|
#include "Debug/Logger.h"
|
2026-04-01 17:47:49 +08:00
|
|
|
#include "Rendering/ObjectIdEncoding.h"
|
2026-04-02 20:18:39 +08:00
|
|
|
#include "Rendering/Detail/ShaderVariantUtils.h"
|
2026-04-01 17:47:49 +08:00
|
|
|
#include "RHI/RHICommandList.h"
|
|
|
|
|
#include "RHI/RHIDevice.h"
|
2026-04-02 20:18:39 +08:00
|
|
|
#include "Resources/BuiltinResources.h"
|
2026-03-29 15:12:38 +08:00
|
|
|
|
2026-04-01 17:33:07 +08:00
|
|
|
#include <algorithm>
|
2026-03-29 15:12:38 +08:00
|
|
|
|
|
|
|
|
namespace XCEngine {
|
2026-04-01 17:47:49 +08:00
|
|
|
namespace Rendering {
|
|
|
|
|
namespace Passes {
|
2026-03-29 15:12:38 +08:00
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
const Resources::ShaderPass* FindObjectIdOutlineCompatiblePass(
|
|
|
|
|
const Resources::Shader& shader,
|
|
|
|
|
Resources::ShaderBackend backend) {
|
|
|
|
|
const Resources::ShaderPass* outlinePass = shader.FindPass("ObjectIdOutline");
|
|
|
|
|
if (outlinePass != nullptr &&
|
|
|
|
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, outlinePass->name, backend)) {
|
|
|
|
|
return outlinePass;
|
|
|
|
|
}
|
2026-04-01 17:33:07 +08:00
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
const Resources::ShaderPass* editorOutlinePass = shader.FindPass("EditorObjectIdOutline");
|
|
|
|
|
if (editorOutlinePass != nullptr &&
|
|
|
|
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, editorOutlinePass->name, backend)) {
|
|
|
|
|
return editorOutlinePass;
|
2026-04-01 17:33:07 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
if (shader.GetPassCount() > 0 &&
|
|
|
|
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) {
|
|
|
|
|
return &shader.GetPasses()[0];
|
2026-04-01 17:33:07 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
return nullptr;
|
2026-03-29 15:12:38 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
|
|
|
|
RHI::RHIType backendType,
|
|
|
|
|
RHI::RHIPipelineLayout* pipelineLayout,
|
|
|
|
|
const Resources::Shader& shader,
|
|
|
|
|
const Containers::String& passName) {
|
|
|
|
|
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::Unknown);
|
|
|
|
|
pipelineDesc.sampleCount = 1;
|
2026-03-29 15:12:38 +08:00
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
|
|
|
|
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
|
|
|
|
pipelineDesc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
|
|
|
|
pipelineDesc.rasterizerState.depthClipEnable = true;
|
2026-04-01 17:33:07 +08:00
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
pipelineDesc.blendState.blendEnable = true;
|
|
|
|
|
pipelineDesc.blendState.srcBlend = static_cast<uint32_t>(RHI::BlendFactor::SrcAlpha);
|
|
|
|
|
pipelineDesc.blendState.dstBlend = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
|
|
|
|
pipelineDesc.blendState.srcBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::One);
|
|
|
|
|
pipelineDesc.blendState.dstBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
|
|
|
|
pipelineDesc.blendState.blendOp = static_cast<uint32_t>(RHI::BlendOp::Add);
|
|
|
|
|
pipelineDesc.blendState.blendOpAlpha = static_cast<uint32_t>(RHI::BlendOp::Add);
|
|
|
|
|
pipelineDesc.blendState.colorWriteMask = static_cast<uint8_t>(RHI::ColorWriteMask::All);
|
2026-03-29 15:12:38 +08:00
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
pipelineDesc.depthStencilState.depthTestEnable = false;
|
|
|
|
|
pipelineDesc.depthStencilState.depthWriteEnable = false;
|
|
|
|
|
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
2026-03-29 15:12:38 +08:00
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
|
|
|
|
|
if (const Resources::ShaderStageVariant* vertexVariant =
|
|
|
|
|
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
|
|
|
|
|
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader);
|
|
|
|
|
}
|
|
|
|
|
if (const Resources::ShaderStageVariant* fragmentVariant =
|
|
|
|
|
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
|
|
|
|
|
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader);
|
2026-03-29 15:12:38 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
return pipelineDesc;
|
2026-03-29 15:12:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2026-04-01 17:47:49 +08:00
|
|
|
void BuiltinObjectIdOutlinePass::Shutdown() {
|
2026-03-29 15:12:38 +08:00
|
|
|
DestroyResources();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 17:47:49 +08:00
|
|
|
bool BuiltinObjectIdOutlinePass::Render(
|
|
|
|
|
const RenderContext& renderContext,
|
|
|
|
|
const RenderSurface& surface,
|
2026-04-01 17:33:07 +08:00
|
|
|
RHI::RHIResourceView* objectIdTextureView,
|
|
|
|
|
const std::vector<uint64_t>& selectedObjectIds,
|
2026-04-01 17:47:49 +08:00
|
|
|
const ObjectIdOutlineStyle& style) {
|
2026-03-29 15:12:38 +08:00
|
|
|
if (!renderContext.IsValid() ||
|
|
|
|
|
renderContext.backendType != RHI::RHIType::D3D12 ||
|
2026-04-01 17:33:07 +08:00
|
|
|
objectIdTextureView == nullptr ||
|
|
|
|
|
selectedObjectIds.empty()) {
|
2026-03-29 15:12:38 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!EnsureInitialized(renderContext)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
|
|
|
|
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OutlineConstants constants = {};
|
|
|
|
|
constants.viewportSizeAndTexelSize = Math::Vector4(
|
|
|
|
|
static_cast<float>(surface.GetWidth()),
|
|
|
|
|
static_cast<float>(surface.GetHeight()),
|
|
|
|
|
surface.GetWidth() > 0 ? 1.0f / static_cast<float>(surface.GetWidth()) : 0.0f,
|
|
|
|
|
surface.GetHeight() > 0 ? 1.0f / static_cast<float>(surface.GetHeight()) : 0.0f);
|
2026-04-01 17:47:49 +08:00
|
|
|
constants.outlineColor = Math::Vector4(
|
|
|
|
|
style.outlineColor.r,
|
|
|
|
|
style.outlineColor.g,
|
|
|
|
|
style.outlineColor.b,
|
|
|
|
|
style.outlineColor.a);
|
2026-04-01 17:33:07 +08:00
|
|
|
|
|
|
|
|
const uint32_t selectedCount = (std::min)(
|
|
|
|
|
static_cast<uint32_t>(selectedObjectIds.size()),
|
|
|
|
|
kMaxSelectedObjectCount);
|
|
|
|
|
constants.selectedInfo = Math::Vector4(
|
|
|
|
|
static_cast<float>(selectedCount),
|
2026-04-01 17:47:49 +08:00
|
|
|
style.debugSelectionMask ? 1.0f : 0.0f,
|
|
|
|
|
style.outlineWidthPixels,
|
2026-04-01 17:33:07 +08:00
|
|
|
0.0f);
|
|
|
|
|
for (uint32_t index = 0; index < selectedCount; ++index) {
|
|
|
|
|
constants.selectedObjectColors[index] =
|
2026-04-01 17:47:49 +08:00
|
|
|
EncodeObjectIdToColor(selectedObjectIds[index]);
|
2026-04-01 17:33:07 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-29 15:12:38 +08:00
|
|
|
m_constantSet->WriteConstant(0, &constants, sizeof(constants));
|
2026-04-01 17:33:07 +08:00
|
|
|
m_textureSet->Update(0, objectIdTextureView);
|
2026-03-29 15:12:38 +08:00
|
|
|
|
|
|
|
|
RHI::RHICommandList* commandList = renderContext.commandList;
|
|
|
|
|
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
|
|
|
|
commandList->SetRenderTargets(1, &renderTarget, nullptr);
|
|
|
|
|
|
|
|
|
|
const RHI::Viewport viewport = {
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
static_cast<float>(surface.GetWidth()),
|
|
|
|
|
static_cast<float>(surface.GetHeight()),
|
|
|
|
|
0.0f,
|
|
|
|
|
1.0f
|
|
|
|
|
};
|
|
|
|
|
const RHI::Rect scissorRect = {
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
static_cast<int32_t>(surface.GetWidth()),
|
|
|
|
|
static_cast<int32_t>(surface.GetHeight())
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
commandList->SetViewport(viewport);
|
|
|
|
|
commandList->SetScissorRect(scissorRect);
|
|
|
|
|
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
|
|
|
|
commandList->SetPipelineState(m_pipelineState);
|
|
|
|
|
|
2026-04-01 17:33:07 +08:00
|
|
|
RHI::RHIDescriptorSet* descriptorSets[] = { m_constantSet, m_textureSet };
|
|
|
|
|
commandList->SetGraphicsDescriptorSets(0, 2, descriptorSets, m_pipelineLayout);
|
2026-03-29 15:12:38 +08:00
|
|
|
commandList->Draw(3, 1, 0, 0);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 17:47:49 +08:00
|
|
|
bool BuiltinObjectIdOutlinePass::EnsureInitialized(const RenderContext& renderContext) {
|
2026-03-29 15:12:38 +08:00
|
|
|
if (m_pipelineLayout != nullptr &&
|
|
|
|
|
m_pipelineState != nullptr &&
|
|
|
|
|
m_constantPool != nullptr &&
|
|
|
|
|
m_constantSet != nullptr &&
|
|
|
|
|
m_texturePool != nullptr &&
|
|
|
|
|
m_textureSet != nullptr &&
|
|
|
|
|
m_device == renderContext.device &&
|
|
|
|
|
m_backendType == renderContext.backendType) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return CreateResources(renderContext);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 17:47:49 +08:00
|
|
|
bool BuiltinObjectIdOutlinePass::CreateResources(const RenderContext& renderContext) {
|
2026-03-29 15:12:38 +08:00
|
|
|
if (!renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_device = renderContext.device;
|
|
|
|
|
m_backendType = renderContext.backendType;
|
2026-04-02 20:18:39 +08:00
|
|
|
m_builtinObjectIdOutlineShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
|
|
|
|
|
Resources::GetBuiltinObjectIdOutlineShaderPath());
|
|
|
|
|
if (!m_builtinObjectIdOutlineShader.IsValid()) {
|
|
|
|
|
Debug::Logger::Get().Error(
|
|
|
|
|
Debug::LogCategory::Rendering,
|
|
|
|
|
"BuiltinObjectIdOutlinePass failed to load builtin object-id-outline shader resource");
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType);
|
|
|
|
|
const Resources::ShaderPass* outlinePass =
|
|
|
|
|
FindObjectIdOutlineCompatiblePass(*m_builtinObjectIdOutlineShader.Get(), backend);
|
|
|
|
|
if (outlinePass == nullptr) {
|
|
|
|
|
Debug::Logger::Get().Error(
|
|
|
|
|
Debug::LogCategory::Rendering,
|
|
|
|
|
"BuiltinObjectIdOutlinePass could not resolve a valid ObjectIdOutline shader pass");
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-29 15:12:38 +08:00
|
|
|
|
2026-04-01 17:33:07 +08:00
|
|
|
RHI::DescriptorSetLayoutBinding setBindings[2] = {};
|
2026-03-29 15:12:38 +08:00
|
|
|
setBindings[0].binding = 0;
|
|
|
|
|
setBindings[0].type = static_cast<uint32_t>(RHI::DescriptorType::CBV);
|
|
|
|
|
setBindings[0].count = 1;
|
|
|
|
|
setBindings[1].binding = 0;
|
|
|
|
|
setBindings[1].type = static_cast<uint32_t>(RHI::DescriptorType::SRV);
|
|
|
|
|
setBindings[1].count = 1;
|
|
|
|
|
|
|
|
|
|
RHI::DescriptorSetLayoutDesc constantLayout = {};
|
|
|
|
|
constantLayout.bindings = &setBindings[0];
|
|
|
|
|
constantLayout.bindingCount = 1;
|
|
|
|
|
|
|
|
|
|
RHI::DescriptorSetLayoutDesc textureLayout = {};
|
|
|
|
|
textureLayout.bindings = &setBindings[1];
|
|
|
|
|
textureLayout.bindingCount = 1;
|
|
|
|
|
|
2026-04-01 17:33:07 +08:00
|
|
|
RHI::DescriptorSetLayoutDesc setLayouts[2] = {};
|
2026-03-29 15:12:38 +08:00
|
|
|
setLayouts[0] = constantLayout;
|
|
|
|
|
setLayouts[1] = textureLayout;
|
|
|
|
|
|
|
|
|
|
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
|
|
|
|
pipelineLayoutDesc.setLayouts = setLayouts;
|
2026-04-01 17:33:07 +08:00
|
|
|
pipelineLayoutDesc.setLayoutCount = 2;
|
2026-03-29 15:12:38 +08:00
|
|
|
m_pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
|
|
|
|
if (m_pipelineLayout == nullptr) {
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::DescriptorPoolDesc constantPoolDesc = {};
|
|
|
|
|
constantPoolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV;
|
|
|
|
|
constantPoolDesc.descriptorCount = 1;
|
|
|
|
|
constantPoolDesc.shaderVisible = false;
|
|
|
|
|
m_constantPool = m_device->CreateDescriptorPool(constantPoolDesc);
|
|
|
|
|
if (m_constantPool == nullptr) {
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_constantSet = m_constantPool->AllocateSet(constantLayout);
|
|
|
|
|
if (m_constantSet == nullptr) {
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::DescriptorPoolDesc texturePoolDesc = {};
|
|
|
|
|
texturePoolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV;
|
|
|
|
|
texturePoolDesc.descriptorCount = 1;
|
|
|
|
|
texturePoolDesc.shaderVisible = true;
|
|
|
|
|
m_texturePool = m_device->CreateDescriptorPool(texturePoolDesc);
|
|
|
|
|
if (m_texturePool == nullptr) {
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_textureSet = m_texturePool->AllocateSet(textureLayout);
|
|
|
|
|
if (m_textureSet == nullptr) {
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
m_pipelineState = m_device->CreatePipelineState(
|
|
|
|
|
CreatePipelineDesc(
|
|
|
|
|
m_backendType,
|
|
|
|
|
m_pipelineLayout,
|
|
|
|
|
*m_builtinObjectIdOutlineShader.Get(),
|
|
|
|
|
outlinePass->name));
|
2026-03-29 15:12:38 +08:00
|
|
|
if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) {
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 17:47:49 +08:00
|
|
|
void BuiltinObjectIdOutlinePass::DestroyResources() {
|
2026-03-29 15:12:38 +08:00
|
|
|
if (m_pipelineState != nullptr) {
|
|
|
|
|
m_pipelineState->Shutdown();
|
|
|
|
|
delete m_pipelineState;
|
|
|
|
|
m_pipelineState = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_textureSet != nullptr) {
|
|
|
|
|
m_textureSet->Shutdown();
|
|
|
|
|
delete m_textureSet;
|
|
|
|
|
m_textureSet = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_texturePool != nullptr) {
|
|
|
|
|
m_texturePool->Shutdown();
|
|
|
|
|
delete m_texturePool;
|
|
|
|
|
m_texturePool = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_constantSet != nullptr) {
|
|
|
|
|
m_constantSet->Shutdown();
|
|
|
|
|
delete m_constantSet;
|
|
|
|
|
m_constantSet = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_constantPool != nullptr) {
|
|
|
|
|
m_constantPool->Shutdown();
|
|
|
|
|
delete m_constantPool;
|
|
|
|
|
m_constantPool = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_pipelineLayout != nullptr) {
|
|
|
|
|
m_pipelineLayout->Shutdown();
|
|
|
|
|
delete m_pipelineLayout;
|
|
|
|
|
m_pipelineLayout = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_device = nullptr;
|
|
|
|
|
m_backendType = RHI::RHIType::D3D12;
|
2026-04-02 20:18:39 +08:00
|
|
|
m_builtinObjectIdOutlineShader.Reset();
|
2026-03-29 15:12:38 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-01 17:47:49 +08:00
|
|
|
} // namespace Passes
|
|
|
|
|
} // namespace Rendering
|
2026-03-29 15:12:38 +08:00
|
|
|
} // namespace XCEngine
|