2026-04-09 01:11:59 +08:00
|
|
|
#include "Rendering/Passes/BuiltinVolumetricPass.h"
|
|
|
|
|
|
|
|
|
|
#include "Components/GameObject.h"
|
|
|
|
|
#include "Core/Asset/ResourceManager.h"
|
|
|
|
|
#include "Debug/Logger.h"
|
|
|
|
|
#include "RHI/RHICommandList.h"
|
|
|
|
|
#include "RHI/RHIDevice.h"
|
|
|
|
|
#include "Rendering/Builtin/BuiltinPassLayoutUtils.h"
|
|
|
|
|
#include "Rendering/Detail/ShaderVariantUtils.h"
|
|
|
|
|
#include "Rendering/FrameData/RenderSceneData.h"
|
|
|
|
|
#include "Rendering/FrameData/VisibleVolumeItem.h"
|
|
|
|
|
#include "Rendering/RenderSurface.h"
|
|
|
|
|
#include "Resources/BuiltinResources.h"
|
|
|
|
|
#include "Resources/Material/Material.h"
|
|
|
|
|
#include "Resources/Shader/Shader.h"
|
|
|
|
|
#include "Resources/Volume/VolumeField.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine {
|
|
|
|
|
namespace Rendering {
|
|
|
|
|
namespace Passes {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
bool IsDepthFormat(RHI::Format format) {
|
|
|
|
|
return format == RHI::Format::D24_UNorm_S8_UInt ||
|
|
|
|
|
format == RHI::Format::D32_Float;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Resources::ShaderKeywordSet ResolvePassKeywordSet(
|
|
|
|
|
const RenderSceneData& sceneData,
|
|
|
|
|
const Resources::Material* material) {
|
|
|
|
|
return Resources::CombineShaderKeywordSets(
|
|
|
|
|
sceneData.globalShaderKeywords,
|
|
|
|
|
material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Resources::ShaderPass* FindCompatibleVolumePass(
|
|
|
|
|
const Resources::Shader& shader,
|
|
|
|
|
const RenderSceneData& sceneData,
|
|
|
|
|
const Resources::Material* material,
|
|
|
|
|
Resources::ShaderBackend backend) {
|
|
|
|
|
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
|
|
|
|
|
for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) {
|
|
|
|
|
if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::Volumetric) &&
|
|
|
|
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(
|
|
|
|
|
shader,
|
|
|
|
|
shaderPass.name,
|
|
|
|
|
backend,
|
|
|
|
|
keywordSet)) {
|
|
|
|
|
return &shaderPass;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
|
|
|
|
RHI::RHIType backendType,
|
|
|
|
|
RHI::RHIPipelineLayout* pipelineLayout,
|
|
|
|
|
const Resources::Shader& shader,
|
|
|
|
|
const Resources::ShaderPass& shaderPass,
|
|
|
|
|
const Containers::String& passName,
|
|
|
|
|
const Resources::ShaderKeywordSet& keywordSet,
|
|
|
|
|
const Resources::Material* material,
|
|
|
|
|
RHI::Format renderTargetFormat,
|
|
|
|
|
RHI::Format depthStencilFormat) {
|
|
|
|
|
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>(renderTargetFormat);
|
|
|
|
|
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(depthStencilFormat);
|
|
|
|
|
pipelineDesc.sampleCount = 1;
|
|
|
|
|
pipelineDesc.inputLayout = BuiltinVolumetricPass::BuildInputLayout();
|
|
|
|
|
ApplyResolvedRenderState(&shaderPass, material, pipelineDesc);
|
|
|
|
|
|
|
|
|
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
|
|
|
|
|
if (const Resources::ShaderStageVariant* vertexVariant =
|
|
|
|
|
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet)) {
|
|
|
|
|
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
|
|
|
|
shader.GetPath(),
|
|
|
|
|
shaderPass,
|
|
|
|
|
backend,
|
|
|
|
|
*vertexVariant,
|
|
|
|
|
pipelineDesc.vertexShader);
|
|
|
|
|
}
|
|
|
|
|
if (const Resources::ShaderStageVariant* fragmentVariant =
|
|
|
|
|
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend, keywordSet)) {
|
|
|
|
|
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
|
|
|
|
shader.GetPath(),
|
|
|
|
|
shaderPass,
|
|
|
|
|
backend,
|
|
|
|
|
*fragmentVariant,
|
|
|
|
|
pipelineDesc.fragmentShader);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pipelineDesc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Math::Bounds ResolveVolumeBounds(const Resources::VolumeField* volumeField) {
|
|
|
|
|
if (volumeField == nullptr) {
|
|
|
|
|
return Math::Bounds(Math::Vector3::Zero(), Math::Vector3::One());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Resources::VolumeIndexBounds& indexBounds = volumeField->GetIndexBounds();
|
|
|
|
|
const Math::Vector3 indexMin(
|
|
|
|
|
static_cast<float>(indexBounds.minX),
|
|
|
|
|
static_cast<float>(indexBounds.minY),
|
|
|
|
|
static_cast<float>(indexBounds.minZ));
|
|
|
|
|
const Math::Vector3 indexMax(
|
|
|
|
|
static_cast<float>(indexBounds.maxX),
|
|
|
|
|
static_cast<float>(indexBounds.maxY),
|
|
|
|
|
static_cast<float>(indexBounds.maxZ));
|
|
|
|
|
const Math::Vector3 indexSize = indexMax - indexMin;
|
|
|
|
|
if (indexSize.SqrMagnitude() > Math::EPSILON) {
|
|
|
|
|
Math::Bounds bounds;
|
|
|
|
|
bounds.SetMinMax(indexMin, indexMax);
|
|
|
|
|
return bounds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Math::Bounds bounds = volumeField->GetBounds();
|
|
|
|
|
const Math::Vector3 size = bounds.extents * 2.0f;
|
|
|
|
|
if (size.SqrMagnitude() <= Math::EPSILON) {
|
|
|
|
|
return Math::Bounds(Math::Vector3::Zero(), Math::Vector3::One());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bounds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
BuiltinVolumetricPass::~BuiltinVolumetricPass() {
|
|
|
|
|
Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* BuiltinVolumetricPass::GetName() const {
|
|
|
|
|
return "BuiltinVolumetricPass";
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 05:22:03 +08:00
|
|
|
BuiltinVolumetricPass::LightingConstants BuiltinVolumetricPass::BuildLightingConstants(
|
|
|
|
|
const RenderLightingData& lightingData) {
|
|
|
|
|
LightingConstants lightingConstants = {};
|
|
|
|
|
if (!lightingData.HasMainDirectionalLight()) {
|
|
|
|
|
return lightingConstants;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lightingConstants.mainLightDirectionAndIntensity = Math::Vector4(
|
|
|
|
|
lightingData.mainDirectionalLight.direction.x,
|
|
|
|
|
lightingData.mainDirectionalLight.direction.y,
|
|
|
|
|
lightingData.mainDirectionalLight.direction.z,
|
|
|
|
|
lightingData.mainDirectionalLight.intensity);
|
|
|
|
|
lightingConstants.mainLightColorAndFlags = Math::Vector4(
|
|
|
|
|
lightingData.mainDirectionalLight.color.r,
|
|
|
|
|
lightingData.mainDirectionalLight.color.g,
|
|
|
|
|
lightingData.mainDirectionalLight.color.b,
|
|
|
|
|
1.0f);
|
|
|
|
|
return lightingConstants;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 01:11:59 +08:00
|
|
|
RHI::InputLayoutDesc BuiltinVolumetricPass::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 = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, position));
|
|
|
|
|
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 BuiltinVolumetricPass::Initialize(const RenderContext& context) {
|
|
|
|
|
return EnsureInitialized(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinVolumetricPass::Execute(const RenderPassContext& context) {
|
|
|
|
|
if (!context.renderContext.IsValid()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.sceneData.visibleVolumes.empty()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::vector<RHI::RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
|
|
|
|
if (colorAttachments.empty() || colorAttachments[0] == nullptr || context.surface.GetDepthAttachment() == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Math::RectInt renderArea = context.surface.GetRenderArea();
|
|
|
|
|
if (renderArea.width <= 0 || renderArea.height <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!EnsureInitialized(context.renderContext)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHICommandList* commandList = context.renderContext.commandList;
|
|
|
|
|
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
|
|
|
|
commandList->SetRenderTargets(1, &renderTarget, context.surface.GetDepthAttachment());
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
};
|
|
|
|
|
commandList->SetViewport(viewport);
|
|
|
|
|
commandList->SetScissorRect(scissorRect);
|
|
|
|
|
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
|
|
|
|
|
|
|
|
|
for (const VisibleVolumeItem& visibleVolume : context.sceneData.visibleVolumes) {
|
|
|
|
|
DrawVisibleVolume(context.renderContext, context.surface, context.sceneData, visibleVolume);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BuiltinVolumetricPass::Shutdown() {
|
|
|
|
|
DestroyResources();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinVolumetricPass::EnsureInitialized(const RenderContext& context) {
|
|
|
|
|
if (!context.IsValid()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_device == context.device &&
|
|
|
|
|
m_backendType == context.backendType &&
|
|
|
|
|
m_builtinCubeMesh.IsValid()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return CreateResources(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinVolumetricPass::CreateResources(const RenderContext& context) {
|
|
|
|
|
m_device = context.device;
|
|
|
|
|
m_backendType = context.backendType;
|
|
|
|
|
m_builtinCubeMesh = Resources::ResourceManager::Get().Load<Resources::Mesh>(
|
|
|
|
|
Resources::GetBuiltinPrimitiveMeshPath(Resources::BuiltinPrimitiveType::Cube));
|
|
|
|
|
if (!m_builtinCubeMesh.IsValid()) {
|
|
|
|
|
Debug::Logger::Get().Error(
|
|
|
|
|
Debug::LogCategory::Rendering,
|
|
|
|
|
"BuiltinVolumetricPass failed to load builtin cube mesh resource");
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BuiltinVolumetricPass::DestroyResources() {
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
m_builtinCubeMesh.Reset();
|
|
|
|
|
m_device = nullptr;
|
|
|
|
|
m_backendType = RHI::RHIType::D3D12;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BuiltinVolumetricPass::ResolvedShaderPass BuiltinVolumetricPass::ResolveVolumeShaderPass(
|
|
|
|
|
const RenderSceneData& sceneData,
|
|
|
|
|
const Resources::Material* material) const {
|
|
|
|
|
ResolvedShaderPass resolved = {};
|
|
|
|
|
if (material == nullptr || material->GetShader() == nullptr) {
|
|
|
|
|
return resolved;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Resources::Shader* shader = material->GetShader();
|
|
|
|
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType);
|
|
|
|
|
if (const Resources::ShaderPass* shaderPass =
|
|
|
|
|
FindCompatibleVolumePass(*shader, sceneData, material, backend)) {
|
|
|
|
|
resolved.shader = shader;
|
|
|
|
|
resolved.pass = shaderPass;
|
|
|
|
|
resolved.passName = shaderPass->name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resolved;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BuiltinVolumetricPass::PassResourceLayout* BuiltinVolumetricPass::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;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan bindingPlan = {};
|
|
|
|
|
Containers::String bindingPlanError;
|
|
|
|
|
if (!TryBuildBuiltinPassResourceBindingPlan(*resolvedShaderPass.pass, bindingPlan, &bindingPlanError)) {
|
|
|
|
|
const Containers::String contextualError =
|
|
|
|
|
Containers::String("BuiltinVolumetricPass failed to resolve pass resource bindings for shader='") +
|
|
|
|
|
resolvedShaderPass.shader->GetPath() +
|
|
|
|
|
"', pass='" + resolvedShaderPass.passName +
|
|
|
|
|
"': " + bindingPlanError +
|
|
|
|
|
". Bindings: " + DescribeShaderResourceBindings(resolvedShaderPass.pass->resources);
|
|
|
|
|
return failLayout(contextualError.CStr());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!bindingPlan.perObject.IsValid()) {
|
|
|
|
|
return failLayout("BuiltinVolumetricPass requires a PerObject resource binding");
|
|
|
|
|
}
|
|
|
|
|
if (!bindingPlan.volumeField.IsValid()) {
|
|
|
|
|
return failLayout("BuiltinVolumetricPass requires a VolumeField structured-buffer binding");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Containers::String setLayoutError;
|
|
|
|
|
if (!TryBuildBuiltinPassSetLayouts(bindingPlan, passLayout.setLayouts, &setLayoutError)) {
|
|
|
|
|
return failLayout(setLayoutError.CStr());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
passLayout.firstDescriptorSet = bindingPlan.firstDescriptorSet;
|
|
|
|
|
passLayout.descriptorSetCount = bindingPlan.descriptorSetCount;
|
|
|
|
|
passLayout.perObject = bindingPlan.perObject;
|
2026-04-09 05:22:03 +08:00
|
|
|
passLayout.lighting = bindingPlan.lighting;
|
2026-04-09 01:11:59 +08:00
|
|
|
passLayout.material = bindingPlan.material;
|
|
|
|
|
passLayout.volumeField = bindingPlan.volumeField;
|
|
|
|
|
|
|
|
|
|
std::vector<RHI::DescriptorSetLayoutDesc> nativeSetLayouts(passLayout.setLayouts.size());
|
|
|
|
|
for (size_t setIndex = 0; setIndex < passLayout.setLayouts.size(); ++setIndex) {
|
|
|
|
|
nativeSetLayouts[setIndex] = passLayout.setLayouts[setIndex].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("BuiltinVolumetricPass failed to create 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* BuiltinVolumetricPass::GetOrCreatePipelineState(
|
|
|
|
|
const RenderContext& context,
|
|
|
|
|
const RenderSurface& surface,
|
|
|
|
|
const RenderSceneData& sceneData,
|
|
|
|
|
const Resources::Material* material) {
|
|
|
|
|
const ResolvedShaderPass resolvedShaderPass = ResolveVolumeShaderPass(sceneData, material);
|
|
|
|
|
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass);
|
|
|
|
|
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
|
|
|
|
|
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
|
|
|
|
const RHI::Format renderTargetFormat =
|
|
|
|
|
(!colorAttachments.empty() && colorAttachments[0] != nullptr)
|
|
|
|
|
? colorAttachments[0]->GetFormat()
|
|
|
|
|
: RHI::Format::Unknown;
|
|
|
|
|
const RHI::Format depthStencilFormat =
|
|
|
|
|
surface.GetDepthAttachment() != nullptr
|
|
|
|
|
? surface.GetDepthAttachment()->GetFormat()
|
|
|
|
|
: RHI::Format::Unknown;
|
|
|
|
|
|
|
|
|
|
PipelineStateKey pipelineKey = {};
|
|
|
|
|
pipelineKey.renderState =
|
|
|
|
|
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material));
|
|
|
|
|
pipelineKey.shader = resolvedShaderPass.shader;
|
|
|
|
|
pipelineKey.passName = resolvedShaderPass.passName;
|
|
|
|
|
pipelineKey.keywordSignature = ::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(keywordSet);
|
|
|
|
|
pipelineKey.renderTargetCount = renderTargetFormat != RHI::Format::Unknown ? 1u : 0u;
|
|
|
|
|
pipelineKey.renderTargetFormat = static_cast<uint32_t>(renderTargetFormat);
|
|
|
|
|
pipelineKey.depthStencilFormat = static_cast<uint32_t>(depthStencilFormat);
|
|
|
|
|
|
|
|
|
|
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.pass,
|
|
|
|
|
resolvedShaderPass.passName,
|
|
|
|
|
keywordSet,
|
|
|
|
|
material,
|
|
|
|
|
renderTargetFormat,
|
|
|
|
|
depthStencilFormat);
|
|
|
|
|
RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc);
|
|
|
|
|
if (pipelineState == nullptr || !pipelineState->IsValid()) {
|
|
|
|
|
if (pipelineState != nullptr) {
|
|
|
|
|
pipelineState->Shutdown();
|
|
|
|
|
delete pipelineState;
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_pipelineStates.emplace(pipelineKey, pipelineState);
|
|
|
|
|
return pipelineState;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinVolumetricPass::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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BuiltinVolumetricPass::CachedDescriptorSet* BuiltinVolumetricPass::GetOrCreateDynamicDescriptorSet(
|
|
|
|
|
const PassLayoutKey& passLayoutKey,
|
|
|
|
|
const PassResourceLayout& passLayout,
|
|
|
|
|
const BuiltinPassSetLayoutMetadata& setLayout,
|
|
|
|
|
Core::uint32 setIndex,
|
|
|
|
|
Core::uint64 objectId,
|
2026-04-09 05:22:03 +08:00
|
|
|
const Resources::Material* material,
|
|
|
|
|
const Resources::VolumeField* volumeField,
|
|
|
|
|
const MaterialConstantPayloadView& materialConstants,
|
|
|
|
|
const LightingConstants& lightingConstants,
|
|
|
|
|
RHI::RHIResourceView* volumeFieldView) {
|
2026-04-09 01:11:59 +08:00
|
|
|
DynamicDescriptorSetKey key = {};
|
|
|
|
|
key.passLayout = passLayoutKey;
|
|
|
|
|
key.setIndex = setIndex;
|
|
|
|
|
key.objectId = objectId;
|
|
|
|
|
key.material = material;
|
|
|
|
|
key.volumeField = volumeField;
|
|
|
|
|
|
|
|
|
|
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() : 0u;
|
|
|
|
|
if (setLayout.usesMaterial) {
|
|
|
|
|
if (!passLayout.material.IsValid() || passLayout.material.set != setIndex || !materialConstants.IsValid()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cachedDescriptorSet.materialVersion != materialVersion) {
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->WriteConstant(
|
|
|
|
|
passLayout.material.binding,
|
|
|
|
|
materialConstants.data,
|
|
|
|
|
materialConstants.size);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 05:22:03 +08:00
|
|
|
if (setLayout.usesLighting) {
|
|
|
|
|
if (!passLayout.lighting.IsValid() || passLayout.lighting.set != setIndex) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->WriteConstant(
|
|
|
|
|
passLayout.lighting.binding,
|
|
|
|
|
&lightingConstants,
|
|
|
|
|
sizeof(lightingConstants));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 01:11:59 +08:00
|
|
|
if (setLayout.usesVolumeField) {
|
|
|
|
|
if (volumeFieldView == nullptr ||
|
|
|
|
|
!passLayout.volumeField.IsValid() ||
|
|
|
|
|
passLayout.volumeField.set != setIndex) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cachedDescriptorSet.volumeFieldView != volumeFieldView) {
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->Update(
|
|
|
|
|
passLayout.volumeField.binding,
|
|
|
|
|
volumeFieldView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cachedDescriptorSet.materialVersion = materialVersion;
|
|
|
|
|
cachedDescriptorSet.volumeFieldView = volumeFieldView;
|
|
|
|
|
return &cachedDescriptorSet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BuiltinVolumetricPass::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 BuiltinVolumetricPass::DestroyPassResourceLayout(PassResourceLayout& passLayout) {
|
|
|
|
|
if (passLayout.pipelineLayout != nullptr) {
|
|
|
|
|
passLayout.pipelineLayout->Shutdown();
|
|
|
|
|
delete passLayout.pipelineLayout;
|
|
|
|
|
passLayout.pipelineLayout = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
passLayout.setLayouts.clear();
|
|
|
|
|
passLayout.firstDescriptorSet = 0;
|
|
|
|
|
passLayout.descriptorSetCount = 0;
|
|
|
|
|
passLayout.perObject = {};
|
2026-04-09 05:22:03 +08:00
|
|
|
passLayout.lighting = {};
|
2026-04-09 01:11:59 +08:00
|
|
|
passLayout.material = {};
|
|
|
|
|
passLayout.volumeField = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinVolumetricPass::DrawVisibleVolume(
|
|
|
|
|
const RenderContext& context,
|
|
|
|
|
const RenderSurface& surface,
|
|
|
|
|
const RenderSceneData& sceneData,
|
|
|
|
|
const VisibleVolumeItem& visibleVolume) {
|
|
|
|
|
if (m_builtinCubeMesh.Get() == nullptr ||
|
|
|
|
|
visibleVolume.gameObject == nullptr ||
|
|
|
|
|
visibleVolume.volumeField == nullptr ||
|
|
|
|
|
visibleVolume.material == nullptr ||
|
|
|
|
|
visibleVolume.volumeField->GetStorageKind() != Resources::VolumeStorageKind::NanoVDB) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RenderResourceCache::CachedMesh* cachedMesh =
|
|
|
|
|
m_resourceCache.GetOrCreateMesh(m_device, m_builtinCubeMesh.Get());
|
|
|
|
|
const RenderResourceCache::CachedVolumeField* cachedVolume =
|
|
|
|
|
m_resourceCache.GetOrCreateVolumeField(m_device, visibleVolume.volumeField);
|
|
|
|
|
if (cachedMesh == nullptr ||
|
|
|
|
|
cachedMesh->vertexBufferView == nullptr ||
|
|
|
|
|
cachedVolume == nullptr ||
|
|
|
|
|
cachedVolume->shaderResourceView == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Resources::Material* material = visibleVolume.material;
|
|
|
|
|
const ResolvedShaderPass resolvedShaderPass = ResolveVolumeShaderPass(sceneData, 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);
|
|
|
|
|
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material);
|
|
|
|
|
if (passLayout == nullptr || pipelineState == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Resources::MaterialRenderState effectiveRenderState =
|
|
|
|
|
ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
|
|
|
|
|
const MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material);
|
2026-04-09 05:22:03 +08:00
|
|
|
const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting);
|
2026-04-09 01:11:59 +08:00
|
|
|
if (passLayout->material.IsValid() && !materialConstants.IsValid()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHICommandList* commandList = context.commandList;
|
|
|
|
|
commandList->SetPipelineState(pipelineState);
|
|
|
|
|
|
|
|
|
|
RHI::RHIResourceView* vertexBuffers[] = { cachedMesh->vertexBufferView };
|
|
|
|
|
const uint64_t offsets[] = { 0u };
|
|
|
|
|
const uint32_t strides[] = { cachedMesh->vertexStride };
|
|
|
|
|
commandList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides);
|
|
|
|
|
if (cachedMesh->indexBufferView != nullptr) {
|
|
|
|
|
commandList->SetIndexBuffer(cachedMesh->indexBufferView, 0u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Math::Bounds volumeBounds = ResolveVolumeBounds(visibleVolume.volumeField);
|
|
|
|
|
const PerObjectConstants perObjectConstants = {
|
|
|
|
|
sceneData.cameraData.projection,
|
|
|
|
|
sceneData.cameraData.view,
|
|
|
|
|
visibleVolume.localToWorld.Transpose(),
|
|
|
|
|
visibleVolume.localToWorld.Inverse().Transpose(),
|
|
|
|
|
Math::Vector4(sceneData.cameraData.worldPosition, 1.0f),
|
|
|
|
|
Math::Vector4(volumeBounds.GetMin(), 0.0f),
|
|
|
|
|
Math::Vector4(volumeBounds.GetMax(), 0.0f)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (passLayout->descriptorSetCount > 0u) {
|
|
|
|
|
std::vector<RHI::RHIDescriptorSet*> descriptorSets(passLayout->descriptorSetCount, nullptr);
|
|
|
|
|
for (Core::uint32 descriptorOffset = 0u; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) {
|
|
|
|
|
const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset;
|
|
|
|
|
if (setIndex >= passLayout->setLayouts.size()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex];
|
2026-04-09 05:22:03 +08:00
|
|
|
if (!(setLayout.usesPerObject || setLayout.usesLighting || setLayout.usesMaterial || setLayout.usesVolumeField)) {
|
2026-04-09 01:11:59 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Core::uint64 objectId =
|
|
|
|
|
(setLayout.usesPerObject && visibleVolume.gameObject != nullptr)
|
|
|
|
|
? visibleVolume.gameObject->GetID()
|
|
|
|
|
: 0u;
|
|
|
|
|
const Resources::Material* materialKey = setLayout.usesMaterial ? material : nullptr;
|
|
|
|
|
const Resources::VolumeField* volumeFieldKey =
|
|
|
|
|
setLayout.usesVolumeField ? visibleVolume.volumeField : nullptr;
|
|
|
|
|
|
|
|
|
|
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
|
|
|
|
|
passLayoutKey,
|
|
|
|
|
*passLayout,
|
|
|
|
|
setLayout,
|
|
|
|
|
setIndex,
|
|
|
|
|
objectId,
|
|
|
|
|
materialKey,
|
|
|
|
|
volumeFieldKey,
|
|
|
|
|
materialConstants,
|
2026-04-09 05:22:03 +08:00
|
|
|
lightingConstants,
|
2026-04-09 01:11:59 +08:00
|
|
|
cachedVolume->shaderResourceView);
|
|
|
|
|
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHIDescriptorSet* descriptorSet = cachedDescriptorSet->descriptorSet.set;
|
|
|
|
|
if (setLayout.usesPerObject) {
|
|
|
|
|
if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
descriptorSet->WriteConstant(
|
|
|
|
|
passLayout->perObject.binding,
|
|
|
|
|
&perObjectConstants,
|
|
|
|
|
sizeof(perObjectConstants));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
descriptorSets[descriptorOffset] = descriptorSet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commandList->SetGraphicsDescriptorSets(
|
|
|
|
|
passLayout->firstDescriptorSet,
|
|
|
|
|
passLayout->descriptorSetCount,
|
|
|
|
|
descriptorSets.data(),
|
|
|
|
|
passLayout->pipelineLayout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ApplyDynamicRenderState(effectiveRenderState, *commandList);
|
|
|
|
|
|
|
|
|
|
if (cachedMesh->indexBufferView != nullptr && cachedMesh->indexCount > 0u) {
|
|
|
|
|
commandList->DrawIndexed(cachedMesh->indexCount, 1u, 0u, 0u, 0u);
|
|
|
|
|
} else if (cachedMesh->vertexCount > 0u) {
|
|
|
|
|
commandList->Draw(cachedMesh->vertexCount, 1u, 0u, 0u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Passes
|
|
|
|
|
} // namespace Rendering
|
|
|
|
|
} // namespace XCEngine
|