|
|
|
|
@@ -1,11 +1,13 @@
|
|
|
|
|
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
|
|
|
|
|
|
|
|
|
#include "Particles.h"
|
|
|
|
|
#include "ParticleEffect.h"
|
|
|
|
|
#include "Engine/Content/Assets/Model.h"
|
|
|
|
|
#include "Engine/Core/Collections/Sorting.h"
|
|
|
|
|
#include "Engine/Core/Collections/HashSet.h"
|
|
|
|
|
#include "Engine/Engine/EngineService.h"
|
|
|
|
|
#include "Engine/Engine/Time.h"
|
|
|
|
|
#include "Engine/Engine/Engine.h"
|
|
|
|
|
#include "Engine/Graphics/GPUBuffer.h"
|
|
|
|
|
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
|
|
|
|
#include "Engine/Graphics/RenderTask.h"
|
|
|
|
|
@@ -13,7 +15,7 @@
|
|
|
|
|
#include "Engine/Profiler/ProfilerCPU.h"
|
|
|
|
|
#include "Engine/Renderer/DrawCall.h"
|
|
|
|
|
#include "Engine/Renderer/RenderList.h"
|
|
|
|
|
#include "ParticleEffect.h"
|
|
|
|
|
#include "Engine/Threading/TaskGraph.h"
|
|
|
|
|
#if COMPILE_WITH_GPU_PARTICLES
|
|
|
|
|
#include "Engine/Content/Assets/Shader.h"
|
|
|
|
|
#include "Engine/Profiler/ProfilerGPU.h"
|
|
|
|
|
@@ -46,10 +48,8 @@ public:
|
|
|
|
|
{
|
|
|
|
|
if (VB)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
VB = GPUDevice::Instance->CreateBuffer(TEXT("SpriteParticleRenderer,VB"));
|
|
|
|
|
IB = GPUDevice::Instance->CreateBuffer(TEXT("SpriteParticleRenderer.IB"));
|
|
|
|
|
|
|
|
|
|
static SpriteParticleVertex vertexBuffer[] =
|
|
|
|
|
{
|
|
|
|
|
{ -0.5f, -0.5f, 0.0f, 0.0f },
|
|
|
|
|
@@ -57,20 +57,16 @@ public:
|
|
|
|
|
{ +0.5f, +0.5f, 1.0f, 1.0f },
|
|
|
|
|
{ -0.5f, +0.5f, 0.0f, 1.0f },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static uint16 indexBuffer[] =
|
|
|
|
|
{
|
|
|
|
|
0,
|
|
|
|
|
1,
|
|
|
|
|
2,
|
|
|
|
|
|
|
|
|
|
0,
|
|
|
|
|
2,
|
|
|
|
|
3,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return VB->Init(GPUBufferDescription::Vertex(sizeof(SpriteParticleVertex), VertexCount, vertexBuffer)) ||
|
|
|
|
|
IB->Init(GPUBufferDescription::Index(sizeof(uint16), IndexCount, indexBuffer));
|
|
|
|
|
return VB->Init(GPUBufferDescription::Vertex(sizeof(SpriteParticleVertex), VertexCount, vertexBuffer)) || IB->Init(GPUBufferDescription::Index(sizeof(uint16), IndexCount, indexBuffer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Dispose()
|
|
|
|
|
@@ -103,15 +99,17 @@ namespace ParticleManagerImpl
|
|
|
|
|
{
|
|
|
|
|
CriticalSection PoolLocker;
|
|
|
|
|
Dictionary<ParticleEmitter*, Array<EmitterCache>> Pool;
|
|
|
|
|
HashSet<ParticleEffect*> UpdateList(256);
|
|
|
|
|
Array<ParticleEffect*> UpdateList;
|
|
|
|
|
#if COMPILE_WITH_GPU_PARTICLES
|
|
|
|
|
HashSet<ParticleEffect*> GpuUpdateList(256);
|
|
|
|
|
CriticalSection GpuUpdateListLocker;
|
|
|
|
|
Array<ParticleEffect*> GpuUpdateList;
|
|
|
|
|
RenderTask* GpuRenderTask = nullptr;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using namespace ParticleManagerImpl;
|
|
|
|
|
|
|
|
|
|
TaskGraphSystem* Particles::System = nullptr;
|
|
|
|
|
bool Particles::EnableParticleBufferPooling = true;
|
|
|
|
|
float Particles::ParticleBufferRecycleTimeout = 10.0f;
|
|
|
|
|
|
|
|
|
|
@@ -149,14 +147,24 @@ public:
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Update() override;
|
|
|
|
|
bool Init() override;
|
|
|
|
|
void Dispose() override;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class ParticlesSystem : public TaskGraphSystem
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
float DeltaTime, UnscaledDeltaTime, Time, UnscaledTime;
|
|
|
|
|
void Job(int32 index);
|
|
|
|
|
void Execute(TaskGraph* graph) override;
|
|
|
|
|
void PostExecute(TaskGraph* graph) override;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ParticleManagerService ParticleManagerServiceInstance;
|
|
|
|
|
|
|
|
|
|
void Particles::UpdateEffect(ParticleEffect* effect)
|
|
|
|
|
{
|
|
|
|
|
ASSERT_LOW_LAYER(!UpdateList.Contains(effect));
|
|
|
|
|
UpdateList.Add(effect);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1027,14 +1035,14 @@ void Particles::DrawParticles(RenderContext& renderContext, ParticleEffect* effe
|
|
|
|
|
|
|
|
|
|
void UpdateGPU(RenderTask* task, GPUContext* context)
|
|
|
|
|
{
|
|
|
|
|
ScopeLock lock(GpuUpdateListLocker);
|
|
|
|
|
if (GpuUpdateList.IsEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
PROFILE_GPU("GPU Particles");
|
|
|
|
|
|
|
|
|
|
for (auto i = GpuUpdateList.Begin(); i.IsNotEnd(); ++i)
|
|
|
|
|
for (ParticleEffect* effect : GpuUpdateList)
|
|
|
|
|
{
|
|
|
|
|
ParticleEffect* effect = i->Item;
|
|
|
|
|
auto& instance = effect->Instance;
|
|
|
|
|
const auto particleSystem = effect->ParticleSystem.Get();
|
|
|
|
|
if (!particleSystem || !particleSystem->IsLoaded())
|
|
|
|
|
@@ -1155,218 +1163,22 @@ void Particles::OnEmitterUnload(ParticleEmitter* emitter)
|
|
|
|
|
PoolLocker.Unlock();
|
|
|
|
|
|
|
|
|
|
#if COMPILE_WITH_GPU_PARTICLES
|
|
|
|
|
for (auto i = GpuUpdateList.Begin(); i.IsNotEnd(); ++i)
|
|
|
|
|
GpuUpdateListLocker.Lock();
|
|
|
|
|
for (int32 i = GpuUpdateList.Count() - 1; i >= 0; i--)
|
|
|
|
|
{
|
|
|
|
|
if (i->Item->Instance.ContainsEmitter(emitter))
|
|
|
|
|
GpuUpdateList.Remove(i);
|
|
|
|
|
if (GpuUpdateList[i]->Instance.ContainsEmitter(emitter))
|
|
|
|
|
GpuUpdateList.RemoveAt(i);
|
|
|
|
|
}
|
|
|
|
|
GpuUpdateListLocker.Unlock();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParticleManagerService::Update()
|
|
|
|
|
bool ParticleManagerService::Init()
|
|
|
|
|
{
|
|
|
|
|
PROFILE_CPU_NAMED("Particles");
|
|
|
|
|
|
|
|
|
|
// TODO: implement the thread jobs pipeline to run set of tasks at once (use it for multi threaded rendering and animations evaluation and CPU particles simulation)
|
|
|
|
|
|
|
|
|
|
const auto timeSeconds = Platform::GetTimeSeconds();
|
|
|
|
|
const auto& tickData = Time::Update;
|
|
|
|
|
const float deltaTimeUnscaled = tickData.UnscaledDeltaTime.GetTotalSeconds();
|
|
|
|
|
const float timeUnscaled = tickData.UnscaledTime.GetTotalSeconds();
|
|
|
|
|
const float deltaTime = tickData.DeltaTime.GetTotalSeconds();
|
|
|
|
|
const float time = tickData.Time.GetTotalSeconds();
|
|
|
|
|
|
|
|
|
|
// Update particle effects
|
|
|
|
|
for (auto i = UpdateList.Begin(); i.IsNotEnd(); ++i)
|
|
|
|
|
{
|
|
|
|
|
ParticleEffect* effect = i->Item;
|
|
|
|
|
auto& instance = effect->Instance;
|
|
|
|
|
const auto particleSystem = effect->ParticleSystem.Get();
|
|
|
|
|
if (!particleSystem || !particleSystem->IsLoaded())
|
|
|
|
|
continue;
|
|
|
|
|
bool anyEmitterNotReady = false;
|
|
|
|
|
for (int32 j = 0; j < particleSystem->Tracks.Count(); j++)
|
|
|
|
|
{
|
|
|
|
|
const auto& track = particleSystem->Tracks[j];
|
|
|
|
|
if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled)
|
|
|
|
|
continue;
|
|
|
|
|
auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get();
|
|
|
|
|
if (!emitter || !emitter->IsLoaded())
|
|
|
|
|
{
|
|
|
|
|
anyEmitterNotReady = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (anyEmitterNotReady)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
#if USE_EDITOR
|
|
|
|
|
// Lock in editor only (more reloads during asset live editing)
|
|
|
|
|
ScopeLock lock(particleSystem->Locker);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Prepare instance data
|
|
|
|
|
instance.Sync(particleSystem);
|
|
|
|
|
|
|
|
|
|
bool updateBounds = false;
|
|
|
|
|
bool updateGpu = false;
|
|
|
|
|
|
|
|
|
|
// Simulation delta time can be based on a time since last update or the current delta time
|
|
|
|
|
float dt = effect->UseTimeScale ? deltaTime : deltaTimeUnscaled;
|
|
|
|
|
float t = effect->UseTimeScale ? time : timeUnscaled;
|
|
|
|
|
#if USE_EDITOR
|
|
|
|
|
if (!Editor::IsPlayMode)
|
|
|
|
|
{
|
|
|
|
|
dt = deltaTimeUnscaled;
|
|
|
|
|
t = timeUnscaled;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
const float lastUpdateTime = instance.LastUpdateTime;
|
|
|
|
|
if (lastUpdateTime > 0 && t > lastUpdateTime)
|
|
|
|
|
{
|
|
|
|
|
dt = t - lastUpdateTime;
|
|
|
|
|
}
|
|
|
|
|
else if (lastUpdateTime < 0)
|
|
|
|
|
{
|
|
|
|
|
// Update bounds after first system update
|
|
|
|
|
updateBounds = true;
|
|
|
|
|
}
|
|
|
|
|
// TODO: if using fixed timestep quantize the dt and accumulate remaining part for the next update?
|
|
|
|
|
if (dt <= 1.0f / 240.0f)
|
|
|
|
|
continue;
|
|
|
|
|
dt *= effect->SimulationSpeed;
|
|
|
|
|
instance.Time += dt;
|
|
|
|
|
const float fps = particleSystem->FramesPerSecond;
|
|
|
|
|
const float duration = particleSystem->DurationFrames / fps;
|
|
|
|
|
if (instance.Time > duration)
|
|
|
|
|
{
|
|
|
|
|
if (effect->IsLooping)
|
|
|
|
|
{
|
|
|
|
|
// Loop
|
|
|
|
|
// TODO: accumulate (duration - instance.Time) into next update dt
|
|
|
|
|
instance.Time = 0;
|
|
|
|
|
for (int32 j = 0; j < instance.Emitters.Count(); j++)
|
|
|
|
|
{
|
|
|
|
|
auto& e = instance.Emitters[j];
|
|
|
|
|
e.Time = 0;
|
|
|
|
|
for (auto& s : e.SpawnModulesData)
|
|
|
|
|
{
|
|
|
|
|
s.NextSpawnTime = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// End
|
|
|
|
|
instance.Time = duration;
|
|
|
|
|
for (auto& emitterInstance : instance.Emitters)
|
|
|
|
|
{
|
|
|
|
|
if (emitterInstance.Buffer)
|
|
|
|
|
{
|
|
|
|
|
Particles::RecycleParticleBuffer(emitterInstance.Buffer);
|
|
|
|
|
emitterInstance.Buffer = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
instance.LastUpdateTime = t;
|
|
|
|
|
|
|
|
|
|
// Update all emitter tracks
|
|
|
|
|
for (int32 j = 0; j < particleSystem->Tracks.Count(); j++)
|
|
|
|
|
{
|
|
|
|
|
const auto& track = particleSystem->Tracks[j];
|
|
|
|
|
if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled)
|
|
|
|
|
continue;
|
|
|
|
|
auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get();
|
|
|
|
|
auto& data = instance.Emitters[track.AsEmitter.Index];
|
|
|
|
|
ASSERT(emitter && emitter->IsLoaded());
|
|
|
|
|
ASSERT(emitter->Capacity != 0 && emitter->Graph.Layout.Size != 0);
|
|
|
|
|
|
|
|
|
|
// Calculate new time position
|
|
|
|
|
const float startTime = track.AsEmitter.StartFrame / fps;
|
|
|
|
|
const float durationTime = track.AsEmitter.DurationFrames / fps;
|
|
|
|
|
const bool canSpawn = startTime <= instance.Time && instance.Time <= startTime + durationTime;
|
|
|
|
|
|
|
|
|
|
// Update instance data
|
|
|
|
|
data.Sync(effect->Instance, particleSystem, track.AsEmitter.Index);
|
|
|
|
|
if (!data.Buffer)
|
|
|
|
|
{
|
|
|
|
|
data.Buffer = Particles::AcquireParticleBuffer(emitter);
|
|
|
|
|
}
|
|
|
|
|
data.Time += dt;
|
|
|
|
|
|
|
|
|
|
// Update particles simulation
|
|
|
|
|
switch (emitter->SimulationMode)
|
|
|
|
|
{
|
|
|
|
|
case ParticlesSimulationMode::CPU:
|
|
|
|
|
emitter->GraphExecutorCPU.Update(emitter, effect, data, dt, canSpawn);
|
|
|
|
|
updateBounds |= emitter->UseAutoBounds;
|
|
|
|
|
break;
|
|
|
|
|
#if COMPILE_WITH_GPU_PARTICLES
|
|
|
|
|
case ParticlesSimulationMode::GPU:
|
|
|
|
|
emitter->GPU.Update(emitter, effect, data, dt, canSpawn);
|
|
|
|
|
updateGpu = true;
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
default:
|
|
|
|
|
CRASH;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update bounds if any of the emitters uses auto-bounds
|
|
|
|
|
if (updateBounds)
|
|
|
|
|
{
|
|
|
|
|
effect->UpdateBounds();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if COMPILE_WITH_GPU_PARTICLES
|
|
|
|
|
// Register for GPU update
|
|
|
|
|
if (updateGpu)
|
|
|
|
|
{
|
|
|
|
|
GpuUpdateList.Add(effect);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
UpdateList.Clear();
|
|
|
|
|
|
|
|
|
|
#if COMPILE_WITH_GPU_PARTICLES
|
|
|
|
|
// Create GPU render task if missing but required
|
|
|
|
|
if (GpuUpdateList.HasItems() && !GpuRenderTask)
|
|
|
|
|
{
|
|
|
|
|
GpuRenderTask = New<RenderTask>();
|
|
|
|
|
GpuRenderTask->Order = -10000000;
|
|
|
|
|
GpuRenderTask->Render.Bind(UpdateGPU);
|
|
|
|
|
ScopeLock lock(RenderTask::TasksLocker);
|
|
|
|
|
RenderTask::Tasks.Add(GpuRenderTask);
|
|
|
|
|
}
|
|
|
|
|
else if (GpuRenderTask)
|
|
|
|
|
{
|
|
|
|
|
ScopeLock lock(RenderTask::TasksLocker);
|
|
|
|
|
GpuRenderTask->Enabled = GpuUpdateList.HasItems();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Recycle buffers
|
|
|
|
|
PoolLocker.Lock();
|
|
|
|
|
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
|
|
|
|
|
{
|
|
|
|
|
auto& entries = i->Value;
|
|
|
|
|
for (int32 j = 0; j < entries.Count(); j++)
|
|
|
|
|
{
|
|
|
|
|
auto& e = entries[j];
|
|
|
|
|
if (timeSeconds - e.LastTimeUsed >= Particles::ParticleBufferRecycleTimeout)
|
|
|
|
|
{
|
|
|
|
|
Delete(e.Buffer);
|
|
|
|
|
entries.RemoveAt(j--);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (entries.IsEmpty())
|
|
|
|
|
Pool.Remove(i);
|
|
|
|
|
}
|
|
|
|
|
PoolLocker.Unlock();
|
|
|
|
|
Particles::System = New<ParticlesSystem>();
|
|
|
|
|
Particles::System->Order = 10000;
|
|
|
|
|
Engine::UpdateGraph->AddSystem(Particles::System);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParticleManagerService::Dispose()
|
|
|
|
|
@@ -1401,4 +1213,215 @@ void ParticleManagerService::Dispose()
|
|
|
|
|
PoolLocker.Unlock();
|
|
|
|
|
|
|
|
|
|
SpriteRenderer.Dispose();
|
|
|
|
|
SAFE_DELETE(Particles::System);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParticlesSystem::Job(int32 index)
|
|
|
|
|
{
|
|
|
|
|
PROFILE_CPU_NAMED("Particles.Job");
|
|
|
|
|
auto effect = UpdateList[index];
|
|
|
|
|
auto& instance = effect->Instance;
|
|
|
|
|
const auto particleSystem = effect->ParticleSystem.Get();
|
|
|
|
|
if (!particleSystem || !particleSystem->IsLoaded())
|
|
|
|
|
return;
|
|
|
|
|
bool anyEmitterNotReady = false;
|
|
|
|
|
for (int32 j = 0; j < particleSystem->Tracks.Count(); j++)
|
|
|
|
|
{
|
|
|
|
|
const auto& track = particleSystem->Tracks[j];
|
|
|
|
|
if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled)
|
|
|
|
|
continue;
|
|
|
|
|
auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get();
|
|
|
|
|
if (!emitter || !emitter->IsLoaded())
|
|
|
|
|
{
|
|
|
|
|
anyEmitterNotReady = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (anyEmitterNotReady)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Prepare instance data
|
|
|
|
|
instance.Sync(particleSystem);
|
|
|
|
|
|
|
|
|
|
bool updateBounds = false;
|
|
|
|
|
bool updateGpu = false;
|
|
|
|
|
|
|
|
|
|
// Simulation delta time can be based on a time since last update or the current delta time
|
|
|
|
|
bool useTimeScale = effect->UseTimeScale;
|
|
|
|
|
#if USE_EDITOR
|
|
|
|
|
if (!Editor::IsPlayMode)
|
|
|
|
|
useTimeScale = false;
|
|
|
|
|
#endif
|
|
|
|
|
float dt = useTimeScale ? DeltaTime : UnscaledDeltaTime;
|
|
|
|
|
float t = useTimeScale ? Time : UnscaledTime;
|
|
|
|
|
const float lastUpdateTime = instance.LastUpdateTime;
|
|
|
|
|
if (lastUpdateTime > 0 && t > lastUpdateTime)
|
|
|
|
|
{
|
|
|
|
|
dt = t - lastUpdateTime;
|
|
|
|
|
}
|
|
|
|
|
else if (lastUpdateTime < 0)
|
|
|
|
|
{
|
|
|
|
|
// Update bounds after first system update
|
|
|
|
|
updateBounds = true;
|
|
|
|
|
}
|
|
|
|
|
// TODO: if using fixed timestep quantize the dt and accumulate remaining part for the next update?
|
|
|
|
|
if (dt <= 1.0f / 240.0f)
|
|
|
|
|
return;
|
|
|
|
|
dt *= effect->SimulationSpeed;
|
|
|
|
|
instance.Time += dt;
|
|
|
|
|
const float fps = particleSystem->FramesPerSecond;
|
|
|
|
|
const float duration = (float)particleSystem->DurationFrames / fps;
|
|
|
|
|
if (instance.Time > duration)
|
|
|
|
|
{
|
|
|
|
|
if (effect->IsLooping)
|
|
|
|
|
{
|
|
|
|
|
// Loop
|
|
|
|
|
// TODO: accumulate (duration - instance.Time) into next update dt
|
|
|
|
|
instance.Time = 0;
|
|
|
|
|
for (int32 j = 0; j < instance.Emitters.Count(); j++)
|
|
|
|
|
{
|
|
|
|
|
auto& e = instance.Emitters[j];
|
|
|
|
|
e.Time = 0;
|
|
|
|
|
for (auto& s : e.SpawnModulesData)
|
|
|
|
|
{
|
|
|
|
|
s.NextSpawnTime = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// End
|
|
|
|
|
instance.Time = duration;
|
|
|
|
|
for (auto& emitterInstance : instance.Emitters)
|
|
|
|
|
{
|
|
|
|
|
if (emitterInstance.Buffer)
|
|
|
|
|
{
|
|
|
|
|
Particles::RecycleParticleBuffer(emitterInstance.Buffer);
|
|
|
|
|
emitterInstance.Buffer = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
instance.LastUpdateTime = t;
|
|
|
|
|
|
|
|
|
|
// Update all emitter tracks
|
|
|
|
|
for (int32 j = 0; j < particleSystem->Tracks.Count(); j++)
|
|
|
|
|
{
|
|
|
|
|
const auto& track = particleSystem->Tracks[j];
|
|
|
|
|
if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled)
|
|
|
|
|
continue;
|
|
|
|
|
auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get();
|
|
|
|
|
auto& data = instance.Emitters[track.AsEmitter.Index];
|
|
|
|
|
ASSERT(emitter && emitter->IsLoaded());
|
|
|
|
|
ASSERT(emitter->Capacity != 0 && emitter->Graph.Layout.Size != 0);
|
|
|
|
|
|
|
|
|
|
// Calculate new time position
|
|
|
|
|
const float startTime = (float)track.AsEmitter.StartFrame / fps;
|
|
|
|
|
const float durationTime = (float)track.AsEmitter.DurationFrames / fps;
|
|
|
|
|
const bool canSpawn = startTime <= instance.Time && instance.Time <= startTime + durationTime;
|
|
|
|
|
|
|
|
|
|
// Update instance data
|
|
|
|
|
data.Sync(effect->Instance, particleSystem, track.AsEmitter.Index);
|
|
|
|
|
if (!data.Buffer)
|
|
|
|
|
{
|
|
|
|
|
data.Buffer = Particles::AcquireParticleBuffer(emitter);
|
|
|
|
|
}
|
|
|
|
|
data.Time += dt;
|
|
|
|
|
|
|
|
|
|
// Update particles simulation
|
|
|
|
|
switch (emitter->SimulationMode)
|
|
|
|
|
{
|
|
|
|
|
case ParticlesSimulationMode::CPU:
|
|
|
|
|
emitter->GraphExecutorCPU.Update(emitter, effect, data, dt, canSpawn);
|
|
|
|
|
updateBounds |= emitter->UseAutoBounds;
|
|
|
|
|
break;
|
|
|
|
|
#if COMPILE_WITH_GPU_PARTICLES
|
|
|
|
|
case ParticlesSimulationMode::GPU:
|
|
|
|
|
emitter->GPU.Update(emitter, effect, data, dt, canSpawn);
|
|
|
|
|
updateGpu = true;
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update bounds if any of the emitters uses auto-bounds
|
|
|
|
|
if (updateBounds)
|
|
|
|
|
{
|
|
|
|
|
effect->UpdateBounds();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if COMPILE_WITH_GPU_PARTICLES
|
|
|
|
|
// Register for GPU update
|
|
|
|
|
if (updateGpu)
|
|
|
|
|
{
|
|
|
|
|
ScopeLock lock(GpuUpdateListLocker);
|
|
|
|
|
GpuUpdateList.Add(effect);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParticlesSystem::Execute(TaskGraph* graph)
|
|
|
|
|
{
|
|
|
|
|
if (UpdateList.Count() == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Setup data for async update
|
|
|
|
|
const auto& tickData = Time::Update;
|
|
|
|
|
DeltaTime = tickData.DeltaTime.GetTotalSeconds();
|
|
|
|
|
UnscaledDeltaTime = tickData.UnscaledDeltaTime.GetTotalSeconds();
|
|
|
|
|
Time = tickData.Time.GetTotalSeconds();
|
|
|
|
|
UnscaledTime = tickData.UnscaledTime.GetTotalSeconds();
|
|
|
|
|
|
|
|
|
|
// Schedule work to update all particles in async
|
|
|
|
|
Function<void(int32)> job;
|
|
|
|
|
job.Bind<ParticlesSystem, &ParticlesSystem::Job>(this);
|
|
|
|
|
graph->DispatchJob(job, UpdateList.Count());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParticlesSystem::PostExecute(TaskGraph* graph)
|
|
|
|
|
{
|
|
|
|
|
PROFILE_CPU_NAMED("Particles.PostExecute");
|
|
|
|
|
|
|
|
|
|
UpdateList.Clear();
|
|
|
|
|
|
|
|
|
|
#if COMPILE_WITH_GPU_PARTICLES
|
|
|
|
|
// Create GPU render task if missing but required
|
|
|
|
|
if (GpuUpdateList.HasItems() && !GpuRenderTask)
|
|
|
|
|
{
|
|
|
|
|
GpuRenderTask = New<RenderTask>();
|
|
|
|
|
GpuRenderTask->Order = -10000000;
|
|
|
|
|
GpuRenderTask->Render.Bind(UpdateGPU);
|
|
|
|
|
ScopeLock lock(RenderTask::TasksLocker);
|
|
|
|
|
RenderTask::Tasks.Add(GpuRenderTask);
|
|
|
|
|
}
|
|
|
|
|
else if (GpuRenderTask)
|
|
|
|
|
{
|
|
|
|
|
ScopeLock lock(RenderTask::TasksLocker);
|
|
|
|
|
GpuRenderTask->Enabled = GpuUpdateList.HasItems();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Recycle buffers
|
|
|
|
|
const auto timeSeconds = Platform::GetTimeSeconds();
|
|
|
|
|
PoolLocker.Lock();
|
|
|
|
|
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
|
|
|
|
|
{
|
|
|
|
|
auto& entries = i->Value;
|
|
|
|
|
for (int32 j = 0; j < entries.Count(); j++)
|
|
|
|
|
{
|
|
|
|
|
auto& e = entries[j];
|
|
|
|
|
if (timeSeconds - e.LastTimeUsed >= Particles::ParticleBufferRecycleTimeout)
|
|
|
|
|
{
|
|
|
|
|
Delete(e.Buffer);
|
|
|
|
|
entries.RemoveAt(j--);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (entries.IsEmpty())
|
|
|
|
|
Pool.Remove(i);
|
|
|
|
|
}
|
|
|
|
|
PoolLocker.Unlock();
|
|
|
|
|
}
|
|
|
|
|
|