Refactor scene rendering to use batched culling for main view and shadow projections

This commit is contained in:
Wojtek Figat
2022-10-28 09:13:28 +02:00
parent e217d5e79b
commit eb52d333ae
44 changed files with 1064 additions and 514 deletions

View File

@@ -1218,6 +1218,13 @@ void Actor::Draw(RenderContext& renderContext)
{
}
void Actor::Draw(RenderContextBatch& renderContextBatch)
{
// Default impl calls single-context
for (RenderContext& renderContext : renderContextBatch.Contexts)
Draw(renderContext);
}
#if USE_EDITOR
void Actor::OnDebugDraw()

View File

@@ -12,6 +12,7 @@
struct RenderView;
struct RenderContext;
struct RenderContextBatch;
class GPUContext;
class MemoryWriteStream;
class PhysicsScene;
@@ -675,6 +676,12 @@ public:
/// <param name="renderContext">The rendering context.</param>
virtual void Draw(RenderContext& renderContext);
/// <summary>
/// Draws this actor. Called by Scene Rendering service. This call is more optimized than generic Draw (eg. geometry is rendered during all pass types but other actors are drawn only during GBufferFill pass).
/// </summary>
/// <param name="renderContextBatch">The rendering context batch (eg, main view and shadow projections).</param>
virtual void Draw(RenderContextBatch& renderContextBatch);
#if USE_EDITOR
/// <summary>

View File

@@ -707,8 +707,7 @@ void AnimatedModel::Draw(RenderContext& renderContext)
renderContext.View.GetWorldMatrix(_transform, world);
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world);
const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass & (int32)renderContext.View.GetShadowsDrawPassMask(ShadowsMode));
if (SkinnedModel && SkinnedModel->IsLoaded() && drawModes != DrawPass::None)
if (SkinnedModel && SkinnedModel->IsLoaded())
{
_lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.Position + renderContext.View.Origin));
@@ -728,7 +727,9 @@ void AnimatedModel::Draw(RenderContext& renderContext)
draw.BlendShapes = &_blendShapes;
draw.World = &world;
draw.DrawState = &_drawState;
draw.DrawModes = drawModes;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
draw.DrawModes = (DrawPass)(DrawModes & renderContext.View.GetShadowsDrawPassMask(ShadowsMode));
PRAGMA_ENABLE_DEPRECATION_WARNINGS
draw.Bounds = _sphere;
draw.Bounds.Center -= renderContext.View.Origin;
draw.PerInstanceRandom = GetPerInstanceRandom();
@@ -742,6 +743,58 @@ void AnimatedModel::Draw(RenderContext& renderContext)
GEOMETRY_DRAW_STATE_EVENT_END(_drawState, world);
}
void AnimatedModel::Draw(RenderContextBatch& renderContextBatch)
{
if (!SkinnedModel || !SkinnedModel->IsLoaded())
return;
const RenderContext& renderContext = renderContextBatch.GetMainContext();
Matrix world;
renderContext.View.GetWorldMatrix(_transform, world);
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world);
_lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.Position + renderContext.View.Origin));
if (_skinningData.IsReady())
{
#if USE_EDITOR
// Disable motion blur effects in editor without play mode enabled to hide minor artifacts on objects moving
if (!Editor::IsPlayMode)
_drawState.PrevWorld = world;
#endif
_skinningData.Flush(GPUDevice::Instance->GetMainContext());
SkinnedMesh::DrawInfo draw;
draw.Buffer = &Entries;
draw.Skinning = &_skinningData;
draw.BlendShapes = &_blendShapes;
draw.World = &world;
draw.DrawState = &_drawState;
draw.DrawModes = DrawModes;
draw.Bounds = _sphere;
draw.Bounds.Center -= renderContext.View.Origin;
draw.PerInstanceRandom = GetPerInstanceRandom();
draw.LODBias = LODBias;
draw.ForcedLOD = ForcedLOD;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (ShadowsMode != ShadowsCastingMode::All)
{
// To handle old ShadowsMode option for all meshes we need to call per-context drawing (no batching opportunity)
// TODO: maybe deserialize ShadowsMode into ModelInstanceBuffer entries options?
for (auto& e : renderContextBatch.Contexts)
{
draw.DrawModes = (DrawPass)(DrawModes & e.View.GetShadowsDrawPassMask(ShadowsMode));
SkinnedModel->Draw(e, draw);
}
}
else
{
SkinnedModel->Draw(renderContextBatch, draw);
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
GEOMETRY_DRAW_STATE_EVENT_END(_drawState, world);
}
#if USE_EDITOR
#include "Engine/Debug/DebugDraw.h"
@@ -795,7 +848,9 @@ void AnimatedModel::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE(LODBias);
SERIALIZE(ForcedLOD);
SERIALIZE(DrawModes);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
SERIALIZE(ShadowsMode);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
SERIALIZE(RootMotionTarget);
stream.JKEY("Buffer");
@@ -819,7 +874,9 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m
DESERIALIZE(LODBias);
DESERIALIZE(ForcedLOD);
DESERIALIZE(DrawModes);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
DESERIALIZE(ShadowsMode);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
DESERIALIZE(RootMotionTarget);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
@@ -889,7 +946,7 @@ void AnimatedModel::OnDeleteObject()
{
// Ensure this object is no longer referenced for anim update
Animations::RemoveFromUpdate(this);
ModelInstanceActor::OnDeleteObject();
}

View File

@@ -139,9 +139,10 @@ public:
/// <summary>
/// The shadows casting mode.
/// [Deprecated on 26.10.2022, expires on 26.10.2024]
/// </summary>
API_FIELD(Attributes="EditorOrder(110), DefaultValue(ShadowsCastingMode.All), EditorDisplay(\"Skinned Model\")")
ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
DEPRECATED ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
/// <summary>
/// The animation root motion apply target. If not specified the animated model will apply it itself.
@@ -358,6 +359,7 @@ public:
// [ModelInstanceActor]
bool HasContentLoaded() const override;
void Draw(RenderContext& renderContext) override;
void Draw(RenderContextBatch& renderContextBatch) override;
#if USE_EDITOR
void OnDebugDrawSelected() override;
BoundingBox GetEditorBox() const override;

View File

@@ -410,12 +410,6 @@ void SplineModel::Draw(RenderContext& renderContext)
continue;
const MaterialSlot& slot = model->MaterialSlots[mesh->GetMaterialSlotIndex()];
// Check if skip rendering
const auto shadowsMode = static_cast<ShadowsCastingMode>(entry.ShadowsMode & slot.ShadowsMode);
const auto drawModes = static_cast<DrawPass>(actorDrawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode));
if (drawModes == DrawPass::None)
continue;
// Select material
MaterialBase* material = nullptr;
if (entry.Material && entry.Material->IsLoaded())
@@ -427,6 +421,12 @@ void SplineModel::Draw(RenderContext& renderContext)
if (!material || !material->IsDeformable())
continue;
// Check if skip rendering
const auto shadowsMode = static_cast<ShadowsCastingMode>(entry.ShadowsMode & slot.ShadowsMode);
const auto drawModes = static_cast<DrawPass>(actorDrawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode) & (uint32)material->GetDrawModes());
if (drawModes == DrawPass::None)
continue;
// Submit draw call
mesh->GetDrawCallGeometry(drawCall);
drawCall.Material = material;

