Use bytes for internal RGBA components of Color, iterate particles sequentially in ParticleEmitters, increase limit for CPU particles, reduce size of Particle struct.

This commit is contained in:
2024-10-16 00:28:39 +02:00
parent 692fdf8ef0
commit 7c7c61fd56
7 changed files with 71 additions and 78 deletions

11
TODO.md
View File

@@ -13,9 +13,9 @@
- ~~Use GUIDs and string ID maps for fetching resources instead of string IDs alone.~~ - ~~Use GUIDs and string ID maps for fetching resources instead of string IDs alone.~~
- Reimplement unloading. - Reimplement unloading.
- Finalize ResourceManager and ResourceLoader APIs for 1.0. - Finalize ResourceManager and ResourceLoader APIs for 1.0.
- Virtual file system.
- (stretch goal) Streamed resource loading. - (stretch goal) Streamed resource loading.
- (stretch goal) Add async API for ResourceManager. - (stretch goal) Add async API for ResourceManager.
- (stretch goal) Virtual file system.
## Serialization ## Serialization
@@ -41,14 +41,15 @@
- ~~Asset manager~~ - ~~Asset manager~~
- ~~Separate engine and game into separate projects~~ - ~~Separate engine and game into separate projects~~
- Particle system 2.0 - Particle system 2.0
- Reduce the size of Particle struct by infering most parameters through lifetime. - ~~Reduce the size of Particle struct by infering most parameters through lifetime.~~
- Reduce cache misses by utilizing SoA. - ~~Reduce cache misses by utilizing SoA.~~
- SIMD acceleration. - ~~SIMD acceleration.~~
- Dynamically resize particle arrays as emitters get added or removed.
## SceneGraph module ## SceneGraph module
- Layers (sorting mechanism)
- Full overhaul using Entity Component architecture (not to be mistaken with ECS). - Full overhaul using Entity Component architecture (not to be mistaken with ECS).
- Layers (sorting mechanism)
- Save/load scenes from file - Save/load scenes from file
## Input ## Input

View File

@@ -1,14 +1,14 @@
[ParticleEmitterSettings] [ParticleEmitterSettings]
MaxParticles = 8096 MaxParticles = 1024
EmitRadius = 128 EmitRadius = 128
Explosiveness = 1.0 Explosiveness = 1.0
LifeTime = 1.0 LifeTime = 2.0
Direction = [0.0, 1.0] Direction = [0.0, -1.0]
LinearVelocity = 200 LinearVelocity = 200
Gravity = [0.0, 0.0] Gravity = [0.0, 0.0]
LinearVelocityRandom = 0.5 LinearVelocityRandom = 0.5
ScaleBegin = 1.0 ScaleBegin = 1.0
ScaleEnd = 0.0 ScaleEnd = 5.0
ColorBegin = [255, 0, 255, 255] ColorBegin = [255, 0, 0, 0]
ColorEnd = [0, 0, 0, 0] ColorEnd = [0, 255, 0, 255]

View File

