395 lines
12 KiB
C++
395 lines
12 KiB
C++
|
// Copyright 2020 Phyronnaz
|
||
|
|
||
|
#include "VoxelRendererBasicMeshHandler.h"
|
||
|
#include "VoxelRender/VoxelRenderUtilities.h"
|
||
|
#include "VoxelRender/IVoxelRenderer.h"
|
||
|
#include "VoxelRender/VoxelProceduralMeshComponent.h"
|
||
|
#include "VoxelRender/VoxelChunkMaterials.h"
|
||
|
#include "VoxelRender/VoxelChunkMesh.h"
|
||
|
#include "VoxelRender/VoxelProcMeshBuffers.h"
|
||
|
#include "VoxelDebug/VoxelDebugManager.h"
|
||
|
#include "VoxelUtilities/VoxelThreadingUtilities.h"
|
||
|
#include "IVoxelPool.h"
|
||
|
#include "VoxelAsyncWork.h"
|
||
|
|
||
|
#include "Async/Async.h"
|
||
|
|
||
|
class FVoxelBasicMeshMergeWork : public FVoxelAsyncWork
|
||
|
{
|
||
|
public:
|
||
|
static FVoxelBasicMeshMergeWork* Create(
|
||
|
FVoxelRendererBasicMeshHandler& Handler,
|
||
|
FVoxelRendererBasicMeshHandler::FChunkInfoRef ChunkInfoRef,
|
||
|
FVoxelChunkMeshesToBuild&& MeshesToBuild)
|
||
|
{
|
||
|
auto* ChunkInfo = Handler.GetChunkInfo(ChunkInfoRef);
|
||
|
check(ChunkInfo);
|
||
|
return new FVoxelBasicMeshMergeWork(
|
||
|
ChunkInfoRef,
|
||
|
ChunkInfo->Position,
|
||
|
Handler,
|
||
|
ChunkInfo->UpdateIndex.ToSharedRef(),
|
||
|
MoveTemp(MeshesToBuild));
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
const FVoxelRendererBasicMeshHandler::FChunkInfoRef ChunkInfoRef;
|
||
|
const FIntVector Position;
|
||
|
const FVoxelRendererSettingsBase RendererSettings;
|
||
|
const TVoxelWeakPtr<FVoxelRendererBasicMeshHandler> Handler;
|
||
|
|
||
|
const FVoxelChunkMeshesToBuild MeshesToBuild;
|
||
|
const TVoxelSharedRef<FThreadSafeCounter> UpdateIndexPtr;
|
||
|
const int32 UpdateIndex;
|
||
|
|
||
|
FVoxelBasicMeshMergeWork(
|
||
|
FVoxelRendererBasicMeshHandler::FChunkInfoRef Ref,
|
||
|
const FIntVector& Position,
|
||
|
FVoxelRendererBasicMeshHandler& Handler,
|
||
|
const TVoxelSharedRef<FThreadSafeCounter>& UpdateIndexPtr,
|
||
|
FVoxelChunkMeshesToBuild&& MeshesToBuild)
|
||
|
: FVoxelAsyncWork(STATIC_FNAME("FVoxelBasicMeshMergeWork"), 1e9, true)
|
||
|
, ChunkInfoRef(Ref)
|
||
|
, Position(Position)
|
||
|
, RendererSettings(static_cast<const FVoxelRendererSettingsBase&>(Handler.Renderer.Settings))
|
||
|
, Handler(StaticCastVoxelSharedRef<FVoxelRendererBasicMeshHandler>(Handler.AsShared()))
|
||
|
, MeshesToBuild(MoveTemp(MeshesToBuild))
|
||
|
, UpdateIndexPtr(UpdateIndexPtr)
|
||
|
, UpdateIndex(UpdateIndexPtr->GetValue())
|
||
|
{
|
||
|
}
|
||
|
~FVoxelBasicMeshMergeWork() = default;
|
||
|
|
||
|
virtual uint32 GetPriority() const override
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
virtual void DoWork() override
|
||
|
{
|
||
|
if (UpdateIndexPtr->GetValue() > UpdateIndex)
|
||
|
{
|
||
|
// Canceled
|
||
|
return;
|
||
|
}
|
||
|
auto BuiltMeshes = FVoxelRenderUtilities::BuildMeshes_AnyThread(MeshesToBuild, RendererSettings, Position, *UpdateIndexPtr, UpdateIndex);
|
||
|
if (!BuiltMeshes.IsValid())
|
||
|
{
|
||
|
// Canceled
|
||
|
return;
|
||
|
}
|
||
|
auto HandlerPinned = Handler.Pin();
|
||
|
if (HandlerPinned.IsValid())
|
||
|
{
|
||
|
// Queue callback
|
||
|
HandlerPinned->MeshMergeCallback(ChunkInfoRef, UpdateIndex, MoveTemp(BuiltMeshes));
|
||
|
FVoxelUtilities::DeleteOnGameThread_AnyThread(HandlerPinned);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
FVoxelRendererBasicMeshHandler::~FVoxelRendererBasicMeshHandler()
|
||
|
{
|
||
|
FlushBuiltDataQueue();
|
||
|
FlushActionQueue(MAX_dbl);
|
||
|
|
||
|
ensure(ChunkInfos.Num() == 0);
|
||
|
}
|
||
|
|
||
|
IVoxelRendererMeshHandler::FChunkId FVoxelRendererBasicMeshHandler::AddChunkImpl(int32 LOD, const FIntVector& Position)
|
||
|
{
|
||
|
return ChunkInfos.Add(FChunkInfo::Create(LOD, Position));
|
||
|
}
|
||
|
|
||
|
void FVoxelRendererBasicMeshHandler::ApplyAction(const FAction& Action)
|
||
|
{
|
||
|
VOXEL_FUNCTION_COUNTER();
|
||
|
|
||
|
switch (Action.Action)
|
||
|
{
|
||
|
case EAction::UpdateChunk:
|
||
|
{
|
||
|
check(Action.UpdateChunk().InitialCall.MainChunk);
|
||
|
const auto& MainChunk = *Action.UpdateChunk().InitialCall.MainChunk;
|
||
|
const auto* TransitionChunk = Action.UpdateChunk().InitialCall.TransitionChunk;
|
||
|
|
||
|
// This should never happen, as the chunk should be removed instead
|
||
|
ensure(!MainChunk.IsEmpty() || (TransitionChunk && !TransitionChunk->IsEmpty()));
|
||
|
|
||
|
auto& ChunkInfo = ChunkInfos[Action.ChunkId];
|
||
|
if (!ChunkInfo.Materials.IsValid())
|
||
|
{
|
||
|
ChunkInfo.Materials = MakeShared<FVoxelChunkMaterials>();
|
||
|
}
|
||
|
if (!ChunkInfo.UpdateIndex.IsValid())
|
||
|
{
|
||
|
ChunkInfo.UpdateIndex = MakeVoxelShared<FThreadSafeCounter>();
|
||
|
}
|
||
|
|
||
|
// Cancel any previous build task
|
||
|
// Note: we do not clear the built data, as it could still be used
|
||
|
// The added cost of applying the update is probably worth it compared to stalling the entire queue waiting for an update
|
||
|
ChunkInfo.UpdateIndex->Increment();
|
||
|
|
||
|
// Find the meshes to build (= copying mesh buffers to proc mesh buffers)
|
||
|
FVoxelChunkMeshesToBuild MeshesToBuild = FVoxelRenderUtilities::GetMeshesToBuild(
|
||
|
ChunkInfo.LOD,
|
||
|
ChunkInfo.Position,
|
||
|
Renderer.Settings,
|
||
|
Action.UpdateChunk().InitialCall.ChunkSettings,
|
||
|
*ChunkInfo.Materials,
|
||
|
MainChunk,
|
||
|
TransitionChunk,
|
||
|
Renderer.OnMaterialInstanceCreated,
|
||
|
ChunkInfo.DitheringInfo);
|
||
|
|
||
|
// Start a task to asynchronously build them
|
||
|
auto* Task = FVoxelBasicMeshMergeWork::Create(*this, { Action.ChunkId, ChunkInfo.UniqueId }, MoveTemp(MeshesToBuild));
|
||
|
Renderer.Settings.Pool->QueueTask(EVoxelTaskType::MeshMerge, Task);
|
||
|
|
||
|
FAction NewAction;
|
||
|
NewAction.Action = EAction::UpdateChunk;
|
||
|
NewAction.ChunkId = Action.ChunkId;
|
||
|
NewAction.UpdateChunk().AfterCall.UpdateIndex = ChunkInfo.UpdateIndex->GetValue();
|
||
|
NewAction.UpdateChunk().AfterCall.DistanceFieldVolumeData = Action.UpdateChunk().InitialCall.MainChunk->GetDistanceFieldVolumeData();
|
||
|
ActionQueue.Enqueue(NewAction);
|
||
|
|
||
|
if (Renderer.Settings.RenderType == EVoxelRenderType::SurfaceNets)
|
||
|
{
|
||
|
SetTransitionsMaskForSurfaceNets(Action.ChunkId, Action.UpdateChunk().InitialCall.ChunkSettings.TransitionsMask);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case EAction::RemoveChunk:
|
||
|
case EAction::DitherChunk:
|
||
|
case EAction::ResetDithering:
|
||
|
case EAction::SetTransitionsMaskForSurfaceNets:
|
||
|
case EAction::HideChunk:
|
||
|
case EAction::ShowChunk:
|
||
|
{
|
||
|
ActionQueue.Enqueue(Action);
|
||
|
break;
|
||
|
}
|
||
|
default: ensure(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FVoxelRendererBasicMeshHandler::ClearChunkMaterials()
|
||
|
{
|
||
|
for (auto& ChunkInfo : ChunkInfos)
|
||
|
{
|
||
|
if (ChunkInfo.Materials.IsValid())
|
||
|
{
|
||
|
ChunkInfo.Materials->Reset();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FVoxelRendererBasicMeshHandler::Tick(double MaxTime)
|
||
|
{
|
||
|
VOXEL_FUNCTION_COUNTER();
|
||
|
|
||
|
IVoxelRendererMeshHandler::Tick(MaxTime);
|
||
|
|
||
|
FlushBuiltDataQueue();
|
||
|
FlushActionQueue(MaxTime);
|
||
|
|
||
|
Renderer.Settings.DebugManager->ReportMeshActionQueueNum(ActionQueue.Num());
|
||
|
}
|
||
|
|
||
|
void FVoxelRendererBasicMeshHandler::FlushBuiltDataQueue()
|
||
|
{
|
||
|
VOXEL_FUNCTION_COUNTER();
|
||
|
|
||
|
// Copy built data from async task callbacks to the chunk infos
|
||
|
// Should be fast enough to not require checking the time
|
||
|
FBuildCallback Callback;
|
||
|
while (CallbackQueue.Dequeue(Callback))
|
||
|
{
|
||
|
auto& BuiltData = Callback.BuiltData;
|
||
|
|
||
|
if (!ensure(BuiltData.BuiltMeshes.IsValid())) continue;
|
||
|
|
||
|
auto* ChunkInfo = GetChunkInfo(Callback.ChunkInfoRef);
|
||
|
if (ChunkInfo && BuiltData.UpdateIndex >= ChunkInfo->UpdateIndex->GetValue())
|
||
|
{
|
||
|
// Not outdated
|
||
|
ensure(BuiltData.UpdateIndex == ChunkInfo->UpdateIndex->GetValue());
|
||
|
ChunkInfo->BuiltData = MoveTemp(BuiltData);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FVoxelRendererBasicMeshHandler::FlushActionQueue(double MaxTime)
|
||
|
{
|
||
|
VOXEL_FUNCTION_COUNTER();
|
||
|
|
||
|
FAction Action;
|
||
|
// Peek: if UpdateChunk isn't ready yet we don't want to pop the action
|
||
|
// Always process dithering in immediately, as else the chunk will be showed until the next tick and then hidden (one frame glitch)
|
||
|
while (ActionQueue.Peek(Action) &&
|
||
|
(FPlatformTime::Seconds() < MaxTime ||
|
||
|
(Action.Action == EAction::DitherChunk && Action.DitherChunk().DitheringType == EDitheringType::Classic_DitherIn)))
|
||
|
{
|
||
|
auto& ChunkInfo = ChunkInfos[Action.ChunkId];
|
||
|
CleanUp(ChunkInfo.Meshes);
|
||
|
|
||
|
if (IsDestroying() && Action.Action != EAction::RemoveChunk)
|
||
|
{
|
||
|
ActionQueue.Pop();
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
switch (Action.Action)
|
||
|
{
|
||
|
case EAction::UpdateChunk:
|
||
|
{
|
||
|
const int32 WantedUpdateIndex = Action.UpdateChunk().AfterCall.UpdateIndex;
|
||
|
if (ChunkInfo.MeshUpdateIndex >= WantedUpdateIndex)
|
||
|
{
|
||
|
// Already updated
|
||
|
// This happens when a previous UpdateChunk used the built data we triggered
|
||
|
break;
|
||
|
}
|
||
|
if (WantedUpdateIndex > ChunkInfo.BuiltData.UpdateIndex)
|
||
|
{
|
||
|
// Not built yet, wait
|
||
|
|
||
|
if (ChunkInfo.BuiltData.UpdateIndex != -1)
|
||
|
{
|
||
|
// Stored built data is outdated, clear it to save memory
|
||
|
ensure(ChunkInfo.BuiltData.BuiltMeshes.IsValid());
|
||
|
ChunkInfo.BuiltData.BuiltMeshes.Reset();
|
||
|
ChunkInfo.BuiltData.UpdateIndex = -1;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Move to clear the built data value
|
||
|
const auto BuiltMeshes = MoveTemp(ChunkInfo.BuiltData.BuiltMeshes);
|
||
|
ChunkInfo.MeshUpdateIndex = ChunkInfo.BuiltData.UpdateIndex;
|
||
|
ChunkInfo.BuiltData.UpdateIndex = -1;
|
||
|
|
||
|
if (!ensure(BuiltMeshes.IsValid())) continue;
|
||
|
|
||
|
int32 MeshIndex = 0;
|
||
|
// Apply built meshes
|
||
|
for (auto& BuiltMesh : *BuiltMeshes)
|
||
|
{
|
||
|
const FVoxelMeshConfig& MeshConfig = BuiltMesh.Key;
|
||
|
if (ChunkInfo.Meshes.Num() <= MeshIndex)
|
||
|
{
|
||
|
// Not enough meshes to render the built mesh, allocate new ones
|
||
|
auto* NewMesh = GetNewMesh(Action.ChunkId, ChunkInfo.Position, ChunkInfo.LOD);
|
||
|
if (!ensureVoxelSlow(NewMesh)) return;
|
||
|
ChunkInfo.Meshes.Add(NewMesh);
|
||
|
}
|
||
|
|
||
|
auto& Mesh = *ChunkInfo.Meshes[MeshIndex];
|
||
|
MeshConfig.ApplyTo(Mesh);
|
||
|
|
||
|
Mesh.SetDistanceFieldData(nullptr);
|
||
|
Mesh.ClearSections(EVoxelProcMeshSectionUpdate::DelayUpdate);
|
||
|
for (auto& Section : BuiltMesh.Value)
|
||
|
{
|
||
|
if (!ensure(Section.Value.IsValid())) continue;
|
||
|
Mesh.AddProcMeshSection(Section.Key, MoveTemp(Section.Value), EVoxelProcMeshSectionUpdate::DelayUpdate);
|
||
|
}
|
||
|
Mesh.FinishSectionsUpdates();
|
||
|
|
||
|
MeshIndex++;
|
||
|
}
|
||
|
|
||
|
// Clear unused meshes
|
||
|
for (; MeshIndex < ChunkInfo.Meshes.Num(); MeshIndex++)
|
||
|
{
|
||
|
auto& Mesh = *ChunkInfo.Meshes[MeshIndex];
|
||
|
Mesh.SetDistanceFieldData(nullptr);
|
||
|
Mesh.ClearSections(EVoxelProcMeshSectionUpdate::UpdateNow);
|
||
|
}
|
||
|
|
||
|
// Handle distance fields
|
||
|
const auto& DistanceFieldVolumeData = Action.UpdateChunk().AfterCall.DistanceFieldVolumeData;
|
||
|
if (DistanceFieldVolumeData.IsValid())
|
||
|
{
|
||
|
// Use the first mesh to hold the distance field data
|
||
|
// We should always have at least one mesh, else the chunk should have been removed instead of updated
|
||
|
if (ensure(ChunkInfo.Meshes.Num() > 0))
|
||
|
{
|
||
|
ChunkInfo.Meshes[0]->SetDistanceFieldData(DistanceFieldVolumeData);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case EAction::RemoveChunk:
|
||
|
{
|
||
|
for (auto& Mesh : ChunkInfo.Meshes)
|
||
|
{
|
||
|
RemoveMesh(*Mesh);
|
||
|
}
|
||
|
ChunkInfos.RemoveAt(Action.ChunkId);
|
||
|
break;
|
||
|
}
|
||
|
case EAction::DitherChunk:
|
||
|
{
|
||
|
ChunkInfo.DitheringInfo.bIsValid = true;
|
||
|
ChunkInfo.DitheringInfo.DitheringType = Action.DitherChunk().DitheringType;
|
||
|
ChunkInfo.DitheringInfo.Time = FVoxelRenderUtilities::GetWorldCurrentTime(Renderer.Settings.World.Get());
|
||
|
for (auto& Mesh : ChunkInfo.Meshes)
|
||
|
{
|
||
|
FVoxelRenderUtilities::StartMeshDithering(
|
||
|
*Mesh,
|
||
|
Renderer.Settings,
|
||
|
ChunkInfo.DitheringInfo);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case EAction::ResetDithering:
|
||
|
{
|
||
|
ChunkInfo.DitheringInfo.bIsValid = false;
|
||
|
for (auto& Mesh : ChunkInfo.Meshes)
|
||
|
{
|
||
|
FVoxelRenderUtilities::ResetDithering(*Mesh, Renderer.Settings);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case EAction::SetTransitionsMaskForSurfaceNets:
|
||
|
{
|
||
|
for (auto& Mesh : ChunkInfo.Meshes)
|
||
|
{
|
||
|
FVoxelRenderUtilities::SetMeshTransitionsMask(*Mesh, Action.SetTransitionsMaskForSurfaceNets().TransitionsMask);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case EAction::HideChunk:
|
||
|
{
|
||
|
for (auto& Mesh : ChunkInfo.Meshes)
|
||
|
{
|
||
|
FVoxelRenderUtilities::HideMesh(*Mesh);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case EAction::ShowChunk:
|
||
|
{
|
||
|
for (auto& Mesh : ChunkInfo.Meshes)
|
||
|
{
|
||
|
FVoxelRenderUtilities::ShowMesh(*Mesh);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
default: ensure(false);
|
||
|
}
|
||
|
|
||
|
if (CVarLogActionQueue.GetValueOnGameThread() != 0)
|
||
|
{
|
||
|
LOG_VOXEL(Log, TEXT("ActionQueue: LOD: %d; %s; Position: %s"), ChunkInfo.LOD, *Action.ToString(), *ChunkInfo.Position.ToString());
|
||
|
}
|
||
|
|
||
|
ActionQueue.Pop();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FVoxelRendererBasicMeshHandler::MeshMergeCallback(FChunkInfoRef ChunkInfoRef, int32 UpdateIndex, TUniquePtr<FVoxelBuiltChunkMeshes> BuiltMeshes)
|
||
|
{
|
||
|
CallbackQueue.Enqueue({ ChunkInfoRef, FChunkBuiltData{ UpdateIndex, MoveTemp(BuiltMeshes) } });
|
||
|
}
|