View File

@@ -222,6 +222,31 @@ void StaticModel::UpdateBounds()
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey);
}
void StaticModel::FlushVertexColors()
{
for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++)
{
auto& vertexColorsData = _vertexColorsData[lodIndex];
auto& vertexColorsBuffer = _vertexColorsBuffer[lodIndex];
if (vertexColorsData.HasItems())
{
const uint32 size = vertexColorsData.Count() * sizeof(Color32);
if (!vertexColorsBuffer)
vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors"));
if (vertexColorsBuffer->GetSize() != size)
{
if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(sizeof(Color32), vertexColorsData.Count())))
return;
}
GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size);
}
else
{
SAFE_DELETE_GPU_RESOURCE(vertexColorsBuffer);
}
}
}
bool StaticModel::HasContentLoaded() const
{
return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded();
@@ -229,8 +254,7 @@ bool StaticModel::HasContentLoaded() const
void StaticModel::Draw(RenderContext& renderContext)
{
const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass);
if (!Model || !Model->IsLoaded() || !Model->CanBeRendered() || drawModes == DrawPass::None)
if (!Model || !Model->IsLoaded() || !Model->CanBeRendered())
return;
if (renderContext.View.Pass == DrawPass::GlobalSDF)
{
@@ -246,32 +270,8 @@ void StaticModel::Draw(RenderContext& renderContext)
renderContext.View.GetWorldMatrix(_transform, world);
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world);
// Flush vertex colors if need to
if (_vertexColorsDirty)
{
for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++)
{
auto& vertexColorsData = _vertexColorsData[lodIndex];
auto& vertexColorsBuffer = _vertexColorsBuffer[lodIndex];
if (vertexColorsData.HasItems())
{
const uint32 size = vertexColorsData.Count() * sizeof(Color32);
if (!vertexColorsBuffer)
vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors"));
if (vertexColorsBuffer->GetSize() != size)
{
if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(sizeof(Color32), vertexColorsData.Count())))
return;
}
GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size);
}
else
{
SAFE_DELETE_GPU_RESOURCE(vertexColorsBuffer);
}
}
_vertexColorsDirty = false;
}
FlushVertexColors();
#if USE_EDITOR
// Disable motion blur effects in editor without play mode enabled to hide minor artifacts on objects moving
@@ -286,7 +286,7 @@ void StaticModel::Draw(RenderContext& renderContext)
draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex);
draw.LightmapUVs = &Lightmap.UVsArea;
draw.Flags = _staticFlags;
draw.DrawModes = drawModes;
draw.DrawModes = DrawModes;
draw.Bounds = _sphere;
draw.Bounds.Center -= renderContext.View.Origin;
draw.PerInstanceRandom = GetPerInstanceRandom();
@@ -299,6 +299,44 @@ void StaticModel::Draw(RenderContext& renderContext)
GEOMETRY_DRAW_STATE_EVENT_END(_drawState, world);
}
void StaticModel::Draw(RenderContextBatch& renderContextBatch)
{
if (!Model || !Model->IsLoaded())
return;
const RenderContext& renderContext = renderContextBatch.GetMainContext();
Matrix world;
renderContext.View.GetWorldMatrix(_transform, world);
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world);
if (_vertexColorsDirty)
FlushVertexColors();
#if USE_EDITOR
// Disable motion blur effects in editor without play mode enabled to hide minor artifacts on objects moving
if (!Editor::IsPlayMode)
_drawState.PrevWorld = world;
#endif
Mesh::DrawInfo draw;
draw.Buffer = &Entries;
draw.World = &world;
draw.DrawState = &_drawState;
draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex);
draw.LightmapUVs = &Lightmap.UVsArea;
draw.Flags = _staticFlags;
draw.DrawModes = DrawModes;
draw.Bounds = _sphere;
draw.Bounds.Center -= renderContext.View.Origin;
draw.PerInstanceRandom = GetPerInstanceRandom();
draw.LODBias = _lodBias;
draw.ForcedLOD = _forcedLod;
draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr;
Model->Draw(renderContextBatch, draw);
GEOMETRY_DRAW_STATE_EVENT_END(_drawState, world);
}
bool StaticModel::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
{
bool result = false;

View File

@@ -171,11 +171,13 @@ private:
void OnModelLoaded();
void OnModelResidencyChanged();
void UpdateBounds();
void FlushVertexColors();
public:
// [ModelInstanceActor]
bool HasContentLoaded() const override;
void Draw(RenderContext& renderContext) override;
void Draw(RenderContextBatch& renderContextBatch) override;
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;

View File

@@ -389,7 +389,7 @@ void Level::CallBeginPlay(Actor* obj)
}
}
void Level::DrawActors(RenderContext& renderContext, byte category)
void Level::DrawActors(RenderContextBatch& renderContextBatch, byte category)
{
PROFILE_CPU();
@@ -398,7 +398,7 @@ void Level::DrawActors(RenderContext& renderContext, byte category)
for (Scene* scene : Scenes)
{
if (scene->IsActiveInHierarchy())
scene->Rendering.Draw(renderContext, (SceneRendering::DrawCategory)category);
scene->Rendering.Draw(renderContextBatch, (SceneRendering::DrawCategory)category);
}
}

