Files
FlaxEngine/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp
2022-01-14 13:31:12 +01:00

285 lines
9.7 KiB
C++

// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_GPU_PARTICLES
#include "GPUParticles.h"
#include "Engine/Particles/ParticleEmitter.h"
#include "Engine/Particles/ParticleEffect.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUBuffer.h"
#include "Engine/Graphics/Shaders/GPUShader.h"
#include "Engine/Graphics/Shaders/GPUConstantBuffer.h"
PACK_STRUCT(struct GPUParticlesData {
Matrix ViewProjectionMatrix;
Matrix InvViewProjectionMatrix;
Matrix InvViewMatrix;
Matrix ViewMatrix;
Matrix WorldMatrix;
Matrix InvWorldMatrix;
Vector3 ViewPos;
float ViewFar;
Vector3 ViewDir;
float Time;
Vector4 ViewInfo;
Vector4 ScreenSize;
Vector3 EffectPosition;
float DeltaTime;
Quaternion EffectRotation;
Vector3 EffectScale;
uint32 ParticleCounterOffset;
Vector3 Dummy0;
uint32 SpawnCount;
});
bool GPUParticles::Init(ParticleEmitter* owner, MemoryReadStream& shaderCacheStream, ReadStream* materialParamsStream, int32 customDataSize)
{
ASSERT(!_shader);
// Load shader
ASSERT(GPUDevice::Instance);
#if GPU_ENABLE_RESOURCE_NAMING
const StringView name(owner->GetPath());
#else
const StringView name;
#endif
_shader = GPUDevice::Instance->CreateShader(name);
if (_shader->Create(shaderCacheStream))
{
LOG(Warning, "Failed to load shader.");
return true;
}
// Setup pipeline
_mainCS = _shader->GetCS("CS_Main");
if (!_mainCS)
{
LOG(Warning, "Missing CS_Main shader.");
return true;
}
const auto cb0 = _shader->GetCB(0);
if (!cb0)
{
LOG(Warning, "Missing valid GPU particles constant buffer.");
return true;
}
if (cb0->GetSize() < sizeof(GPUParticlesData))
{
LOG(Warning, "Invalid size GPU particles constant buffer. required {0} bytes but got {1}", sizeof(GPUParticlesData), cb0->GetSize());
return true;
}
_cbData.Resize(cb0->GetSize());
// Load material parameters
if (_params.Load(materialParamsStream))
{
LOG(Warning, "Cannot load material parameters.");
return true;
}
if (_params.Count() < owner->Graph.Parameters.Count())
{
LOG(Warning, "Invalid amount of GPU parameters load material parameters. Shader has {0} params, graph has {1}.", _params.Count(), owner->Graph.Parameters.Count());
return true;
}
// Setup custom data size stored by the particle emitter graph generator for the GPU
ASSERT(customDataSize >= 0 && customDataSize <= 1024);
CustomDataSize = customDataSize;
return false;
}
void GPUParticles::Dispose()
{
// Cleanup
_mainCS = nullptr;
SAFE_DELETE_GPU_RESOURCE(_shader);
_cbData.Resize(0);
_params.Dispose();
}
void GPUParticles::Update(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, float dt, bool canSpawn)
{
if (canSpawn)
{
// CPU logic controls the particles spawn rate
data.GPU.SpawnCount += emitter->GraphExecutorCPU.UpdateSpawn(emitter, effect, data, dt);
}
// Accumulate delta time for GPU evaluation
data.GPU.DeltaTime += dt;
}
void GPUParticles::CopyParticlesCount(GPUContext* context, ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, GPUBuffer* dstBuffer, uint32 dstOffset)
{
if (data.Buffer->GPU.PendingClear || !data.Buffer->GPU.Buffer || !data.Buffer->GPU.HasValidCount)
{
uint32 counterDefaultValue = 0;
context->UpdateBuffer(dstBuffer, &counterDefaultValue, sizeof(counterDefaultValue), dstOffset);
}
else
{
const uint32 counterOffset = data.Buffer->GPU.ParticleCounterOffset;
context->CopyBuffer(dstBuffer, data.Buffer->GPU.Buffer, sizeof(uint32), dstOffset, counterOffset);
}
}
void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, ParticleEffect* effect, int32 emitterIndex, ParticleEmitterInstance& data)
{
ASSERT(emitter->Graph.Version == data.Version);
ASSERT(emitter->Graph.Version == data.Buffer->Version);
uint32 counterDefaultValue = 0;
const uint32 counterOffset = data.Buffer->GPU.ParticleCounterOffset;
const bool hasCB = _cbData.HasItems();
// Clear buffers if need to
if (data.Buffer->GPU.PendingClear)
{
data.Buffer->GPU.PendingClear = false;
data.Buffer->GPU.ParticlesCountMax = 0;
// Clear counter in the particles buffer
context->UpdateBuffer(data.Buffer->GPU.Buffer, &counterDefaultValue, sizeof(counterDefaultValue), counterOffset);
// Clear custom data
for (int32 i = 0; i < CustomDataSize; i += 4)
{
const uint32 offset = counterOffset + 4 + i * 4;
context->UpdateBuffer(data.Buffer->GPU.Buffer, &counterDefaultValue, 4, offset);
}
}
// Skip if can
SceneRenderTask* viewTask = effect->GetRenderTask();
const int32 threads = data.Buffer->GPU.ParticlesCountMax + data.GPU.SpawnCount;
if (data.GPU.DeltaTime <= 0.0f || threads == 0 || !_mainCS)
return;
// Clear destination buffer counter
context->UpdateBuffer(data.Buffer->GPU.BufferSecondary, &counterDefaultValue, sizeof(counterDefaultValue), counterOffset);
// Setup parameters
MaterialParameter::BindMeta bindMeta;
bindMeta.Context = context;
bindMeta.Constants = hasCB ? Span<byte>(_cbData.Get() + sizeof(GPUParticlesData), _cbData.Count() - sizeof(GPUParticlesData)) : Span<byte>(nullptr, 0);
bindMeta.Input = nullptr;
if (viewTask)
{
bindMeta.Buffers = viewTask->Buffers;
bindMeta.CanSampleDepth = bindMeta.CanSampleGBuffer = true;
}
else
{
bindMeta.Buffers = nullptr;
bindMeta.CanSampleDepth = bindMeta.CanSampleGBuffer = false;
}
ASSERT(data.Parameters.Count() <= _params.Count());
for (int32 i = 0; i < data.Parameters.Count(); i++)
{
// Copy instance parameters values
_params[i].SetValue(data.Parameters[i]);
}
MaterialParamsLink link;
link.This = &_params;
link.Up = link.Down = nullptr;
MaterialParams::Bind(&link, bindMeta);
// Setup constant buffer
if (hasCB)
{
auto cbData = (GPUParticlesData*)_cbData.Get();
if (viewTask)
{
auto& view = viewTask->View;
Matrix::Transpose(view.PrevViewProjection, cbData->ViewProjectionMatrix);
Matrix tmp;
Matrix::Invert(view.PrevViewProjection, tmp);
Matrix::Transpose(tmp, cbData->InvViewProjectionMatrix);
Matrix::Invert(view.PrevView, tmp);
Matrix::Transpose(tmp, cbData->InvViewMatrix);
Matrix::Transpose(view.PrevView, cbData->ViewMatrix);
cbData->ViewPos = view.Position;
cbData->ViewFar = view.Far;
cbData->ViewDir = view.Direction;
cbData->ViewInfo = view.ViewInfo;
cbData->ScreenSize = view.ScreenSize;
}
else
{
Matrix::Transpose(Matrix::Identity, cbData->ViewProjectionMatrix);
Matrix::Transpose(Matrix::Identity, cbData->InvViewProjectionMatrix);
Matrix::Transpose(Matrix::Identity, cbData->InvViewMatrix);
Matrix::Transpose(Matrix::Identity, cbData->ViewMatrix);
cbData->ViewPos = Vector3::Zero;
cbData->ViewFar = 0.0f;
cbData->ViewDir = Vector3::Forward;
cbData->ViewInfo = Vector4::Zero;
cbData->ScreenSize = Vector4::Zero;
}
cbData->Time = data.Time;
if (emitter->SimulationSpace == ParticlesSimulationSpace::World)
{
Matrix::Transpose(Matrix::Identity, cbData->WorldMatrix);
Matrix::Transpose(Matrix::Identity, cbData->InvWorldMatrix);
}
else
{
Matrix worldMatrix;
effect->GetWorld(&worldMatrix);
Matrix::Transpose(worldMatrix, cbData->WorldMatrix);
worldMatrix.Invert();
Matrix::Transpose(worldMatrix, cbData->InvWorldMatrix);
}
cbData->Time = data.Time;
cbData->EffectPosition = effect->GetPosition();
cbData->DeltaTime = data.GPU.DeltaTime;
cbData->EffectRotation = effect->GetOrientation();
cbData->EffectScale = effect->GetScale();
cbData->ParticleCounterOffset = counterOffset;
cbData->SpawnCount = data.GPU.SpawnCount;
// Bind constant buffer
const auto cb0 = _shader->GetCB(0);
context->UpdateCB(cb0, cbData);
context->BindCB(0, cb0);
}
// Bind buffers
context->BindSR(0, data.Buffer->GPU.Buffer->View());
context->BindUA(0, data.Buffer->GPU.BufferSecondary->View());
// Invoke Compute shader
const int32 threadGroupSize = 1024;
context->Dispatch(_mainCS, Math::Min(Math::DivideAndRoundUp(threads, threadGroupSize), GPU_MAX_CS_DISPATCH_THREAD_GROUPS), 1, 1);
// Copy custom data
for (int32 i = 0; i < CustomDataSize; i += 4)
{
const uint32 offset = counterOffset + 4 + i * 4;
context->CopyBuffer(data.Buffer->GPU.Buffer, data.Buffer->GPU.BufferSecondary, 4, offset, offset);
}
// Update state
data.Buffer->GPU.ParticlesCountMax = Math::Min(data.Buffer->GPU.ParticlesCountMax + data.GPU.SpawnCount, data.Buffer->Capacity);
data.Buffer->GPU.HasValidCount = true;
data.GPU.DeltaTime = 0.0f;
data.GPU.SpawnCount = 0;
// Swap particle buffers
Swap(data.Buffer->GPU.Buffer, data.Buffer->GPU.BufferSecondary);
// Copy particles count if need to
if (effect->Instance.GPUParticlesCountReadback && effect->Instance.GPUParticlesCountReadback->IsAllocated())
{
context->CopyBuffer(effect->Instance.GPUParticlesCountReadback, data.Buffer->GPU.Buffer, sizeof(uint32), emitterIndex * sizeof(uint32), counterOffset);
}
}
#endif