Files
FlaxEngine/Source/Engine/Particles/ParticleEmitter.cpp
2021-04-30 16:27:57 +02:00

418 lines
13 KiB
C++

// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "ParticleEmitter.h"
#include "ParticleManager.h"
#include "ParticleSystem.h"
#include "ParticleEffect.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Content/Upgraders/ShaderAssetUpgrader.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Graphics/Shaders/Cache/ShaderCacheManager.h"
#include "Engine/Level/Level.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#if USE_EDITOR
#include "ParticleEmitterFunction.h"
#include "Engine/ShadersCompilation/Config.h"
#if BUILD_DEBUG
#include "Engine/Engine/Globals.h"
#endif
#endif
#if COMPILE_WITH_GPU_PARTICLES
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Graphics/Shaders/Cache/ShaderStorage.h"
#endif
#if COMPILE_WITH_PARTICLE_GPU_GRAPH && COMPILE_WITH_SHADER_COMPILER
#include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h"
#include "Engine/Utilities/Encryption.h"
#endif
REGISTER_BINARY_ASSET_WITH_UPGRADER(ParticleEmitter, "FlaxEngine.ParticleEmitter", ShaderAssetUpgrader, false);
ParticleEmitter::ParticleEmitter(const SpawnParams& params, const AssetInfo* info)
: ShaderAssetTypeBase<BinaryAsset>(params, info)
, GraphExecutorCPU(Graph)
{
}
ParticleEffect* ParticleEmitter::Spawn(Actor* parent, const Transform& transform, float duration, bool autoDestroy)
{
CHECK_RETURN(!WaitForLoaded(), nullptr);
auto system = Content::CreateVirtualAsset<ParticleSystem>();
CHECK_RETURN(system, nullptr);
system->Init(this, duration);
auto effect = New<ParticleEffect>();
effect->SetTransform(transform);
effect->ParticleSystem = system;
Level::SpawnActor(effect, parent);
if (autoDestroy && duration < MAX_float)
effect->DeleteObject(duration, true);
return effect;
}
#if COMPILE_WITH_PARTICLE_GPU_GRAPH && COMPILE_WITH_SHADER_COMPILER
namespace
{
void OnGeneratorError(ShaderGraph<>::Node* node, ShaderGraphBox* box, const StringView& text)
{
LOG(Error, "GPU Particles graph error: {0} (Node:{1}:{2}, Box:{3})", text, node ? node->Type : -1, node ? node->ID : -1, box ? box->ID : -1);
}
}
#endif
Asset::LoadResult ParticleEmitter::load()
{
// Load the graph
const auto surfaceChunk = GetChunk(SHADER_FILE_CHUNK_VISJECT_SURFACE);
if (!surfaceChunk)
{
// Initialize as empty graph with only root node (no modules)
Graph.CreateDefault();
return LoadResult::Ok;
}
if (LoadChunks(GET_CHUNK_FLAG(SHADER_FILE_CHUNK_VISJECT_SURFACE)))
{
LOG(Warning, "Cannot load \'{0}\' data from chunk {1}.", ToString(), SHADER_FILE_CHUNK_VISJECT_SURFACE);
return LoadResult::CannotLoadStorage;
}
MemoryReadStream surfaceChunkStream(surfaceChunk->Get(), surfaceChunk->Size());
if (Graph.Load(&surfaceChunkStream, USE_EDITOR))
{
LOG(Warning, "Cannot load Particle Emitter graph '{0}'.", GetPath());
return LoadResult::CannotLoadData;
}
// Cache data
{
auto root = Graph.Root;
if (root->Values.Count() != 6)
{
Graph.Clear();
Graph.CreateDefault();
LOG(Warning, "Invalid Particle Emitter graph root node '{0}'.", GetPath());
}
Capacity = root->Values[0].AsInt;
SimulationMode = (ParticlesSimulationMode)root->Values[1].AsInt;
SimulationSpace = (ParticlesSimulationSpace)root->Values[2].AsInt;
EnablePooling = root->Values[3].AsBool;
CustomBounds = (BoundingBox)root->Values[4];
UseAutoBounds = root->Values[5].AsBool;
IsUsingLights = Graph.UsesLightRendering();
}
// Select simulation mode
//SimulationMode = ParticlesSimulationMode::CPU;
//SimulationMode = ParticlesSimulationMode::GPU;
if (SimulationMode == ParticlesSimulationMode::Default)
{
// Use GPU simulation only for bigger systems
SimulationMode = Capacity >= 1024 ? ParticlesSimulationMode::GPU : ParticlesSimulationMode::CPU;
}
if (SimulationMode == ParticlesSimulationMode::GPU)
{
// Downgrade to CPU simulation if no GPU support for that
if (IsUsingLights || Graph.RibbonRenderingModules.HasItems() || Graph.UsesVolumetricFogRendering)
{
// Downgrade to CPU simulation for ribbons (no GPU support for that)
SimulationMode = ParticlesSimulationMode::CPU;
}
}
#if COMPILE_WITH_PARTICLE_GPU_GRAPH && COMPILE_WITH_SHADER_COMPILER
// Check if generate shader source code
if (SimulationMode == ParticlesSimulationMode::GPU &&
(
_shaderHeader.ParticleEmitter.GraphVersion != PARTICLE_GPU_GRAPH_VERSION
#if USE_EDITOR
|| !HasChunk(SHADER_FILE_CHUNK_SOURCE)
#endif
|| HasDependenciesModified()
#if COMPILE_WITH_DEV_ENV
// Set to true to enable force GPU particle simulation shaders regeneration (don't commit it)
|| false
#endif
))
{
// Load GPU graph
ParticleEmitterGPUGenerator generator;
generator.Error.Bind(&OnGeneratorError);
auto graph = New<ParticleEmitterGraphGPU>();
surfaceChunkStream.SetPosition(0);
if (graph->Load(&surfaceChunkStream, false))
{
Delete(graph);
LOG(Warning, "Cannot load Particle Emitter GPU graph '{0}'.", GetPath());
return LoadResult::CannotLoadData;
}
generator.AddGraph(graph);
// Get chunk with material parameters
auto materialParamsChunk = GetOrCreateChunk(SHADER_FILE_CHUNK_MATERIAL_PARAMS);
if (materialParamsChunk == nullptr)
return LoadResult::MissingDataChunk;
materialParamsChunk->Data.Release();
// Generate source code and metadata
MemoryWriteStream source(16 * 1024);
int32 customDataSize;
if (generator.Generate(source, materialParamsChunk->Data, customDataSize))
{
LOG(Error, "Cannot generate particle emitter GPU shader source code for \'{0}\'. Please see log for more information.", ToString());
return LoadResult::Failed;
}
// Update asset dependencies
ClearDependencies();
for (auto& asset : generator.Assets)
{
if (asset->Is<ParticleEmitterFunction>())
AddDependency(asset.As<BinaryAsset>());
}
// Setup shader header
Platform::MemoryClear(&_shaderHeader, sizeof(_shaderHeader));
_shaderHeader.ParticleEmitter.GraphVersion = PARTICLE_GPU_GRAPH_VERSION;
_shaderHeader.ParticleEmitter.CustomDataSize = customDataSize;
#if BUILD_DEBUG && USE_EDITOR
// Dump generated shader source to the temporary file
source.SaveToFile(Globals::ProjectCacheFolder / TEXT("particle_emitter.txt"));
#endif
// Encrypt source code
Encryption::EncryptBytes((byte*)source.GetHandle(), source.GetPosition());
// Set new source code chunk
SetChunk(SHADER_FILE_CHUNK_SOURCE, ToSpan(source.GetHandle(), source.GetPosition()));
// Save to file
#if USE_EDITOR
if (Save())
{
LOG(Error, "Cannot save \'{0}\'", ToString());
return LoadResult::Failed;
}
#endif
#if COMPILE_WITH_SHADER_CACHE_MANAGER
// Invalidate shader cache
ShaderCacheManager::RemoveCache(GetID());
#endif
}
#endif
#if USE_EDITOR
// Find asset dependencies to particle emitter functions (GPU shader generation collects dependencies so don't override them)
if (SimulationMode != ParticlesSimulationMode::GPU)
{
ClearDependencies();
for (const auto& node : Graph.Nodes)
{
if (node.Type == GRAPH_NODE_MAKE_TYPE(14, 300))
{
const auto function = node.Assets[0].As<ParticleEmitterFunction>();
if (function)
{
AddDependency(function);
}
}
}
}
#endif
#if COMPILE_WITH_GPU_PARTICLES
// Handle the current runtime support for simulation mode
if (SimulationMode == ParticlesSimulationMode::GPU && (!GPUDevice::Instance->Limits.HasCompute || !GPUDevice::Instance->Limits.HasDrawIndirect))
{
// Downgrade to CPU simulation if GPU doesn't support compute shaders
SimulationMode = ParticlesSimulationMode::CPU;
}
// Check if use GPU simulation
if (SimulationMode == ParticlesSimulationMode::GPU)
{
// Load shader cache (it may call compilation or gather cached data)
ShaderCacheResult shaderCache;
if (LoadShaderCache(shaderCache))
{
LOG(Error, "Cannot load \'{0}\' shader cache.", ToString());
return LoadResult::Failed;
}
MemoryReadStream shaderCacheStream(shaderCache.Data.Get(), shaderCache.Data.Length());
// Get material parameters (may be not created if material is not using parameters)
const auto materialParamsChunk = GetChunk(SHADER_FILE_CHUNK_MATERIAL_PARAMS);
const byte* materialParamsData = nullptr;
uint32 materialParamsDataSize = 0;
if (materialParamsChunk != nullptr)
{
if (LoadChunks(GET_CHUNK_FLAG(SHADER_FILE_CHUNK_MATERIAL_PARAMS)))
{
LOG(Warning, "Cannot load \'{0}\' data from chunk {1}.", ToString(), SHADER_FILE_CHUNK_MATERIAL_PARAMS);
return LoadResult::CannotLoadStorage;
}
materialParamsData = materialParamsChunk->Get();
materialParamsDataSize = materialParamsChunk->Size();
}
MemoryReadStream materialParamsStream(materialParamsData, materialParamsDataSize);
// Setup GPU execution pipeline
if (GPU.Init(this, shaderCacheStream, &materialParamsStream, _shaderHeader.ParticleEmitter.CustomDataSize))
{
LOG(Error, "Cannot init \'{0}\' GPU execution runtime.", ToString());
return LoadResult::Failed;
}
#if COMPILE_WITH_SHADER_COMPILER
RegisterForShaderReloads(this, shaderCache);
#endif
}
#else
// Downgrade to CPU simulation if no support in build
SimulationMode = ParticlesSimulationMode::CPU;
#endif
return LoadResult::Ok;
}
void ParticleEmitter::unload(bool isReloading)
{
#if COMPILE_WITH_SHADER_COMPILER
UnregisterForShaderReloads(this);
#endif
ParticleManager::OnEmitterUnload(this);
Graph.Clear();
#if COMPILE_WITH_GPU_PARTICLES
GPU.Dispose();
#endif
}
AssetChunksFlag ParticleEmitter::getChunksToPreload() const
{
AssetChunksFlag result = ShaderAssetTypeBase<BinaryAsset>::getChunksToPreload();
result |= GET_CHUNK_FLAG(SHADER_FILE_CHUNK_VISJECT_SURFACE);
#if COMPILE_WITH_GPU_PARTICLES
result |= GET_CHUNK_FLAG(SHADER_FILE_CHUNK_MATERIAL_PARAMS);
#endif
return result;
}
#if USE_EDITOR
void ParticleEmitter::OnDependencyModified(BinaryAsset* asset)
{
BinaryAsset::OnDependencyModified(asset);
Reload();
}
#endif
#if USE_EDITOR
void ParticleEmitter::InitCompilationOptions(ShaderCompilationOptions& options)
{
// Base
ShaderAssetBase::InitCompilationOptions(options);
#if COMPILE_WITH_SHADER_COMPILER
// Setup shader macros
options.Macros.Add({ "THREAD_GROUP_SIZE", "1024" });
#endif
}
#endif
BytesContainer ParticleEmitter::LoadSurface(bool createDefaultIfMissing)
{
BytesContainer result;
ScopeLock lock(Locker);
// Check if has that chunk
if (HasChunk(SHADER_FILE_CHUNK_VISJECT_SURFACE))
{
// Load graph
if (!LoadChunks(GET_CHUNK_FLAG(SHADER_FILE_CHUNK_VISJECT_SURFACE)))
{
// Get stream with graph data
const auto data = GetChunk(SHADER_FILE_CHUNK_VISJECT_SURFACE);
result.Copy(data->Data);
return result;
}
}
LOG(Warning, "Particle Emitter \'{0}\' surface data is missing.", GetPath());
// Check if create default surface
if (createDefaultIfMissing)
{
// Create default layer
ParticleEmitterGraphCPU graph;
graph.CreateDefault();
// Serialize layer to stream
MemoryWriteStream stream(512);
graph.Save(&stream, false);
// Set output data
result.Copy(stream.GetHandle(), stream.GetPosition());
}
return result;
}
#if USE_EDITOR
bool ParticleEmitter::SaveSurface(BytesContainer& data)
{
// Wait for asset to be loaded or don't if last load failed (eg. by shader source compilation error)
if (LastLoadFailed())
{
LOG(Warning, "Saving asset that failed to load.");
}
else if (WaitForLoaded())
{
LOG(Error, "Asset loading failed. Cannot save it.");
return true;
}
ScopeLock lock(Locker);
// Release all chunks
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
ReleaseChunk(i);
// Clear particle emitter info
Platform::MemoryClear(&_shaderHeader, sizeof(_shaderHeader));
_shaderHeader.ParticleEmitter.GraphVersion = PARTICLE_GPU_GRAPH_VERSION;
_shaderHeader.ParticleEmitter.CustomDataSize = 0;
// Set Visject Surface data
auto visjectSurfaceChunk = GetOrCreateChunk(SHADER_FILE_CHUNK_VISJECT_SURFACE);
ASSERT(visjectSurfaceChunk != nullptr);
visjectSurfaceChunk->Data.Copy(data);
if (Save())
{
LOG(Error, "Cannot save \'{0}\'", ToString());
return true;
}
#if COMPILE_WITH_SHADER_CACHE_MANAGER
// Invalidate shader cache
ShaderCacheManager::RemoveCache(GetID());
#endif
return false;
}
#endif