605 lines
20 KiB
C++
605 lines
20 KiB
C++
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
|
|
|
#if COMPILE_WITH_PARTICLE_GPU_GRAPH
|
|
|
|
#include "ParticleEmitterGraph.GPU.h"
|
|
#include "Engine/Particles/ParticleEmitterFunction.h"
|
|
|
|
namespace
|
|
{
|
|
VariantType::Types GetValueType(ParticleAttribute::ValueTypes valueType)
|
|
{
|
|
VariantType::Types type;
|
|
switch (valueType)
|
|
{
|
|
case ParticleAttribute::ValueTypes::Float:
|
|
type = VariantType::Float;
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Vector2:
|
|
type = VariantType::Vector2;
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Vector3:
|
|
type = VariantType::Vector3;
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Vector4:
|
|
type = VariantType::Vector4;
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Int:
|
|
type = VariantType::Int;
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Uint:
|
|
type = VariantType::Uint;
|
|
break;
|
|
default:
|
|
type = VariantType::Null;
|
|
}
|
|
return type;
|
|
}
|
|
}
|
|
|
|
ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAttribute(Node* caller, const StringView& name, ParticleAttribute::ValueTypes valueType, AccessMode mode)
|
|
{
|
|
// Find this attribute
|
|
return AccessParticleAttribute(caller, ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.FindAttribute(name, valueType), mode);
|
|
}
|
|
|
|
ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAttribute(Node* caller, int32 index, AccessMode mode)
|
|
{
|
|
// Handle invalid attribute
|
|
if (index == -1)
|
|
return Value::Zero;
|
|
|
|
// Try reuse cached value
|
|
auto& value = _attributeValues[index];
|
|
value.Access = (AccessMode)((int)value.Access | (int)mode);
|
|
if (value.Variable.Type != VariantType::Null)
|
|
return value.Variable;
|
|
|
|
auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[index];
|
|
const VariantType::Types type = GetValueType(attribute.ValueType);
|
|
|
|
// Generate local variable name that matches the attribute name for easier shader source debugging
|
|
String attributeNameAnsi;
|
|
for (int32 i = 0; i < attribute.Name.Length(); i++)
|
|
{
|
|
if (StringUtils::IsAlnum(attribute.Name[i]))
|
|
attributeNameAnsi += attribute.Name[i];
|
|
}
|
|
if (attributeNameAnsi.IsEmpty())
|
|
attributeNameAnsi = getLocalName(_localIndex++);
|
|
const String localName = String(TEXT("particle")) + attributeNameAnsi;
|
|
|
|
if (mode == AccessMode::Write)
|
|
{
|
|
// Create local variable
|
|
value.Variable = writeLocal(type, caller, localName);
|
|
}
|
|
else if (_contextType == ParticleContextType::Initialize)
|
|
{
|
|
// Initialize with default value
|
|
const Value defaultValue(((ParticleEmitterGraphGPU*)_graphStack.Peek())->AttributesDefaults[index]);
|
|
value.Variable = writeLocal(type, defaultValue.Value, caller, localName);
|
|
}
|
|
else
|
|
{
|
|
// Read from the attributes buffer
|
|
const Char* format;
|
|
switch (attribute.ValueType)
|
|
{
|
|
case ParticleAttribute::ValueTypes::Float:
|
|
format = TEXT("GetParticleFloat(context.ParticleIndex, {0})");
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Vector2:
|
|
format = TEXT("GetParticleVec2(context.ParticleIndex, {0})");
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Vector3:
|
|
format = TEXT("GetParticleVec3(context.ParticleIndex, {0})");
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Vector4:
|
|
format = TEXT("GetParticleVec4(context.ParticleIndex, {0})");
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Int:
|
|
format = TEXT("GetParticleInt(context.ParticleIndex, {0})");
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Uint:
|
|
format = TEXT("GetParticleUint(context.ParticleIndex, {0})");
|
|
break;
|
|
default:
|
|
return Value::Zero;
|
|
}
|
|
value.Variable = writeLocal(type, String::Format(format, attribute.Offset), caller, localName);
|
|
}
|
|
|
|
return value.Variable;
|
|
}
|
|
|
|
void ParticleEmitterGPUGenerator::ProcessGroupParameters(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// Get
|
|
case 1:
|
|
case 2:
|
|
{
|
|
// Get parameter
|
|
const auto param = findParam((Guid)node->Values[0]);
|
|
if (param)
|
|
{
|
|
switch (param->Type)
|
|
{
|
|
case MaterialParameterType::Bool:
|
|
value = Value(VariantType::Bool, param->ShaderName);
|
|
break;
|
|
case MaterialParameterType::Integer:
|
|
case MaterialParameterType::SceneTexture:
|
|
value = Value(VariantType::Int, param->ShaderName);
|
|
break;
|
|
case MaterialParameterType::Float:
|
|
value = Value(VariantType::Float, param->ShaderName);
|
|
break;
|
|
case MaterialParameterType::Vector2:
|
|
case MaterialParameterType::Vector3:
|
|
case MaterialParameterType::Vector4:
|
|
case MaterialParameterType::Color:
|
|
{
|
|
// Set result values based on box ID
|
|
const Value sample(box->Type.Type, param->ShaderName);
|
|
switch (box->ID)
|
|
{
|
|
case 0:
|
|
value = sample;
|
|
break;
|
|
case 1:
|
|
value.Value = sample.Value + _subs[0];
|
|
break;
|
|
case 2:
|
|
value.Value = sample.Value + _subs[1];
|
|
break;
|
|
case 3:
|
|
value.Value = sample.Value + _subs[2];
|
|
break;
|
|
case 4:
|
|
value.Value = sample.Value + _subs[3];
|
|
break;
|
|
default: CRASH;
|
|
break;
|
|
}
|
|
value.Type = box->Type.Type;
|
|
break;
|
|
}
|
|
|
|
case MaterialParameterType::Matrix:
|
|
{
|
|
value = Value(box->Type.Type, String::Format(TEXT("{0}[{1}]"), param->ShaderName, box->ID));
|
|
break;
|
|
}
|
|
case MaterialParameterType::ChannelMask:
|
|
{
|
|
const auto input = tryGetValue(node->GetBox(0), Value::Zero);
|
|
value = writeLocal(VariantType::Float, String::Format(TEXT("dot({0}, {1})"), input.Value, param->ShaderName), node);
|
|
break;
|
|
}
|
|
case MaterialParameterType::CubeTexture:
|
|
case MaterialParameterType::Texture:
|
|
case MaterialParameterType::GPUTextureArray:
|
|
case MaterialParameterType::GPUTextureCube:
|
|
case MaterialParameterType::GPUTextureVolume:
|
|
case MaterialParameterType::GPUTexture:
|
|
value = Value(VariantType::Object, param->ShaderName);
|
|
break;
|
|
default:
|
|
CRASH;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OnError(node, box, String::Format(TEXT("Missing graph parameter {0}."), node->Values[0]));
|
|
value = Value::Zero;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ParticleEmitterGPUGenerator::ProcessGroupTools(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// Linearize Depth
|
|
case 7:
|
|
{
|
|
// Get input
|
|
const Value depth = tryGetValue(node->GetBox(0), Value::Zero).AsFloat();
|
|
|
|
// Linearize raw device depth
|
|
linearizeSceneDepth(node, depth, value);
|
|
break;
|
|
}
|
|
// Time
|
|
case 8:
|
|
value = box->ID == 0 ? Value(VariantType::Float, TEXT("Time")) : Value(VariantType::Float, TEXT("DeltaTime"));
|
|
break;
|
|
// Transform Position To Screen UV
|
|
case 9:
|
|
{
|
|
const Value position = tryGetValue(node->GetBox(0), Value::Zero).AsVector3();
|
|
const Value projPos = writeLocal(VariantType::Vector4, String::Format(TEXT("mul(float4({0}, 1.0f), ViewProjectionMatrix)"), position.Value), node);
|
|
_writer.Write(TEXT("\t{0}.xy /= {0}.w;\n"), projPos.Value);
|
|
_writer.Write(TEXT("\t{0}.xy = {0}.xy * 0.5f + 0.5f;\n"), projPos.Value);
|
|
value = Value(VariantType::Vector2, projPos.Value + TEXT(".xy"));
|
|
break;
|
|
}
|
|
default:
|
|
ShaderGenerator::ProcessGroupTools(box, node, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// Particle Attribute
|
|
case 100:
|
|
{
|
|
value = AccessParticleAttribute(node, (StringView)node->Values[0], static_cast<ParticleAttribute::ValueTypes>(node->Values[1].AsInt), AccessMode::Read);
|
|
break;
|
|
}
|
|
// Particle Attribute (by index)
|
|
case 303:
|
|
{
|
|
const Char* format;
|
|
auto valueType = static_cast<ParticleAttribute::ValueTypes>(node->Values[1].AsInt);
|
|
const int32 attributeIndex = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.FindAttribute((StringView)node->Values[0], valueType);
|
|
if (attributeIndex == -1)
|
|
return;
|
|
auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[attributeIndex];
|
|
const auto particleIndex = Value::Cast(tryGetValue(node->GetBox(1), Value(VariantType::Uint, TEXT("context.ParticleIndex"))), VariantType::Uint);
|
|
switch (valueType)
|
|
{
|
|
case ParticleAttribute::ValueTypes::Float:
|
|
format = TEXT("GetParticleFloat({1}, {0})");
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Vector2:
|
|
format = TEXT("GetParticleVec2({1}, {0})");
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Vector3:
|
|
format = TEXT("GetParticleVec3({1}, {0})");
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Vector4:
|
|
format = TEXT("GetParticleVec4({1}, {0})");
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Int:
|
|
format = TEXT("GetParticleInt({1}, {0})");
|
|
break;
|
|
case ParticleAttribute::ValueTypes::Uint:
|
|
format = TEXT("GetParticleUint({1}, {0})");
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
const VariantType::Types type = GetValueType(attribute.ValueType);
|
|
value = writeLocal(type, String::Format(format, attribute.Offset, particleIndex.Value), node);
|
|
break;
|
|
}
|
|
// Particle Position
|
|
case 101:
|
|
{
|
|
value = AccessParticleAttribute(node, TEXT("Position"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read);
|
|
break;
|
|
}
|
|
// Particle Lifetime
|
|
case 102:
|
|
{
|
|
value = AccessParticleAttribute(node, TEXT("Lifetime"), ParticleAttribute::ValueTypes::Float, AccessMode::Read);
|
|
break;
|
|
}
|
|
// Particle Age
|
|
case 103:
|
|
{
|
|
value = AccessParticleAttribute(node, TEXT("Age"), ParticleAttribute::ValueTypes::Float, AccessMode::Read);
|
|
break;
|
|
}
|
|
// Particle Color
|
|
case 104:
|
|
{
|
|
value = AccessParticleAttribute(node, TEXT("Color"), ParticleAttribute::ValueTypes::Vector4, AccessMode::Read);
|
|
break;
|
|
}
|
|
// Particle Velocity
|
|
case 105:
|
|
{
|
|
value = AccessParticleAttribute(node, TEXT("Velocity"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read);
|
|
break;
|
|
}
|
|
// Particle Sprite Size
|
|
case 106:
|
|
{
|
|
value = AccessParticleAttribute(node, TEXT("SpriteSize"), ParticleAttribute::ValueTypes::Vector2, AccessMode::Read);
|
|
break;
|
|
}
|
|
// Particle Mass
|
|
case 107:
|
|
{
|
|
value = AccessParticleAttribute(node, TEXT("Mass"), ParticleAttribute::ValueTypes::Float, AccessMode::Read);
|
|
break;
|
|
}
|
|
// Particle Rotation
|
|
case 108:
|
|
{
|
|
value = AccessParticleAttribute(node, TEXT("Rotation"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read);
|
|
break;
|
|
}
|
|
// Particle Angular Velocity
|
|
case 109:
|
|
{
|
|
value = AccessParticleAttribute(node, TEXT("AngularVelocity"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read);
|
|
break;
|
|
}
|
|
// Particle Normalized Age
|
|
case 110:
|
|
{
|
|
const auto age = AccessParticleAttribute(node, TEXT("Age"), ParticleAttribute::ValueTypes::Float, AccessMode::Read);
|
|
const auto lifetime = AccessParticleAttribute(node, TEXT("Lifetime"), ParticleAttribute::ValueTypes::Float, AccessMode::Read);
|
|
value = writeOperation2(node, age, lifetime, '/');
|
|
break;
|
|
}
|
|
// Particle Radius
|
|
case 111:
|
|
{
|
|
value = AccessParticleAttribute(node, TEXT("Radius"), ParticleAttribute::ValueTypes::Float, AccessMode::Read);
|
|
break;
|
|
}
|
|
// Effect Position
|
|
case 200:
|
|
{
|
|
value = Value(VariantType::Vector3, TEXT("EffectPosition"));
|
|
break;
|
|
}
|
|
// Effect Rotation
|
|
case 201:
|
|
{
|
|
value = Value(VariantType::Quaternion, TEXT("EffectRotation"));
|
|
break;
|
|
}
|
|
// Effect Scale
|
|
case 202:
|
|
{
|
|
value = Value(VariantType::Vector3, TEXT("EffectScale"));
|
|
break;
|
|
}
|
|
// Simulation Mode
|
|
case 203:
|
|
{
|
|
value = Value(box->ID == 1);
|
|
break;
|
|
}
|
|
// View Position
|
|
case 204:
|
|
{
|
|
value = Value(VariantType::Vector3, TEXT("ViewPos"));
|
|
break;
|
|
}
|
|
// View Direction
|
|
case 205:
|
|
{
|
|
value = Value(VariantType::Vector3, TEXT("ViewDir"));
|
|
break;
|
|
}
|
|
// View Far Plane
|
|
case 206:
|
|
{
|
|
value = Value(VariantType::Float, TEXT("ViewFar"));
|
|
break;
|
|
}
|
|
// Screen Size
|
|
case 207:
|
|
{
|
|
value = Value(VariantType::Vector2, box->ID == 0 ? TEXT("ScreenSize.xy") : TEXT("ScreenSize.zw"));
|
|
break;
|
|
}
|
|
// Random Float
|
|
case 208:
|
|
{
|
|
value = writeLocal(VariantType::Float, TEXT("RAND"), node);
|
|
break;
|
|
}
|
|
// Random Vector2
|
|
case 209:
|
|
{
|
|
value = writeLocal(VariantType::Vector2, TEXT("RAND2"), node);
|
|
break;
|
|
}
|
|
// Random Vector3
|
|
case 210:
|
|
{
|
|
value = writeLocal(VariantType::Vector3, TEXT("RAND3"), node);
|
|
break;
|
|
}
|
|
// Random Vector4
|
|
case 211:
|
|
{
|
|
value = writeLocal(VariantType::Vector4, TEXT("RAND4"), node);
|
|
break;
|
|
}
|
|
// Particle Position (world space)
|
|
case 212:
|
|
value = AccessParticleAttribute(node, TEXT("Position"), ParticleAttribute::ValueTypes::Vector3, AccessMode::Read);
|
|
if (IsLocalSimulationSpace())
|
|
value = writeLocal(VariantType::Vector3, String::Format(TEXT("mul(float4({0}, 1), WorldMatrix).xyz"), value.Value), node);
|
|
break;
|
|
// Random Float Range
|
|
case 213:
|
|
{
|
|
auto& a = node->Values[0].AsFloat;
|
|
auto& b = node->Values[1].AsFloat;
|
|
value = writeLocal(VariantType::Float, String::Format(TEXT("lerp({0}, {1}, RAND)"), a, b), node);
|
|
break;
|
|
}
|
|
// Random Vector2 Range
|
|
case 214:
|
|
{
|
|
auto& a = node->Values[0].AsVector2();
|
|
auto& b = node->Values[1].AsVector2();
|
|
value = writeLocal(VariantType::Vector2, String::Format(TEXT("float2(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND))"), a.X, b.X, a.Y, b.Y), node);
|
|
break;
|
|
}
|
|
// Random Vector3 Range
|
|
case 215:
|
|
{
|
|
auto& a = node->Values[0].AsVector3();
|
|
auto& b = node->Values[1].AsVector3();
|
|
value = writeLocal(VariantType::Vector3, String::Format(TEXT("float3(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND), lerp({4}, {5}, RAND))"), a.X, b.X, a.Y, b.Y, a.Z, b.Z), node);
|
|
break;
|
|
}
|
|
// Random Vector4 Range
|
|
case 216:
|
|
{
|
|
auto& a = node->Values[0].AsVector4();
|
|
auto& b = node->Values[1].AsVector4();
|
|
value = writeLocal(VariantType::Vector4, String::Format(TEXT("float4(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND), lerp({4}, {5}, RAND), lerp({6}, {7}, RAND))"), a.X, b.X, a.Y, b.Y, a.Z, b.Z, a.W, b.W), node);
|
|
break;
|
|
}
|
|
// Particle Emitter Function
|
|
case 300:
|
|
{
|
|
// Load function asset
|
|
const auto function = Assets.LoadAsync<ParticleEmitterFunction>((Guid)node->Values[0]);
|
|
if (!function || function->WaitForLoaded())
|
|
{
|
|
OnError(node, box, TEXT("Missing or invalid function."));
|
|
value = Value::Zero;
|
|
break;
|
|
}
|
|
|
|
#if 0
|
|
// Prevent recursive calls
|
|
for (int32 i = _callStack.Count() - 1; i >= 0; i--)
|
|
{
|
|
if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(14, 300))
|
|
{
|
|
const auto callFunc = Assets.LoadAsync<ParticleEmitterFunction>((Guid)_callStack[i]->Values[0]);
|
|
if (callFunc == function)
|
|
{
|
|
OnError(node, box, String::Format(TEXT("Recursive call to function '{0}'!"), function->ToString()));
|
|
value = Value::Zero;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Create a instanced version of the function graph
|
|
Graph* graph;
|
|
if (!_functions.TryGet(node, graph))
|
|
{
|
|
graph = (Graph*)New<ParticleEmitterGraphGPU>();
|
|
function->LoadSurface((ParticleEmitterGraphGPU&)*graph);
|
|
_functions.Add(node, graph);
|
|
}
|
|
|
|
// Peek the function output (function->Outputs maps the functions outputs to output nodes indices)
|
|
const int32 outputIndex = box->ID - 16;
|
|
if (outputIndex < 0 || outputIndex >= function->Outputs.Count())
|
|
{
|
|
OnError(node, box, TEXT("Invalid function output box."));
|
|
value = Value::Zero;
|
|
break;
|
|
}
|
|
Node* functionOutputNode = &graph->Nodes[function->Outputs[outputIndex]];
|
|
Box* functionOutputBox = functionOutputNode->TryGetBox(0);
|
|
|
|
// Evaluate the function output
|
|
_graphStack.Push(graph);
|
|
value = functionOutputBox && functionOutputBox->HasConnection() ? eatBox(node, functionOutputBox->FirstConnection()) : Value::Zero;
|
|
_graphStack.Pop();
|
|
break;
|
|
}
|
|
// Particle Index
|
|
case 301:
|
|
value = Value(VariantType::Uint, TEXT("context.ParticleIndex"));
|
|
break;
|
|
// Particles Count
|
|
case 302:
|
|
value = Value(VariantType::Uint, TEXT("context.ParticlesCount"));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ParticleEmitterGPUGenerator::ProcessGroupFunction(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// Function Input
|
|
case 1:
|
|
{
|
|
// Find the function call
|
|
Node* functionCallNode = nullptr;
|
|
ASSERT(_graphStack.Count() >= 2);
|
|
Graph* graph;
|
|
for (int32 i = _callStack.Count() - 1; i >= 0; i--)
|
|
{
|
|
if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(14, 300) && _functions.TryGet(_callStack[i], graph) && _graphStack[_graphStack.Count() - 1] == graph)
|
|
{
|
|
functionCallNode = _callStack[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!functionCallNode)
|
|
{
|
|
OnError(node, box, TEXT("Missing calling function node."));
|
|
value = Value::Zero;
|
|
break;
|
|
}
|
|
const auto function = Assets.LoadAsync<ParticleEmitterFunction>((Guid)functionCallNode->Values[0]);
|
|
if (!_functions.TryGet(functionCallNode, graph) || !function)
|
|
{
|
|
OnError(node, box, TEXT("Missing calling function graph."));
|
|
value = Value::Zero;
|
|
break;
|
|
}
|
|
|
|
// Peek the input box to use
|
|
int32 inputIndex = -1;
|
|
for (int32 i = 0; i < function->Inputs.Count(); i++)
|
|
{
|
|
if (node->ID == graph->Nodes[function->Inputs[i]].ID)
|
|
{
|
|
inputIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
if (inputIndex < 0 || inputIndex >= function->Inputs.Count())
|
|
{
|
|
OnError(node, box, TEXT("Invalid function input box."));
|
|
value = Value::Zero;
|
|
break;
|
|
}
|
|
Box* functionCallBox = functionCallNode->TryGetBox(inputIndex);
|
|
if (functionCallBox && functionCallBox->HasConnection())
|
|
{
|
|
// Use provided input value from the function call
|
|
_graphStack.Pop();
|
|
value = eatBox(node, functionCallBox->FirstConnection());
|
|
_graphStack.Push(graph);
|
|
}
|
|
else
|
|
{
|
|
// Use the default value from the function graph
|
|
value = tryGetValue(node->TryGetBox(1), Value::Zero);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endif
|