@@ -63,6 +63,8 @@ public class TestGame : Game
_particleSystem!.Update(Renderer.FrameTime); _particleSystem!.Update(Renderer.FrameTime);
_particleSimStopwatch.Stop(); _particleSimStopwatch.Stop();
_renderStopwatch = Stopwatch.StartNew();
Renderer.BeginFrame(); Renderer.BeginFrame();
Renderer.ClearBackground(Color.Black); Renderer.ClearBackground(Color.Black);
foreach (var emitter in _particleSystem!.Emitters) foreach (var emitter in _particleSystem!.Emitters)
@@ -70,13 +72,17 @@ public class TestGame : Game
DrawEmitter(emitter); DrawEmitter(emitter);
} }
Renderer.SetTransform(Renderer.WindowSize / 2, Vector2.Zero);
Renderer.DrawTexture(_icon, Color.White);
Renderer.ResetTransform(); Renderer.ResetTransform();
Renderer.DrawText(_font, $"Particle Sim: {TimeSpan.FromTicks(_particleSimStopwatch.ElapsedTicks).TotalMilliseconds} ms", Color.White); Renderer.DrawText(_font, $"Particle Sim: {TimeSpan.FromTicks(_particleSimStopwatch.ElapsedTicks).TotalMilliseconds} ms", Color.White);
Renderer.SetTransform(new Vector2(0.0f, 16.0f), Vector2.Zero);
Renderer.DrawText(_font, $"Render: {TimeSpan.FromTicks(_lastRenderTime).TotalMilliseconds} ms", Color.White);
Renderer.EndFrame(); Renderer.EndFrame();
_lastRenderTime = _renderStopwatch.ElapsedTicks;
_renderStopwatch.Restart();
_particleSimStopwatch.Restart(); _particleSimStopwatch.Restart();
} }
} }
@@ -90,14 +96,16 @@ public class TestGame : Game
{ {
Renderer.BeginBlended(Voile.Rendering.BlendMode.BlendAlpha); Renderer.BeginBlended(Voile.Rendering.BlendMode.BlendAlpha);
for (int i = 0; i < emitter.Settings.MaxParticles; i++) var maxParticles = emitter.Settings.MaxParticles;
for (int i = maxParticles - 1; i > 0; i--)
{ {
var particle = emitter.GetParticle(i); var particle = emitter.GetParticle(i);
var color = new Color(particle.ColorArgb); var color = new Color(particle.ColorArgb);
Renderer.SetTransform(emitter.OriginPosition + particle.Position, Vector2.Zero); Renderer.SetTransform(emitter.OriginPosition + particle.Position, Vector2.Zero);
Renderer.DrawCircle(16f * particle.Scale, color); Renderer.DrawRectangle(Vector2.One * 16.0f * particle.Scale, color);
} }
Renderer.EndBlended(); Renderer.EndBlended();
@@ -105,7 +113,8 @@ public class TestGame : Game
[NotNull] private ParticleSystem _particleSystem; [NotNull] private ParticleSystem _particleSystem;
private Stopwatch _particleSimStopwatch; private Stopwatch _particleSimStopwatch, _renderStopwatch;
private long _lastRenderTime;
private int _emitterId; private int _emitterId;
private ResourceRef<ParticleEmitterSettingsResource> _emitterSettings; private ResourceRef<ParticleEmitterSettingsResource> _emitterSettings;
private ResourceRef<Font> _font; private ResourceRef<Font> _font;

View File

@@ -219,7 +219,8 @@ namespace Voile.Rendering
private Raylib_cs.Color VoileColorToRaylibColor(Color color) private Raylib_cs.Color VoileColorToRaylibColor(Color color)
{ {
return new Raylib_cs.Color { r = (byte)Math.Round(color.R * 255f), g = (byte)Math.Round(color.G * 255f), b = (byte)Math.Round(color.B * 255f), a = (byte)Math.Round(color.A * 255f) }; var rayColor = new Raylib_cs.Color(color.R, color.G, color.B, color.A);
return rayColor;
} }
public override void DrawText(ResourceRef<Font> fontResource, string text, Color color) public override void DrawText(ResourceRef<Font> fontResource, string text, Color color)

View File

@@ -14,8 +14,6 @@ public struct Particle
public int EmitterIndex { get; set; } public int EmitterIndex { get; set; }
public int ColorArgb { get; set; } public int ColorArgb { get; set; }
public Vector2 Position { get; set; } public Vector2 Position { get; set; }
public Vector2 Velocity { get; set; }
public float AngularVelocity { get; set; }
public float LifeTime { get; set; } = 1.0f; public float LifeTime { get; set; } = 1.0f;
public float Scale { get; set; } public float Scale { get; set; }
public float Rotation { get; set; } public float Rotation { get; set; }
@@ -61,7 +59,6 @@ public class ParticleEmitterSettingsResourceLoader : ResourceLoader<ParticleEmit
protected override ParticleEmitterSettingsResource LoadResource(string path) protected override ParticleEmitterSettingsResource LoadResource(string path)
{ {
// TODO: this is ugly, better to make some sort of wrapper API for TOML files.
var settings = new ParticleEmitterSettings(); var settings = new ParticleEmitterSettings();
using (var reader = new TomlDataReader("ParticleEmitterSettings")) using (var reader = new TomlDataReader("ParticleEmitterSettings"))
@@ -116,7 +113,7 @@ public class ParticleEmitter : IUpdatableSystem
_settingsResource = settingsResource; _settingsResource = settingsResource;
_maxParticles = _settingsResource.Value.Settings.MaxParticles; _maxParticles = _settingsResource.Value.Settings.MaxParticles;
_particleIndex = _maxParticles - 1; // _particleIndex = _maxParticles - 1;
_positions = positionsSlice; _positions = positionsSlice;
_velocities = velocitiesSlice; _velocities = velocitiesSlice;
@@ -131,7 +128,6 @@ public class ParticleEmitter : IUpdatableSystem
return new Particle() return new Particle()
{ {
Position = _positions[idx], Position = _positions[idx],
Velocity = _velocities[idx],
Scale = MathUtils.Lerp(Settings.ScaleEnd, Settings.ScaleBegin, t), Scale = MathUtils.Lerp(Settings.ScaleEnd, Settings.ScaleBegin, t),
ColorArgb = MathUtils.LerpColor(Settings.ColorEnd, Settings.ColorBegin, t).Argb ColorArgb = MathUtils.LerpColor(Settings.ColorEnd, Settings.ColorBegin, t).Argb
}; };
@@ -149,7 +145,7 @@ public class ParticleEmitter : IUpdatableSystem
} }
_maxParticles = Settings.MaxParticles; _maxParticles = Settings.MaxParticles;
_particleIndex = _maxParticles - 1; _particleIndex = 0;
} }
/// <summary> /// <summary>
@@ -166,7 +162,6 @@ public class ParticleEmitter : IUpdatableSystem
} }
var deltaTimeVector = new Vector2((float)deltaTime); var deltaTimeVector = new Vector2((float)deltaTime);
var lifeTimeInv = 1.0f / Settings.LifeTime;
var gravityVector = Settings.Gravity * (float)deltaTime; var gravityVector = Settings.Gravity * (float)deltaTime;
var dampingFactor = Settings.Damping * (float)deltaTime; var dampingFactor = Settings.Damping * (float)deltaTime;
@@ -174,8 +169,6 @@ public class ParticleEmitter : IUpdatableSystem
{ {
_lifetimes[i] = Math.Max(0.0f, _lifetimes[i] - (float)deltaTime); _lifetimes[i] = Math.Max(0.0f, _lifetimes[i] - (float)deltaTime);
var t = _lifetimes[i] * lifeTimeInv;
_velocities[i] += gravityVector; _velocities[i] += gravityVector;
_positions[i] += _velocities[i] * deltaTimeVector; _positions[i] += _velocities[i] * deltaTimeVector;
@@ -185,8 +178,6 @@ public class ParticleEmitter : IUpdatableSystem
private void Emit() private void Emit()
{ {
// Particle particle = _particles[_particleIndex];
if (_lifetimes[_particleIndex] > 0) return; if (_lifetimes[_particleIndex] > 0) return;
_positions[_particleIndex] = GetEmitPosition(); _positions[_particleIndex] = GetEmitPosition();
@@ -194,12 +185,9 @@ public class ParticleEmitter : IUpdatableSystem
_velocities[_particleIndex] += Vector2.One * Settings.LinearVelocityRandom * ((float)_random.NextDouble() - 0.5f); _velocities[_particleIndex] += Vector2.One * Settings.LinearVelocityRandom * ((float)_random.NextDouble() - 0.5f);
// particle.AngularVelocity = Settings.AngularVelocity;
// particle.AngularVelocity += 1f * Settings.AngularVelocityRandom * ((float)_random.NextDouble() - 0.5f);
_lifetimes[_particleIndex] = Settings.LifeTime; _lifetimes[_particleIndex] = Settings.LifeTime;
_particleIndex = --_particleIndex <= 0 ? _maxParticles - 1 : --_particleIndex; _particleIndex = (_particleIndex + 1) % _maxParticles;
} }
private Vector2 GetEmitPosition() private Vector2 GetEmitPosition()
@@ -233,7 +221,7 @@ public class ParticleSystem : IUpdatableSystem, IDisposable
/// <summary> /// <summary>
/// Maximum amount of particles emittable by the system. /// Maximum amount of particles emittable by the system.
/// </summary> /// </summary>
public int ParticleLimit { get; set; } = 8192; public int ParticleLimit { get; set; } = short.MaxValue;
/// <summary> /// <summary>
/// List of particle emitters created for this ParticleSystem. /// List of particle emitters created for this ParticleSystem.
@@ -245,8 +233,6 @@ public class ParticleSystem : IUpdatableSystem, IDisposable
/// </summary> /// </summary>
public ParticleSystem() public ParticleSystem()
{ {
_particleIndex = ParticleLimit - 1;
_particlePositions = new Vector2[ParticleLimit]; _particlePositions = new Vector2[ParticleLimit];
_particleVelocities = new Vector2[ParticleLimit]; _particleVelocities = new Vector2[ParticleLimit];
_particleLifetimes = new float[ParticleLimit]; _particleLifetimes = new float[ParticleLimit];
@@ -315,20 +301,14 @@ public class ParticleSystem : IUpdatableSystem, IDisposable
} }
public void Dispose() public void Dispose()
{
CleanupParticles();
}
private void CleanupParticles()
{ {
Array.Clear(_particlePositions); Array.Clear(_particlePositions);
Array.Clear(_particleVelocities); Array.Clear(_particleVelocities);
Array.Clear(_particleLifetimes); Array.Clear(_particleLifetimes);
} }
// private Particle[] _particles;
private Vector2[] _particlePositions, _particleVelocities; private Vector2[] _particlePositions, _particleVelocities;
private float[] _particleLifetimes; private float[] _particleLifetimes;
private int _particleIndex;
private int _emitterSliceOffset; private int _emitterSliceOffset;
private List<ParticleEmitter> _emitters = new(); private List<ParticleEmitter> _emitters = new();

View File

@@ -34,25 +34,33 @@ namespace Voile
public static Color Green = new(0x00FF00); public static Color Green = new(0x00FF00);
public static Color Red = new(0xFF0000); public static Color Red = new(0xFF0000);
public float R { get; set; } public byte R { get; set; }
public float G { get; set; } public byte G { get; set; }
public float B { get; set; } public byte B { get; set; }
public float A { get; set; } = 1.0f; public byte A { get; set; } = 255;
public int Argb public int Argb
{ {
get get
{ {
int a = (int)Math.Round(A * 255f) << 24; int a = A << 24;
int r = (int)Math.Round(R * 255f) << 16; int r = R << 16;
int g = (int)Math.Round(G * 255f) << 8; int g = G << 8;
int b = (int)Math.Round(B * 255f); int b = B;
return a | r | g | b; return a | r | g | b;
} }
} }
public Color(float r, float g, float b, float a) public Color(float r, float g, float b, float a = 1.0f)
{
R = (byte)Math.Clamp(r * 255, 0, 255);
G = (byte)Math.Clamp(g * 255, 0, 255);
B = (byte)Math.Clamp(b * 255, 0, 255);
A = (byte)Math.Clamp(a * 255, 0, 255);
}
public Color(byte r, byte g, byte b, byte a = 255)
{ {
R = r; R = r;
G = g; G = g;
@@ -60,22 +68,16 @@ namespace Voile
A = a; A = a;
} }
public Color(byte r, byte g, byte b, byte a)
{
R = r / 255f;
G = g / 255f;
B = b / 255f;
A = a / 255f;
}
public Color(int hex) public Color(int hex)
{ {
A = 1.0f; A = 255; // Default alpha to 255 if not provided
B = (hex & 0xFF) / 255.0f; B = (byte)(hex & 0xFF);
hex >>= 8; G = (byte)((hex >> 8) & 0xFF);
G = (hex & 0xFF) / 255.0f; R = (byte)((hex >> 16) & 0xFF);
hex >>= 8; if (hex > 0xFFFFFF) // If the hex value includes alpha
R = (hex & 0xFF) / 255.0f; {
A = (byte)((hex >> 24) & 0xFF);
}
} }
public static Color FromHexString(string hex) public static Color FromHexString(string hex)
@@ -104,20 +106,18 @@ namespace Voile
public Color Lightened(float amount) public Color Lightened(float amount)
{ {
var result = this; var result = this;
result.R = result.R + (1.0f - result.R) * amount; result.R = (byte)Math.Min(255, R + (255 - R) * amount);
result.G = result.G + (1.0f - result.G) * amount; result.G = (byte)Math.Min(255, G + (255 - G) * amount);
result.B = result.B + (1.0f - result.B) * amount; result.B = (byte)Math.Min(255, B + (255 - B) * amount);
return result; return result;
} }
public Color Darkened(float amount) public Color Darkened(float amount)
{ {
var result = this; var result = this;
result.R = result.R * (1.0f - amount); result.R = (byte)(R * (1.0f - amount));
result.G = result.G * (1.0f - amount); result.G = (byte)(G * (1.0f - amount));
result.B = result.B * (1.0f - amount); result.B = (byte)(B * (1.0f - amount));
return result; return result;
} }

View File

@@ -17,10 +17,12 @@ namespace Voile
public static Color LerpColor(Color colorA, Color colorB, float t) public static Color LerpColor(Color colorA, Color colorB, float t)
{ {
var r = Lerp(colorA.R, colorB.R, t); t = Math.Clamp(t, 0f, 1f);
var g = Lerp(colorA.G, colorB.G, t);
var b = Lerp(colorA.B, colorB.B, t); byte r = (byte)(colorA.R + (colorB.R - colorA.R) * t);
var a = Lerp(colorA.A, colorB.A, t); byte g = (byte)(colorA.G + (colorB.G - colorA.G) * t);
byte b = (byte)(colorA.B + (colorB.B - colorA.B) * t);
byte a = (byte)(colorA.A + (colorB.A - colorA.A) * t);
return new Color(r, g, b, a); return new Color(r, g, b, a);
} }