- let DrawingSettings carry an optional shaderPassName across managed and native scene draw APIs - allow RenderObjectsRendererFeature to author explicit ForwardLit or Unlit scene draws - exercise the new scene draw pass selection seam in the project render pipeline probe
706 lines
26 KiB
C++
706 lines
26 KiB
C++
#include "Rendering/Pipelines/BuiltinForwardPipeline.h"
|
|
|
|
#include "Components/GameObject.h"
|
|
#include "Debug/Logger.h"
|
|
#include "RHI/RHICommandList.h"
|
|
#include "RHI/RHIDevice.h"
|
|
#include "Rendering/FrameData/RendererListUtils.h"
|
|
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
|
#include "Rendering/Internal/ShaderVariantUtils.h"
|
|
#include "Rendering/Materials/RenderMaterialResolve.h"
|
|
#include "Rendering/Materials/RenderMaterialStateUtils.h"
|
|
#include "Resources/Material/Material.h"
|
|
#include "Resources/Shader/Shader.h"
|
|
#include "Resources/Texture/Texture.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cmath>
|
|
|
|
namespace XCEngine {
|
|
namespace Rendering {
|
|
namespace Pipelines {
|
|
namespace {
|
|
|
|
constexpr float kForwardAmbientIntensity = 0.28f;
|
|
constexpr float kSpotInnerAngleRatio = 0.8f;
|
|
|
|
Resources::ShaderKeywordSet ResolvePassKeywordSet(
|
|
const RenderSceneData& sceneData,
|
|
const Resources::Material* material) {
|
|
return Resources::CombineShaderKeywordSets(
|
|
sceneData.globalShaderKeywords,
|
|
material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet());
|
|
}
|
|
|
|
const Resources::ShaderPass* FindCompatibleSurfacePass(
|
|
const Resources::Shader& shader,
|
|
const RenderSceneData& sceneData,
|
|
const Resources::Material* material,
|
|
BuiltinMaterialPass pass,
|
|
Resources::ShaderBackend backend) {
|
|
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
|
|
|
|
for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) {
|
|
if (ShaderPassMatchesBuiltinPass(shaderPass, pass) &&
|
|
::XCEngine::Rendering::Internal::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,
|
|
const RenderSurface& surface) {
|
|
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
|
pipelineDesc.pipelineLayout = pipelineLayout;
|
|
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
|
::XCEngine::Rendering::Internal::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
|
|
ApplyResolvedRenderState(&shaderPass, material, pipelineDesc);
|
|
|
|
pipelineDesc.inputLayout = BuiltinForwardPipeline::BuildInputLayout();
|
|
|
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
|
const Resources::ShaderStageVariant* vertexVariant =
|
|
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet);
|
|
const Resources::ShaderStageVariant* fragmentVariant =
|
|
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend, keywordSet);
|
|
if (vertexVariant != nullptr) {
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
|
shader.GetPath(),
|
|
shaderPass,
|
|
backend,
|
|
*vertexVariant,
|
|
pipelineDesc.vertexShader);
|
|
}
|
|
if (fragmentVariant != nullptr) {
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
|
shader.GetPath(),
|
|
shaderPass,
|
|
backend,
|
|
*fragmentVariant,
|
|
pipelineDesc.fragmentShader);
|
|
}
|
|
|
|
return pipelineDesc;
|
|
}
|
|
|
|
bool UsesDynamicSurfaceDescriptorSet(const BuiltinPassSetLayoutMetadata& setLayout) {
|
|
return setLayout.usesPerObject ||
|
|
setLayout.usesLighting ||
|
|
setLayout.usesMaterial ||
|
|
setLayout.usesShadowReceiver ||
|
|
setLayout.usesTexture ||
|
|
setLayout.usesMaterialBuffers;
|
|
}
|
|
|
|
bool TryResolveRequestedSurfacePassType(
|
|
const DrawSettings& drawSettings,
|
|
BuiltinMaterialPass& outPass,
|
|
bool& outHasRequestedPass) {
|
|
outHasRequestedPass = false;
|
|
if (!drawSettings.HasShaderPassName()) {
|
|
return true;
|
|
}
|
|
|
|
outHasRequestedPass = true;
|
|
const Containers::String normalizedPassName =
|
|
NormalizeBuiltinPassMetadataValue(drawSettings.shaderPassName);
|
|
if (normalizedPassName == Containers::String("forward") ||
|
|
MatchesBuiltinPassName(normalizedPassName, BuiltinMaterialPass::ForwardLit)) {
|
|
outPass = BuiltinMaterialPass::ForwardLit;
|
|
return true;
|
|
}
|
|
|
|
if (MatchesBuiltinPassName(normalizedPassName, BuiltinMaterialPass::Unlit)) {
|
|
outPass = BuiltinMaterialPass::Unlit;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
RHI::InputLayoutDesc BuiltinForwardPipeline::BuildInputLayout() {
|
|
RHI::InputLayoutDesc inputLayout = {};
|
|
|
|
RHI::InputElementDesc position = {};
|
|
position.semanticName = "POSITION";
|
|
position.semanticIndex = 0;
|
|
position.format = static_cast<uint32_t>(RHI::Format::R32G32B32_Float);
|
|
position.inputSlot = 0;
|
|
position.alignedByteOffset = 0;
|
|
inputLayout.elements.push_back(position);
|
|
|
|
RHI::InputElementDesc normal = {};
|
|
normal.semanticName = "NORMAL";
|
|
normal.semanticIndex = 0;
|
|
normal.format = static_cast<uint32_t>(RHI::Format::R32G32B32_Float);
|
|
normal.inputSlot = 0;
|
|
normal.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, normal));
|
|
inputLayout.elements.push_back(normal);
|
|
|
|
RHI::InputElementDesc texcoord = {};
|
|
texcoord.semanticName = "TEXCOORD";
|
|
texcoord.semanticIndex = 0;
|
|
texcoord.format = static_cast<uint32_t>(RHI::Format::R32G32_Float);
|
|
texcoord.inputSlot = 0;
|
|
texcoord.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, uv0));
|
|
inputLayout.elements.push_back(texcoord);
|
|
|
|
RHI::InputElementDesc backTexcoord = {};
|
|
backTexcoord.semanticName = "TEXCOORD";
|
|
backTexcoord.semanticIndex = 1;
|
|
backTexcoord.format = static_cast<uint32_t>(RHI::Format::R32G32_Float);
|
|
backTexcoord.inputSlot = 0;
|
|
backTexcoord.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, uv1));
|
|
inputLayout.elements.push_back(backTexcoord);
|
|
|
|
RHI::InputElementDesc tangent = {};
|
|
tangent.semanticName = "TEXCOORD";
|
|
tangent.semanticIndex = 2;
|
|
tangent.format = static_cast<uint32_t>(RHI::Format::R32G32B32_Float);
|
|
tangent.inputSlot = 0;
|
|
tangent.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, tangent));
|
|
inputLayout.elements.push_back(tangent);
|
|
|
|
RHI::InputElementDesc bitangent = {};
|
|
bitangent.semanticName = "TEXCOORD";
|
|
bitangent.semanticIndex = 3;
|
|
bitangent.format = static_cast<uint32_t>(RHI::Format::R32G32B32_Float);
|
|
bitangent.inputSlot = 0;
|
|
bitangent.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, bitangent));
|
|
inputLayout.elements.push_back(bitangent);
|
|
|
|
RHI::InputElementDesc color = {};
|
|
color.semanticName = "COLOR";
|
|
color.semanticIndex = 0;
|
|
color.format = static_cast<uint32_t>(RHI::Format::R32G32B32A32_Float);
|
|
color.inputSlot = 0;
|
|
color.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, color));
|
|
inputLayout.elements.push_back(color);
|
|
|
|
return inputLayout;
|
|
}
|
|
|
|
bool BuiltinForwardPipeline::TryResolveSurfacePassType(
|
|
const Resources::Material* material,
|
|
const BuiltinMaterialPass* preferredPass,
|
|
BuiltinMaterialPass& outPass) {
|
|
if (preferredPass != nullptr) {
|
|
if (MatchesBuiltinPass(material, *preferredPass)) {
|
|
outPass = *preferredPass;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (MatchesBuiltinPass(material, BuiltinMaterialPass::Unlit)) {
|
|
outPass = BuiltinMaterialPass::Unlit;
|
|
return true;
|
|
}
|
|
|
|
if (MatchesBuiltinPass(material, BuiltinMaterialPass::ForwardLit)) {
|
|
outPass = BuiltinMaterialPass::ForwardLit;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfaceShaderPass(
|
|
const RenderSceneData& sceneData,
|
|
const Resources::Material* material,
|
|
const BuiltinMaterialPass* preferredPass) const {
|
|
ResolvedShaderPass resolved = {};
|
|
BuiltinMaterialPass pass =
|
|
preferredPass != nullptr
|
|
? *preferredPass
|
|
: BuiltinMaterialPass::ForwardLit;
|
|
if (!TryResolveSurfacePassType(material, preferredPass, pass)) {
|
|
return resolved;
|
|
}
|
|
|
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType);
|
|
|
|
if (material != nullptr && material->GetShader() != nullptr) {
|
|
const Resources::Shader* materialShader = material->GetShader();
|
|
if (const Resources::ShaderPass* shaderPass =
|
|
FindCompatibleSurfacePass(*materialShader, sceneData, material, pass, backend)) {
|
|
resolved.shader = materialShader;
|
|
resolved.pass = shaderPass;
|
|
resolved.passName = shaderPass->name;
|
|
return resolved;
|
|
}
|
|
}
|
|
|
|
const Resources::ResourceHandle<Resources::Shader>* builtinShaderHandle =
|
|
pass == BuiltinMaterialPass::Unlit ? &m_builtinUnlitShader : &m_builtinForwardShader;
|
|
if (builtinShaderHandle->IsValid()) {
|
|
const Resources::Shader* builtinShader = builtinShaderHandle->Get();
|
|
if (const Resources::ShaderPass* shaderPass =
|
|
FindCompatibleSurfacePass(*builtinShader, sceneData, material, pass, backend)) {
|
|
resolved.shader = builtinShader;
|
|
resolved.pass = shaderPass;
|
|
resolved.passName = shaderPass->name;
|
|
}
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState(
|
|
const RenderContext& context,
|
|
const RenderSurface& surface,
|
|
const RenderSceneData& sceneData,
|
|
const Resources::Material* material,
|
|
const BuiltinMaterialPass* preferredPass) {
|
|
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
|
|
const ResolvedShaderPass resolvedShaderPass =
|
|
ResolveSurfaceShaderPass(
|
|
sceneData,
|
|
material,
|
|
preferredPass);
|
|
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
|
Debug::Logger::Get().Error(
|
|
Debug::LogCategory::Rendering,
|
|
"BuiltinForwardPipeline could not resolve a valid surface shader pass");
|
|
return nullptr;
|
|
}
|
|
|
|
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass);
|
|
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
PipelineStateKey pipelineKey = {};
|
|
pipelineKey.renderState =
|
|
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material));
|
|
pipelineKey.shader = resolvedShaderPass.shader;
|
|
pipelineKey.passName = resolvedShaderPass.passName;
|
|
pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet);
|
|
pipelineKey.renderTargetCount =
|
|
::XCEngine::Rendering::Internal::ResolveSurfaceColorAttachmentCount(surface);
|
|
pipelineKey.renderTargetFormats =
|
|
::XCEngine::Rendering::Internal::ResolveSurfaceColorFormats(surface);
|
|
pipelineKey.depthStencilFormat =
|
|
static_cast<Core::uint32>(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface));
|
|
pipelineKey.sampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
|
pipelineKey.sampleQuality = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
|
|
|
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,
|
|
surface);
|
|
RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc);
|
|
if (pipelineState == nullptr || !pipelineState->IsValid()) {
|
|
Debug::Logger::Get().Error(
|
|
Debug::LogCategory::Rendering,
|
|
"BuiltinForwardPipeline failed to create pipeline state");
|
|
if (pipelineState != nullptr) {
|
|
pipelineState->Shutdown();
|
|
delete pipelineState;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
m_pipelineStates.emplace(pipelineKey, pipelineState);
|
|
return pipelineState;
|
|
}
|
|
|
|
const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources::Material* material) const {
|
|
return ResolveBuiltinBaseColorTexture(material);
|
|
}
|
|
|
|
RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView(
|
|
const Resources::Texture* texture) {
|
|
if (texture == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
const RenderResourceCache::CachedTexture* cachedTexture = m_resourceCache.GetOrCreateTexture(m_device, texture);
|
|
if (cachedTexture != nullptr && cachedTexture->shaderResourceView != nullptr) {
|
|
return cachedTexture->shaderResourceView;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView(
|
|
const Resources::Material* material) {
|
|
const Resources::Texture* texture = ResolveTexture(material);
|
|
RHI::RHIResourceView* textureView = ResolveTextureView(texture);
|
|
return textureView != nullptr ? textureView : m_fallbackTexture2DView;
|
|
}
|
|
|
|
BuiltinForwardPipeline::LightingConstants BuiltinForwardPipeline::BuildLightingConstants(
|
|
const RenderLightingData& lightingData) {
|
|
LightingConstants lightingConstants = {};
|
|
|
|
if (lightingData.HasMainDirectionalLight()) {
|
|
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);
|
|
}
|
|
|
|
const Core::uint32 additionalLightCount = std::min<Core::uint32>(
|
|
lightingData.additionalLightCount,
|
|
kMaxLightingAdditionalLightCount);
|
|
lightingConstants.lightingParams = Math::Vector4(
|
|
static_cast<float>(additionalLightCount),
|
|
kForwardAmbientIntensity,
|
|
0.0f,
|
|
0.0f);
|
|
|
|
for (Core::uint32 index = 0; index < additionalLightCount; ++index) {
|
|
lightingConstants.additionalLights[index] =
|
|
BuildAdditionalLightConstants(lightingData.additionalLights[index]);
|
|
}
|
|
|
|
return lightingConstants;
|
|
}
|
|
|
|
BuiltinForwardPipeline::AdditionalLightConstants BuiltinForwardPipeline::BuildAdditionalLightConstants(
|
|
const RenderAdditionalLightData& lightData) {
|
|
AdditionalLightConstants constants = {};
|
|
constants.colorAndIntensity = Math::Vector4(
|
|
lightData.color.r,
|
|
lightData.color.g,
|
|
lightData.color.b,
|
|
lightData.intensity);
|
|
constants.positionAndRange = Math::Vector4(
|
|
lightData.position.x,
|
|
lightData.position.y,
|
|
lightData.position.z,
|
|
lightData.range);
|
|
constants.directionAndType = Math::Vector4(
|
|
lightData.direction.x,
|
|
lightData.direction.y,
|
|
lightData.direction.z,
|
|
static_cast<float>(lightData.type));
|
|
|
|
if (lightData.type == RenderLightType::Spot) {
|
|
const float outerHalfAngleRadians = Math::Radians(lightData.spotAngle * 0.5f);
|
|
const float innerHalfAngleRadians = outerHalfAngleRadians * kSpotInnerAngleRatio;
|
|
constants.spotAnglesAndFlags = Math::Vector4(
|
|
std::cos(outerHalfAngleRadians),
|
|
std::cos(innerHalfAngleRadians),
|
|
lightData.castsShadows ? 1.0f : 0.0f,
|
|
0.0f);
|
|
} else {
|
|
constants.spotAnglesAndFlags = Math::Vector4(
|
|
0.0f,
|
|
0.0f,
|
|
lightData.castsShadows ? 1.0f : 0.0f,
|
|
0.0f);
|
|
}
|
|
|
|
return constants;
|
|
}
|
|
|
|
bool BuiltinForwardPipeline::ExecuteForwardOpaquePass(
|
|
const ScenePhaseExecutionContext& executionContext) {
|
|
return DrawVisibleItems(
|
|
executionContext.frameContext,
|
|
BuildDrawSettings(executionContext.scenePhase));
|
|
}
|
|
|
|
bool BuiltinForwardPipeline::ExecuteForwardTransparentPass(
|
|
const ScenePhaseExecutionContext& executionContext) {
|
|
return DrawVisibleItems(
|
|
executionContext.frameContext,
|
|
BuildDrawSettings(executionContext.scenePhase));
|
|
}
|
|
|
|
bool BuiltinForwardPipeline::DrawVisibleItem(
|
|
const FrameExecutionContext& executionContext,
|
|
const VisibleRenderItem& visibleItem,
|
|
const Resources::Material* material,
|
|
const BuiltinMaterialPass* preferredPass) {
|
|
const RenderContext& context = executionContext.renderContext;
|
|
const RenderSceneData& sceneData = executionContext.sceneData;
|
|
|
|
const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleItem.mesh);
|
|
if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
RHI::RHICommandList* commandList = context.commandList;
|
|
|
|
RHI::RHIResourceView* vertexBuffers[] = { cachedMesh->vertexBufferView };
|
|
const uint64_t offsets[] = { 0 };
|
|
const uint32_t strides[] = { cachedMesh->vertexStride };
|
|
commandList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides);
|
|
if (cachedMesh->indexBufferView != nullptr) {
|
|
commandList->SetIndexBuffer(cachedMesh->indexBufferView, 0);
|
|
}
|
|
|
|
const PerObjectConstants constants = {
|
|
sceneData.cameraData.projection,
|
|
sceneData.cameraData.view,
|
|
visibleItem.localToWorld.Transpose(),
|
|
visibleItem.localToWorld.Inverse()
|
|
};
|
|
const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting);
|
|
ShadowReceiverConstants shadowReceiverConstants = {};
|
|
if (sceneData.lighting.HasMainDirectionalShadow()) {
|
|
shadowReceiverConstants.worldToShadow = sceneData.lighting.mainDirectionalShadow.viewProjection;
|
|
shadowReceiverConstants.shadowMapMetrics = sceneData.lighting.mainDirectionalShadow.mapMetrics;
|
|
shadowReceiverConstants.shadowSampling = sceneData.lighting.mainDirectionalShadow.sampling;
|
|
}
|
|
|
|
const ResolvedShaderPass resolvedShaderPass =
|
|
ResolveSurfaceShaderPass(
|
|
sceneData,
|
|
material,
|
|
preferredPass);
|
|
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
|
return false;
|
|
}
|
|
const Resources::MaterialRenderState effectiveRenderState =
|
|
ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
|
|
|
|
PassLayoutKey passLayoutKey = {};
|
|
passLayoutKey.shader = resolvedShaderPass.shader;
|
|
passLayoutKey.passName = resolvedShaderPass.passName;
|
|
|
|
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass);
|
|
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
RHI::RHIResourceView* baseColorTextureView = ResolveTextureView(material);
|
|
if (passLayout->baseColorTexture.IsValid() && baseColorTextureView == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
RHI::RHIResourceView* shadowMapTextureView = sceneData.lighting.HasMainDirectionalShadow()
|
|
? sceneData.lighting.mainDirectionalShadow.shadowMap
|
|
: m_fallbackTexture2DView;
|
|
if (passLayout->shadowMapTexture.IsValid() && shadowMapTextureView == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material);
|
|
if (passLayout->material.IsValid() && !materialConstants.IsValid()) {
|
|
Debug::Logger::Get().Error(
|
|
Debug::LogCategory::Rendering,
|
|
"BuiltinForwardPipeline requires a schema-backed material constant payload");
|
|
return false;
|
|
}
|
|
|
|
if (passLayout->descriptorSetCount > 0) {
|
|
std::vector<RHI::RHIDescriptorSet*> descriptorSets(passLayout->descriptorSetCount, nullptr);
|
|
|
|
for (Core::uint32 descriptorOffset = 0; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) {
|
|
const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset;
|
|
if (setIndex >= passLayout->setLayouts.size()) {
|
|
return false;
|
|
}
|
|
|
|
const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex];
|
|
RHI::RHIDescriptorSet* descriptorSet = nullptr;
|
|
|
|
if (UsesDynamicSurfaceDescriptorSet(setLayout)) {
|
|
const Core::uint64 objectId =
|
|
(setLayout.usesPerObject && visibleItem.gameObject != nullptr)
|
|
? visibleItem.gameObject->GetID()
|
|
: 0;
|
|
const Resources::Material* materialKey =
|
|
(setLayout.usesMaterial ||
|
|
setLayout.usesBaseColorTexture ||
|
|
setLayout.usesMaterialBuffers ||
|
|
setLayout.usesMaterialTextures)
|
|
? material
|
|
: nullptr;
|
|
|
|
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
|
|
passLayoutKey,
|
|
*passLayout,
|
|
setLayout,
|
|
setIndex,
|
|
objectId,
|
|
materialKey,
|
|
materialConstants,
|
|
lightingConstants,
|
|
shadowReceiverConstants,
|
|
baseColorTextureView,
|
|
shadowMapTextureView);
|
|
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
descriptorSet = cachedDescriptorSet->descriptorSet.set;
|
|
if (setLayout.usesPerObject) {
|
|
if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) {
|
|
return false;
|
|
}
|
|
|
|
descriptorSet->WriteConstant(
|
|
passLayout->perObject.binding,
|
|
&constants,
|
|
sizeof(constants));
|
|
}
|
|
} else {
|
|
descriptorSet = GetOrCreateStaticDescriptorSet(*passLayout, setIndex);
|
|
if (descriptorSet == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
descriptorSets[descriptorOffset] = descriptorSet;
|
|
}
|
|
|
|
commandList->SetGraphicsDescriptorSets(
|
|
passLayout->firstDescriptorSet,
|
|
passLayout->descriptorSetCount,
|
|
descriptorSets.data(),
|
|
passLayout->pipelineLayout);
|
|
}
|
|
|
|
ApplyDynamicRenderState(effectiveRenderState, *commandList);
|
|
|
|
if (visibleItem.hasSection) {
|
|
const Containers::Array<Resources::MeshSection>& sections = visibleItem.mesh->GetSections();
|
|
if (visibleItem.sectionIndex >= sections.Size()) {
|
|
return false;
|
|
}
|
|
|
|
const Resources::MeshSection& section = sections[visibleItem.sectionIndex];
|
|
if (cachedMesh->indexBufferView != nullptr && section.indexCount > 0) {
|
|
// MeshLoader flattens section indices into a single global index buffer.
|
|
commandList->DrawIndexed(section.indexCount, 1, section.startIndex, 0, 0);
|
|
} else if (section.vertexCount > 0) {
|
|
commandList->Draw(section.vertexCount, 1, section.baseVertex, 0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (cachedMesh->indexBufferView != nullptr && cachedMesh->indexCount > 0) {
|
|
commandList->DrawIndexed(cachedMesh->indexCount, 1, 0, 0, 0);
|
|
} else if (cachedMesh->vertexCount > 0) {
|
|
commandList->Draw(cachedMesh->vertexCount, 1, 0, 0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BuiltinForwardPipeline::DrawVisibleItems(
|
|
const FrameExecutionContext& executionContext,
|
|
const DrawSettings& drawSettings) {
|
|
const RenderContext& context = executionContext.renderContext;
|
|
const RenderSurface& surface = executionContext.surface;
|
|
const RenderSceneData& sceneData = executionContext.sceneData;
|
|
|
|
RHI::RHICommandList* commandList = context.commandList;
|
|
RHI::RHIPipelineState* currentPipelineState = nullptr;
|
|
bool drawFailed = false;
|
|
BuiltinMaterialPass requestedPass = BuiltinMaterialPass::ForwardLit;
|
|
bool hasRequestedPass = false;
|
|
if (!TryResolveRequestedSurfacePassType(
|
|
drawSettings,
|
|
requestedPass,
|
|
hasRequestedPass)) {
|
|
Debug::Logger::Get().Error(
|
|
Debug::LogCategory::Rendering,
|
|
(Containers::String(
|
|
"BuiltinForwardPipeline only supports explicit shaderPassName values of ForwardLit or Unlit for scene draws, received: ") +
|
|
drawSettings.shaderPassName)
|
|
.CStr());
|
|
return false;
|
|
}
|
|
|
|
const BuiltinMaterialPass* preferredPass =
|
|
hasRequestedPass
|
|
? &requestedPass
|
|
: nullptr;
|
|
|
|
auto drawVisibleItem = [&](const VisibleRenderItem& visibleItem) {
|
|
if (drawFailed) {
|
|
return;
|
|
}
|
|
|
|
const Resources::Material* material =
|
|
drawSettings.HasOverrideMaterial()
|
|
? drawSettings.overrideMaterial.Get()
|
|
: ResolveMaterial(visibleItem);
|
|
BuiltinMaterialPass pass =
|
|
preferredPass != nullptr
|
|
? *preferredPass
|
|
: BuiltinMaterialPass::ForwardLit;
|
|
if (!TryResolveSurfacePassType(
|
|
material,
|
|
preferredPass,
|
|
pass)) {
|
|
return;
|
|
}
|
|
|
|
RHI::RHIPipelineState* pipelineState =
|
|
GetOrCreatePipelineState(
|
|
context,
|
|
surface,
|
|
sceneData,
|
|
material,
|
|
preferredPass);
|
|
if (pipelineState == nullptr) {
|
|
drawFailed = true;
|
|
return;
|
|
}
|
|
if (pipelineState != currentPipelineState) {
|
|
commandList->SetPipelineState(pipelineState);
|
|
currentPipelineState = pipelineState;
|
|
}
|
|
|
|
if (!DrawVisibleItem(
|
|
executionContext,
|
|
visibleItem,
|
|
material,
|
|
preferredPass)) {
|
|
drawFailed = true;
|
|
}
|
|
};
|
|
|
|
VisitRendererListVisibleItems(
|
|
sceneData,
|
|
drawSettings.rendererListDesc,
|
|
drawVisibleItem);
|
|
return !drawFailed;
|
|
}
|
|
|
|
} // namespace Pipelines
|
|
} // namespace Rendering
|
|
} // namespace XCEngine
|