244 lines
8.6 KiB
C++
244 lines
8.6 KiB
C++
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
|
|
|
#include "DeferredMaterialShader.h"
|
|
#include "MaterialShaderFeatures.h"
|
|
#include "MaterialParams.h"
|
|
#include "Engine/Core/Math/Matrix3x4.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/Models/SkinnedMeshDrawData.h"
|
|
#include "Engine/Graphics/GPUDevice.h"
|
|
#include "Engine/Graphics/Shaders/GPUShader.h"
|
|
#include "Engine/Graphics/GPULimits.h"
|
|
#include "Engine/Graphics/RenderTask.h"
|
|
|
|
PACK_STRUCT(struct DeferredMaterialShaderData {
|
|
Matrix3x4 WorldMatrix;
|
|
Matrix3x4 PrevWorldMatrix;
|
|
Float2 Dummy0;
|
|
float LODDitherFactor;
|
|
float PerInstanceRandom;
|
|
Float3 GeometrySize;
|
|
float WorldDeterminantSign;
|
|
});
|
|
|
|
DrawPass DeferredMaterialShader::GetDrawModes() const
|
|
{
|
|
return DrawPass::Depth | DrawPass::GBuffer | DrawPass::GlobalSurfaceAtlas | DrawPass::MotionVectors | DrawPass::QuadOverdraw;
|
|
}
|
|
|
|
bool DeferredMaterialShader::CanUseLightmap() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool DeferredMaterialShader::CanUseInstancing(InstancingHandler& handler) const
|
|
{
|
|
handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, SurfaceDrawCallHandler::WriteDrawCall, };
|
|
return true;
|
|
}
|
|
|
|
void DeferredMaterialShader::Bind(BindParameters& params)
|
|
{
|
|
//PROFILE_CPU();
|
|
// Prepare
|
|
auto context = params.GPUContext;
|
|
auto& view = params.RenderContext.View;
|
|
auto& drawCall = *params.FirstDrawCall;
|
|
Span<byte> cb(_cbData.Get(), _cbData.Count());
|
|
ASSERT_LOW_LAYER(cb.Length() >= sizeof(DeferredMaterialShaderData));
|
|
auto materialData = reinterpret_cast<DeferredMaterialShaderData*>(cb.Get());
|
|
cb = Span<byte>(cb.Get() + sizeof(DeferredMaterialShaderData), cb.Length() - sizeof(DeferredMaterialShaderData));
|
|
int32 srv = 2;
|
|
|
|
// Setup features
|
|
const bool useLightmap = _info.BlendMode == MaterialBlendMode::Opaque && LightmapFeature::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);
|
|
|
|
// Setup material constants
|
|
{
|
|
materialData->WorldMatrix.SetMatrixTranspose(drawCall.World);
|
|
materialData->PrevWorldMatrix.SetMatrixTranspose(drawCall.Surface.PrevWorld);
|
|
materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign;
|
|
materialData->LODDitherFactor = drawCall.Surface.LODDitherFactor;
|
|
materialData->PerInstanceRandom = drawCall.PerInstanceRandom;
|
|
materialData->GeometrySize = drawCall.Surface.GeometrySize;
|
|
}
|
|
|
|
// Check if is using mesh skinning
|
|
const bool useSkinning = drawCall.Surface.Skinning != nullptr;
|
|
bool perBoneMotionBlur = false;
|
|
if (useSkinning)
|
|
{
|
|
// Bind skinning buffer
|
|
ASSERT(drawCall.Surface.Skinning->IsReady());
|
|
context->BindSR(0, drawCall.Surface.Skinning->BoneMatrices->View());
|
|
if (drawCall.Surface.Skinning->PrevBoneMatrices && drawCall.Surface.Skinning->PrevBoneMatrices->IsAllocated())
|
|
{
|
|
context->BindSR(1, drawCall.Surface.Skinning->PrevBoneMatrices->View());
|
|
perBoneMotionBlur = true;
|
|
}
|
|
}
|
|
|
|
// 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.WorldDeterminantSign < 0)
|
|
{
|
|
// Invert culling when scale is negative
|
|
if (cullMode == CullMode::Normal)
|
|
cullMode = CullMode::Inverted;
|
|
else
|
|
cullMode = CullMode::Normal;
|
|
}
|
|
ASSERT_LOW_LAYER(!(useSkinning && params.DrawCallsCount > 1)); // No support for instancing skinned meshes
|
|
const auto cache = params.DrawCallsCount == 1 ? &_cache : &_cacheInstanced;
|
|
PipelineStateCache* psCache = cache->GetPS(view.Pass, useLightmap, useSkinning, perBoneMotionBlur);
|
|
ASSERT(psCache);
|
|
GPUPipelineState* state = psCache->GetPS(cullMode, wireframe);
|
|
|
|
// Bind pipeline
|
|
context->SetState(state);
|
|
}
|
|
|
|
void DeferredMaterialShader::Unload()
|
|
{
|
|
// Base
|
|
MaterialShader::Unload();
|
|
|
|
_cache.Release();
|
|
_cacheInstanced.Release();
|
|
}
|
|
|
|
bool DeferredMaterialShader::Load()
|
|
{
|
|
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;
|
|
}
|
|
|
|
// 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");
|
|
}
|
|
|
|
// GBuffer Pass
|
|
psDesc.VS = _shader->GetVS("VS");
|
|
failed |= psDesc.VS == nullptr;
|
|
psDesc.PS = _shader->GetPS("PS_GBuffer");
|
|
_cache.Default.Init(psDesc);
|
|
psDesc.VS = _shader->GetVS("VS", 1);
|
|
failed |= psDesc.VS == nullptr;
|
|
_cacheInstanced.Default.Init(psDesc);
|
|
|
|
// GBuffer Pass with lightmap (pixel shader permutation for USE_LIGHTMAP=1)
|
|
psDesc.VS = _shader->GetVS("VS");
|
|
failed |= psDesc.VS == nullptr;
|
|
psDesc.PS = _shader->GetPS("PS_GBuffer", 1);
|
|
_cache.DefaultLightmap.Init(psDesc);
|
|
psDesc.VS = _shader->GetVS("VS", 1);
|
|
failed |= psDesc.VS == nullptr;
|
|
_cacheInstanced.DefaultLightmap.Init(psDesc);
|
|
|
|
// GBuffer Pass with skinning
|
|
psDesc.VS = _shader->GetVS("VS_Skinned");
|
|
psDesc.PS = _shader->GetPS("PS_GBuffer");
|
|
_cache.DefaultSkinned.Init(psDesc);
|
|
|
|
#if USE_EDITOR
|
|
if (_shader->HasShader("PS_QuadOverdraw"))
|
|
{
|
|
// Quad Overdraw
|
|
psDesc.VS = _shader->GetVS("VS");
|
|
psDesc.PS = _shader->GetPS("PS_QuadOverdraw");
|
|
_cache.QuadOverdraw.Init(psDesc);
|
|
psDesc.VS = _shader->GetVS("VS", 1);
|
|
_cacheInstanced.Depth.Init(psDesc);
|
|
psDesc.VS = _shader->GetVS("VS_Skinned");
|
|
_cache.QuadOverdrawSkinned.Init(psDesc);
|
|
}
|
|
#endif
|
|
|
|
// Motion Vectors pass
|
|
psDesc.DepthWriteEnable = false;
|
|
psDesc.DepthEnable = true;
|
|
psDesc.DepthFunc = ComparisonFunc::LessEqual;
|
|
psDesc.VS = _shader->GetVS("VS");
|
|
psDesc.PS = _shader->GetPS("PS_MotionVectors");
|
|
_cache.MotionVectors.Init(psDesc);
|
|
|
|
// Motion Vectors pass with skinning
|
|
psDesc.VS = _shader->GetVS("VS_Skinned");
|
|
_cache.MotionVectorsSkinned.Init(psDesc);
|
|
|
|
// Motion Vectors pass with skinning (with per-bone motion blur)
|
|
psDesc.VS = _shader->GetVS("VS_Skinned", 1);
|
|
_cache.MotionVectorsSkinnedPerBone.Init(psDesc);
|
|
|
|
// Depth Pass
|
|
psDesc.CullMode = CullMode::TwoSided;
|
|
psDesc.DepthClipEnable = false;
|
|
psDesc.DepthWriteEnable = true;
|
|
psDesc.DepthEnable = true;
|
|
psDesc.DepthFunc = ComparisonFunc::Less;
|
|
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 = _shader->GetVS("VS");
|
|
instancedDepthPassVS = _shader->GetVS("VS", 1);
|
|
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 = _shader->GetVS("VS_Skinned");
|
|
_cache.DepthSkinned.Init(psDesc);
|
|
|
|
return failed;
|
|
}
|