View File

@@ -15,6 +15,7 @@ class JsonWriter;
class Engine;
struct RenderView;
struct RenderContext;
struct RenderContextBatch;
/// <summary>
/// The scene manager that contains the loaded scenes collection and spawns/deleted actors.
@@ -163,9 +164,9 @@ public:
/// <summary>
/// Draws all the actors.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="renderContextBatch">The rendering context batch.</param>
/// <param name="category">The actors category to draw (see SceneRendering::DrawCategory).</param>
static void DrawActors(RenderContext& renderContext, byte category = 0);
static void DrawActors(RenderContextBatch& renderContextBatch, byte category = 0);
/// <summary>
/// Collects all the post fx volumes.

View File

@@ -11,6 +11,16 @@
#include "Engine/Profiler/ProfilerCPU.h"
#endif
FORCE_INLINE bool FrustumsListCull(const BoundingSphere& bounds, const BoundingFrustum* frustums, int32 frustumsCount)
{
for (int32 i = 0; i < frustumsCount; i++)
{
if (frustums[i].Intersects(bounds))
return true;
}
return false;
}
ISceneRenderingListener::~ISceneRenderingListener()
{
for (SceneRendering* scene : _scenes)
@@ -28,60 +38,90 @@ void ISceneRenderingListener::ListenSceneRendering(SceneRendering* scene)
}
}
void SceneRendering::Draw(RenderContext& renderContext, DrawCategory category)
void SceneRendering::Draw(RenderContextBatch& renderContextBatch, DrawCategory category)
{
ScopeLock lock(Locker);
auto& view = renderContext.View;
const BoundingFrustum frustum = view.CullingFrustum;
auto& view = renderContextBatch.GetMainContext().View;
const Vector3 origin = view.Origin;
renderContext.List->Scenes.Add(this);
for (auto& renderContext : renderContextBatch.Contexts)
renderContext.List->Scenes.Add(this);
auto& list = Actors[(int32)category];
// Setup frustum data
Array<BoundingFrustum, RenderListAllocation> frustumsData;
BoundingFrustum* frustums = &view.CullingFrustum;
int32 frustumsCount = renderContextBatch.Contexts.Count();
if (frustumsCount != 1)
{
frustumsData.Resize(frustumsCount);
frustums = frustumsData.Get();
for (int32 i = 0; i < frustumsCount; i++)
frustums[i] = renderContextBatch.Contexts[i].View.CullingFrustum;
}
#define CHECK_ACTOR ((view.RenderLayersMask.Mask & e.LayerMask) && (e.NoCulling || FrustumsListCull(e.Bounds, frustums, frustumsCount)))
#define CHECK_ACTOR_SINGLE_FRUSTUM ((view.RenderLayersMask.Mask & e.LayerMask) && (e.NoCulling || frustums->Intersects(e.Bounds)))
#if SCENE_RENDERING_USE_PROFILER
#define DRAW_ACTOR(mode) PROFILE_CPU_ACTOR(e.Actor); e.Actor->Draw(mode)
#else
#define DRAW_ACTOR(mode) e.Actor->Draw(mode)
#endif
// Draw all visual components
if (view.IsOfflinePass)
{
// Offline pass with additional static flags culling
for (int32 i = 0; i < list.Count(); i++)
{
auto e = list.Get()[i];
e.Bounds.Center -= origin;
if (view.RenderLayersMask.Mask & e.LayerMask && (e.NoCulling || frustum.Intersects(e.Bounds)) && e.Actor->GetStaticFlags() & view.StaticFlagsMask)
if (CHECK_ACTOR && e.Actor->GetStaticFlags() & view.StaticFlagsMask)
{
#if SCENE_RENDERING_USE_PROFILER
PROFILE_CPU_ACTOR(e.Actor);
#endif
e.Actor->Draw(renderContext);
DRAW_ACTOR(renderContextBatch);
}
}
}
else if (origin.IsZero() && frustumsCount == 1)
{
// Fast path for no origin shifting with a single context
auto& renderContext = renderContextBatch.Contexts[0];
for (int32 i = 0; i < list.Count(); i++)
{
auto e = list.Get()[i];
if (CHECK_ACTOR_SINGLE_FRUSTUM)
{
DRAW_ACTOR(renderContext);
}
}
}
else if (origin.IsZero())
{
// Fast path for no origin shifting
for (int32 i = 0; i < list.Count(); i++)
{
auto e = list.Get()[i];
if (view.RenderLayersMask.Mask & e.LayerMask && (e.NoCulling || frustum.Intersects(e.Bounds)))
if (CHECK_ACTOR)
{
#if SCENE_RENDERING_USE_PROFILER
PROFILE_CPU_ACTOR(e.Actor);
#endif
e.Actor->Draw(renderContext);
DRAW_ACTOR(renderContextBatch);
}
}
}
else
{
// Generic case
for (int32 i = 0; i < list.Count(); i++)
{
auto e = list.Get()[i];
e.Bounds.Center -= origin;
if (view.RenderLayersMask.Mask & e.LayerMask && (e.NoCulling || frustum.Intersects(e.Bounds)))
if (CHECK_ACTOR)
{
#if SCENE_RENDERING_USE_PROFILER
PROFILE_CPU_ACTOR(e.Actor);
#endif
e.Actor->Draw(renderContext);
DRAW_ACTOR(renderContextBatch);
}
}
}
#undef CHECK_ACTOR
#undef DRAW_ACTOR
#if USE_EDITOR
if (view.Pass & DrawPass::GBuffer && category == SceneDraw)
{

View File

@@ -12,6 +12,7 @@ class SceneRenderTask;
class SceneRendering;
struct PostProcessSettings;
struct RenderContext;
struct RenderContextBatch;
struct RenderView;
/// <summary>
@@ -103,9 +104,9 @@ public:
/// <summary>
/// Draws the scene. Performs the optimized actors culling and draw calls submission for the current render pass (defined by the render view).
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="renderContextBatch">The rendering context batch.</param>
/// <param name="category">The actors category to draw.</param>
void Draw(RenderContext& renderContext, DrawCategory category = DrawCategory::SceneDraw);
void Draw(RenderContextBatch& renderContextBatch, DrawCategory category = SceneDraw);
/// <summary>
/// Collects the post fx volumes for the given rendering view.