Files
FlaxEngine/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp
T
mafiesto4 bc36168318 Optimize Animated Model rendering with hardware instancing
All models are using the same global buffer for skinned bones which allows to share shader binding for instancing.
Refactor draw call for batching skinned mesh draws.
Remove `SkinnedMeshDrawData` and merge it into `AnimatedModel` internals.
2026-06-15 17:59:41 +02:00

239 lines
8.3 KiB
C++

// Copyright (c) Wojciech Figat. All rights reserved.
#include "DeferredMaterialShader.h"
#include "MaterialShaderFeatures.h"
#include "MaterialParams.h"
#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/RenderView.h"
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Level/Scene/Lightmap.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/Shaders/GPUConstantBuffer.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Shaders/GPUShader.h"
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Graphics/RenderTask.h"
DrawPass DeferredMaterialShader::GetDrawModes() const
{
return DrawPass::Depth | DrawPass::GBuffer | DrawPass::GlobalSurfaceAtlas | DrawPass::MotionVectors | DrawPass::QuadOverdraw;
}
bool DeferredMaterialShader::CanUseLightmap() const
{
return true;
}
bool DeferredMaterialShader::CanUseInstancing(const RenderContext& renderContext, InstancingHandler& handler) const
{
handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, };
return _instanced || renderContext.View.Pass == DrawPass::Depth;
}
void DeferredMaterialShader::Bind(BindParameters& params)
{
auto context = params.GPUContext;
auto& view = params.RenderContext.View;
auto& drawCall = *params.DrawCall;
Span<byte> cb(_cbData.Get(), _cbData.Count());
int32 srv = 3;
// Setup features
const bool useLightmap = _info.BlendMode == MaterialBlendMode::Opaque && LightmapFeature::Bind(params, cb, srv);
if (_info.ShadingModel == MaterialShadingModel::CustomLit)
ForwardShadingFeature::Bind(params, cb, srv);
// Setup parameters
MaterialParameter::BindMeta bindMeta;
bindMeta.Context = context;
bindMeta.Constants = cb;
bindMeta.Input = nullptr;
bindMeta.Buffers = params.RenderContext.Buffers;
bindMeta.CanSampleDepth = false;
bindMeta.CanSampleGBuffer = false;
MaterialParams::Bind(params.ParamsLink, bindMeta);
context->BindSR(0, params.ObjectBuffer);
// Bind skinning buffer
const bool useSkinning = drawCall.Surface.Skinning != DrawCall::SkinningMode::None;
const bool usePerBoneMotionBlur = drawCall.Surface.Skinning == DrawCall::SkinningMode::WithPrevBones;
if (useSkinning)
{
context->BindSR(1, drawCall.Surface.SkinningBones->View());
}
// Bind constants
if (_cb)
{
context->UpdateCB(_cb, _cbData.Get());
context->BindCB(0, _cb);
}
// Select pipeline state based on current pass and render mode
const bool wireframe = (_info.FeaturesFlags & MaterialFeaturesFlags::Wireframe) != MaterialFeaturesFlags::None || view.Mode == ViewMode::Wireframe;
CullMode cullMode = view.Pass == DrawPass::Depth ? CullMode::TwoSided : _info.CullMode;
#if USE_EDITOR
if (IsRunningRadiancePass)
cullMode = CullMode::TwoSided;
#endif
if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminant)
{
// Invert culling when scale is negative
cullMode = cullMode == CullMode::Normal ? CullMode::Inverted : CullMode::Normal;
}
const auto cache = params.Instanced ? &_cacheInstanced : &_cache;
PipelineStateCache* psCache = cache->GetPS(view.Pass, useLightmap, useSkinning, usePerBoneMotionBlur);
ASSERT(psCache);
GPUPipelineState* state = psCache->GetPS(cullMode, wireframe);
// Bind pipeline
context->SetState(state);
context->SetStencilRef(drawCall.StencilValue);
}
void DeferredMaterialShader::Unload()
{
// Base
MaterialShader::Unload();
_cache.Release();
_cacheInstanced.Release();
}
bool DeferredMaterialShader::Load()
{
// TODO: support instancing when using ForwardShadingFeature
_instanced = _info.BlendMode == MaterialBlendMode::Opaque && _info.ShadingModel != MaterialShadingModel::CustomLit;
bool failed = false;
auto psDesc = GPUPipelineState::Description::Default;
psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None;
if (EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::DisableDepthTest))
{
psDesc.DepthFunc = ComparisonFunc::Always;
if (!psDesc.DepthWriteEnable)
psDesc.DepthEnable = false;
}
#if GPU_ALLOW_TESSELLATION_SHADERS
// Check if use tessellation (both material and runtime supports it)
const bool useTess = _info.TessellationMode != TessellationMethod::None && GPUDevice::Instance->Limits.HasTessellation;
if (useTess)
{
psDesc.HS = _shader->GetHS("HS");
psDesc.DS = _shader->GetDS("DS");
}
#endif
psDesc.StencilEnable = true;
psDesc.StencilReadMask = 0;
psDesc.StencilPassOp = StencilOperation::Replace;
auto vs = _shader->GetVS("VS");
auto vsInstanced = _shader->GetVS("VS", 1);
auto vsSkinned = _shader->GetVS("VS_Skinned");
auto vsSkinnedInstanced = _shader->GetVS("VS_Skinned", 2);
// GBuffer Pass
psDesc.VS = vs;
failed |= psDesc.VS == nullptr;
psDesc.PS = _shader->GetPS("PS_GBuffer");
_cache.Default.Init(psDesc);
psDesc.VS = vsInstanced;
failed |= psDesc.VS == nullptr;
_cacheInstanced.Default.Init(psDesc);
// GBuffer Pass with lightmap (USE_LIGHTMAP=1)
psDesc.VS = vs;
failed |= psDesc.VS == nullptr;
psDesc.PS = _shader->GetPS("PS_GBuffer", 1);
_cache.DefaultLightmap.Init(psDesc);
psDesc.VS = vsInstanced;
failed |= psDesc.VS == nullptr;
_cacheInstanced.DefaultLightmap.Init(psDesc);
// GBuffer Pass with skinning (USE_SKINNING=1)
psDesc.VS = vsSkinned;
psDesc.PS = _shader->GetPS("PS_GBuffer");
_cache.DefaultSkinned.Init(psDesc);
psDesc.VS = vsSkinnedInstanced;
_cacheInstanced.DefaultSkinned.Init(psDesc);
psDesc.StencilEnable = false;
psDesc.StencilPassOp = StencilOperation::Keep;
#if USE_EDITOR
if (_shader->HasShader("PS_QuadOverdraw"))
{
// Quad Overdraw
psDesc.VS = vs;
psDesc.PS = _shader->GetPS("PS_QuadOverdraw");
_cache.QuadOverdraw.Init(psDesc);
psDesc.VS = vsInstanced;
_cacheInstanced.Depth.Init(psDesc);
psDesc.VS = vsSkinned;
_cache.QuadOverdrawSkinned.Init(psDesc);
psDesc.VS = vsSkinnedInstanced;
_cacheInstanced.QuadOverdrawSkinned.Init(psDesc);
}
#endif
// Motion Vectors pass
psDesc.DepthWriteEnable = false;
psDesc.DepthEnable = true;
psDesc.DepthFunc = ComparisonFunc::DefaultEqual;
psDesc.VS = vs;
psDesc.PS = _shader->GetPS("PS_MotionVectors");
_cache.MotionVectors.Init(psDesc);
_cacheInstanced.MotionVectors.Init(psDesc);
// Motion Vectors pass with skinning
psDesc.VS = vsSkinned;
_cache.MotionVectorsSkinned.Init(psDesc);
psDesc.VS = vsSkinnedInstanced;
_cacheInstanced.MotionVectorsSkinned.Init(psDesc);
// Motion Vectors pass with skinning (with per-bone motion blur)
psDesc.VS = _shader->GetVS("VS_Skinned", 1);
_cache.MotionVectorsSkinnedPerBone.Init(psDesc);
psDesc.VS = _shader->GetVS("VS_Skinned", 3);
_cacheInstanced.MotionVectorsSkinnedPerBone.Init(psDesc);
// Depth Pass
psDesc.CullMode = CullMode::TwoSided;
psDesc.DepthClipEnable = false;
psDesc.DepthWriteEnable = true;
psDesc.DepthEnable = true;
psDesc.DepthFunc = ComparisonFunc::Default;
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::None;
psDesc.HS = nullptr;
psDesc.DS = nullptr;
GPUShaderProgramVS* instancedDepthPassVS;
if (EnumHasAnyFlags(_info.UsageFlags, MaterialUsageFlags::UseMask | MaterialUsageFlags::UsePositionOffset))
{
// Materials with masking need full vertex buffer to get texcoord used to sample textures for per pixel masking.
// Materials with world pos offset need full VB to apply offset using texcoord etc.
psDesc.VS = vs;
instancedDepthPassVS = vsInstanced;
psDesc.PS = _shader->GetPS("PS_Depth");
}
else
{
psDesc.VS = _shader->GetVS("VS_Depth");
instancedDepthPassVS = _shader->GetVS("VS_Depth", 1);
psDesc.PS = nullptr;
}
_cache.Depth.Init(psDesc);
psDesc.VS = instancedDepthPassVS;
_cacheInstanced.Depth.Init(psDesc);
// Depth Pass with skinning
psDesc.VS = vsSkinned;
_cache.DepthSkinned.Init(psDesc);
psDesc.VS = vsSkinnedInstanced;
_cacheInstanced.DepthSkinned.Init(psDesc);
return failed;
}