Fractal Softworks Forum

Please login or register.

Login with username, password and session length
Advanced search  

News:

Starsector 0.98a is out! (03/27/25)

Pages: [1] 2 3 4

Author Topic: [0.98a] Particle Engine (5/25/25)  (Read 115583 times)

float

  • Captain
  • ****
  • Posts: 469
    • View Profile
[0.98a] Particle Engine (5/25/25)
« on: April 10, 2023, 06:49:27 PM »

Particle Engine
 


A library that implements a stateless, instanced particle system.

Requires a system that supports OpenGL 3.3.

What is this?
This is a utility mod and a tool. Downloading it and adding it to your game as-is does nothing. It is intended to provide a fast, relatively simple, and relatively up-to-date system for generating, allocating, and rendering particles.
[close]

Why does this exist?
Vanilla particles are CPU-bound (individual particles are simulated on the CPU), and essentially the entire game only utilizes a single CPU core. Furthermore, each particle has its own rendering loop. To keep performance reasonable, vanilla Starsector has a fairly low particle limit, after which old particles start getting replaced.

This mod uses slightly more modern (but not actually all that modern) techniques to minimize CPU strain. Most of the work, including simulation, is done on the GPU. The CPU doesn't keep track of individual particles. The particle engine is a singleton object (accessed via static methods) that performs a single draw call per particle type (every particle with the same sprite and blend mode has the same type) per frame. The result is a much more performant particle system.

The part of the vanilla particle system that is exposed and usable by modders is highly limited. As a modder, you're essentially only easily able to add a few types of particles, all via i.e. Global.getCombatEngine().addSmoothParticle. It's also possible to use a CombatEntityPluginWithParticles, though that has its own limitations -- you're forced to use a 4x4 sprite sheet, the plugin has its own state that must be initialized and updated, and you have to set the properties of each particle using a prev pointer. It also seems to be partially broken, i.e. endSizeMult doesn't actually seem to work, and particles either grow or shrink at a fixed rate.
[close]

How do I use this?
The starting point of all particle generation is the Particles class. There are essentially just three steps:
  • Create an emitter using Particles.initialize. This returns a default Emitter object.
  • Set the emitter's properties. Here you can set and randomize particles' positions, velocities, accelerations, sizes, colors, shift their colors over time, add radial motion, revolution, and so on.
  • Generate the particles using Particles.burst or Particles.stream, passing in an emitter whose properties you want to use.
There is also a javadoc bundled with the zip download that explains the function of each class and method in more detail.
[close]

What are the limitations?
Because particles are simulated on the GPU, they are also stateless (technically it's possible to store state in, e.g. an SSBO, but that's not relevant for now). This means that they don't interact with the game world -- once they're generated, their trajectories are fixed. As a result, particles won't be able to interact with ships or projectiles in either direction -- they can't affect the game state and they can't be affected by the game state. Still, because emitters are managed by the CPU, it's possible to modify emitters according to the game state, so a large variety of effects are nevertheless possible.

Performance is GPU dependent, so PCs with poor graphics cards or low VRAM might experience stuttering or drop outs with obscene numbers of particles. GPU code is also finicky, so with this early version you may run into system-specific issues or bugs.
[close]

Additional features
It's possible to read and write emitter properties from and to JSON files. To read an emitter from a JSON file, simply use Emitter.fromJSON in conjunction with Global.getSettings().loadJSON. For an example emitter JSON, see data/particleengine/sample_emitter.json.

Writing emitters to file is a bit tricker due to file IO limitations put in place by Starsector's custom class loader. The particleengine.json file in the mod's root directory contains the directory that saved emitters should be written to. By default, this is ../saved_emitters, i.e. the saved_emitters directory in the Starsector root. If this directory doesn't exist, you will have to create it manually before any write operations will work.

The JSON functionality can be a valuable tool when designing particle effects as changes to the JSON file can be effected without having to restart Starsector. In released code, it's not recommended to use writeJSON, and, if intending to use readJSON, the files should be batch-read at some initialization stage and cached for later reuse.
[close]

Custom emitters
While the default Emitter can generate a variety of effects already, it is possible to make your own emitters by implementing IEmitter, or extending the BaseIEmitter class. This is helpful in creating emitters with inter-particle dependencies, i.e. particles whose properties depend on other properties in a non-random way.

You will have to implement the initParticle function, which takes in the index of the particle in the burst or stream and outputs a ParticleData that is passed into the vertex shader. The below sample emitter replicates the vanilla muzzle flash visual, whereby particles' velocities are directed outwards and dependent on their initial distance from the emitter origin:

Muzzle flash emitter code

public class MuzzleFlashEmitter extends BaseIEmitter {

    private final Vector2f location;
    private float angle, arc, range, minLife, maxLife, minSize, maxSize, velocityScale;
    private final float[] color = new float[] {1f, 1f, 1f, 1f};
    private CombatEntityAPI anchor;

    public MuzzleFlashEmitter() {
        location = new Vector2f();
        angle = arc = range = 0f;
        minLife = maxLife = 0.5f;
        minSize = 20f;
        maxSize = 30f;
        velocityScale = 1f;
    }

    @Override
    public SpriteAPI getSprite() {
        return particleengine.Utils.getLoadedSprite("graphics/fx/particlealpha64sq.png");
    }

    public MuzzleFlashEmitter anchor(CombatEntityAPI anchor) {
        this.anchor = anchor;
        return this;
    }

    public MuzzleFlashEmitter location(Vector2f location) {
        this.location.set(location);
        return this;
    }

    public MuzzleFlashEmitter angle(float angle) {
        this.angle = angle;
        return this;
    }

    public MuzzleFlashEmitter arc(float arc) {
        this.arc = arc;
        return this;
    }

    public MuzzleFlashEmitter range(float range) {
        this.range = range;
        return this;
    }

    public MuzzleFlashEmitter life(float minLife, float maxLife) {
        this.minLife = minLife;
        this.maxLife = maxLife;
        return this;
    }

    public MuzzleFlashEmitter size(float minSize, float maxSize) {
        this.minSize = minSize;
        this.maxSize = maxSize;
        return this;
    }

    public MuzzleFlashEmitter color(float r, float g, float b, float a) {
        color[0] = r;
        color[1] = g;
        color[2] = b;
        color[3] = a;
        return this;
    }

    public MuzzleFlashEmitter velocityScale(float velocityScale) {
        this.velocityScale = velocityScale;
        return this;
    }

    @Override
    public Vector2f getLocation() {
        return location;
    }

    @Override
    protected ParticleData initParticle(int i) {
        ParticleData data = new ParticleData();

        // Life uniformly random between minLife and maxLife
        float life = randBetween(minLife, maxLife);
        data.life(life).fadeTime(0f, life);

        float theta = angle + randBetween(-arc / 2f, arc / 2f);
        float r = range * (float) Math.sqrt(Misc.random.nextFloat());
        Vector2f pt = new Vector2f(r*(float)Math.cos(theta*Misc.RAD_PER_DEG), r*(float)Math.sin(theta*Misc.RAD_PER_DEG));
        // Velocity is proportional to distance from center
        Vector2f vel = Misc.getUnitVectorAtDegreeAngle(theta);
        vel.scale(velocityScale);
        vel.scale(r);
        // Add the anchor's velocity, if it exists
        if (anchor != null) {
            Vector2f.add(anchor.getVelocity(), vel, vel);
        }
        data.offset(pt).velocity(vel);

        // Size uniformly random between minSize and maxSize
        float size = randBetween(minSize, maxSize);
        data.size(size, size);

        // Color
        data.color(color);

        return data;
    }

    public float randBetween(float a, float b) {
        return Misc.random.nextFloat() * (b - a) + a;
    }
}

[close]

[close]

Sample emitters with code
Ion explosion
Effect

[close]
Code
       
        Emitter emitter = Particles.initialize(pt, "graphics/fx/explosion1.png");
        emitter.circleOffset(30f, 50f);
        emitter.life(0.75f, 1.25f);
        emitter.fadeTime(0.1f, 0.1f, 0.3f, 0.5f);
        emitter.facing(0f, 360f);
        emitter.turnRate(-50f, 50f);
        emitter.turnAcceleration(-50f, 50f);
        emitter.circleVelocity(100f, 100f);
        emitter.radialAcceleration(-50f, -100f);
        emitter.revolutionRate(-20f, 20f);
        emitter.color(colorIn);
        emitter.randomHSVA(20f, 0.1f, 0f, 0f);
        emitter.colorShiftHSVA(0f, -0.3f, -0.3f, -0.05f);
        emitter.size(150f, 200f);
        emitter.growthRate(100f, 150f);
        emitter.growthAcceleration(-50f, -75f);
        Particles.burst(emitter, 50);

        Emitter ringEmitter = Particles.initialize(pt, "graphics/fx/custom_ring.png"); // Just a simple white ring
        ringEmitter.setAxis(proj.getFacing());
        ringEmitter.life(0.75f, 1f);
        ringEmitter.fadeTime(0.1f, 0.1f, 0.5f, 0.7f);
        ringEmitter.size(300f, 400f, 40f, 60f);
        ringEmitter.growthRate(400f, 500f);
        ringEmitter.growthAcceleration(-50f, -60f);
        ringEmitter.color(ringColorIn);
        ringEmitter.hueShift(-50f, 50f);
        ringEmitter.saturationShift(-0.2f, -0.2f);
        ringEmitter.facing(-55f, -35f);
        Particles.burst(ringEmitter, 10);
        ringEmitter.facing(35f, 55f);
        Particles.burst(ringEmitter, 10);

        Emitter arcEmitter = Particles.initialize(pt, "graphics/fx/custom_emp_arcs.png"); // Just emp_arcs.png with a darkening filter applied to border
        arcEmitter.life(0.25f, 0.3f);
        arcEmitter.fadeTime(0f, 0f, 0.15f, 0.25f);
        arcEmitter.size(350f, 350f, 300f, 300f);
        arcEmitter.growthRate(-40f, -80f);
        arcEmitter.turnRate(-10f, 10f);
        arcEmitter.facing(0f, 360f);
        arcEmitter.color(0.7f, 1f, 1f, 0.7f);
        arcEmitter.facing(0f, 360f);
        arcEmitter.alphaShift(-0.5f, -0.5f);
        Particles.stream(arcEmitter, 1, 50, 0.75f);

[close]
[close]

Simple trail
Effect

[close]
Code

        Emitter emitter = Particles.initialize(proj.getLocation(), "graphics/fx/particlealpha_textured.png");
        emitter.life(1f, 1.2f);
        emitter.fadeTime(0f, 0f, 0.5f, 0.5f);
        emitter.offset(-50f, -5f, 0f, 0f);
        emitter.velocity(-40f, -20f, 0f, 0f);
        emitter.color(1f, 0f, 1f, 0.8f);
        emitter.randomHSVA(40f, 0.2f, 0f, 0f);
        emitter.saturationShift(-1f, -1.5f);
        emitter.alphaShift(-0.6f, -0.6f);
        emitter.size(80f, 80f, 20f, 20f);
        emitter.growthRate(-50f, -50f, -8f, -8f);
        Particles.stream(emitter, 1, 100f, 5f, new Particles.StreamAction() {
            @Override
            public boolean apply(Emitter emitter) {
                // Set the emission location and axis to match the projectile's right before each burst of particles is generated.
                emitter.setLocation(proj.getLocation());
                emitter.setAxis(proj.getFacing());
                return !proj.isExpired();
            }
        });

[close]
[close]

Stress test with 1 million particles

WARNING: 55MB gif
Effect

[close]

Code
        Emitter emitter = Particles.initialize(pt);
        emitter.setSyncSize(true);
        emitter.setBlendMode(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL14.GL_FUNC_ADD);
        emitter.life(10f, 10f);
        emitter.fadeTime(0f, 0f, 0.5f, 0.5f);
        emitter.circleOffset(0f, 30f);
        emitter.colorHSVA(new float[] {0f, 0.5f, 1f, 1f});
        emitter.size(10f, 25f, 10f, 25f);
        emitter.growthRate(-10f, 10f, -10f, 10f);
        emitter.randomHSVA(360f, 1f, 0f, 0f);
        emitter.revolutionRate(-30f, 30f);
        emitter.colorShiftHSVA(-200f, 200f, -0.1f, 0.1f, -0.1f, 0.1f, -0.1f, -0.1f);
        final float[] amp = {200f};
        emitter.sinusoidalMotionX(-amp[0], amp[0], 0.25f, 0.25f, 0f, 0f);
        emitter.sinusoidalMotionY(-amp[0], amp[0], 0.25f, 0.25f, 0f, 0f);
        Particles.stream(emitter, 1000, 100000, 10f, new Particles.StreamAction() {
            @Override
            public boolean apply(Emitter emitter) {
                amp[0] += 2f;
                emitter.sinusoidalMotionX(-amp[0], amp[0], 0.25f, 0.25f, 0f, 0f);
                emitter.sinusoidalMotionY(-amp[0], amp[0], 0.25f, 0.25f, 0f, 0f);
                return true;
            }
        });

[close]

[close]
[close]

Version log
Here
[close]
« Last Edit: June 04, 2025, 10:22:39 AM by float »
Logged

Lukas04

  • Admiral
  • *****
  • Posts: 717
    • View Profile
Re: [0.95.1a] Particle Engine (4/10/23)
« Reply #1 on: April 11, 2023, 01:11:48 AM »

Seems quite interesting, being able to use Particles at reasonable quantities without immediate performance concerns would definitly be nice for some visual effects.

Does this work on just the Combat layer, or can it be used in both Combat and Campaign?

I also wonder if something like this wouldnt be better to integrate in to an existing Library aswell, this seems like something that would fit well in to the Utilities provided by MagicLib already, which already has miscellaneous utilities for in-combat graphical effects, and would probably see a quicker gain in useage through it aswell.

Otherwise looking forward on this progressing, always great to have people around that jump in to the horrifying depths of OpenGL to make it simpler for others.
« Last Edit: April 11, 2023, 01:20:19 AM by Lukas04 »
Logged

Nia Tahl

  • Admiral
  • *****
  • Posts: 839
  • AI in disguise
    • View Profile
Re: [0.95.1a] Particle Engine (4/10/23)
« Reply #2 on: April 11, 2023, 02:42:52 AM »

This seems neat, but I'm not sure I want to integrate another lib given the rather limited use cases for high particle counts in this game.
Logged
My mods: Tahlan Shipworks - ScalarTech Solutions - Trailer Moments
It's all in the presentation

tomatopaste

  • Captain
  • ****
  • Posts: 322
    • View Profile
Re: [0.95.1a] Particle Engine (4/10/23)
« Reply #3 on: April 11, 2023, 06:43:50 AM »

cool project, can be optimised using glDrawElementsInstanced instead, also does not support CombatEngineLayers
Logged

float

  • Captain
  • ****
  • Posts: 469
    • View Profile
Re: [0.95.1a] Particle Engine (4/10/23)
« Reply #4 on: April 11, 2023, 08:14:18 AM »

does not support CombatEngineLayers

Yeah I sort of forgot that CombatEngineLayers existed since the EveryFrameCombatPlugin makes no mention of it. Looks like I’d have to use a LayeredRenderingPlugin to actually make use of the layer information, which could be feasible, but OTOH what is the use case for particles that don’t render on top of in-game objects?

can be optimised using glDrawElementsInstanced instead

Why? That’s just an additional element buffer overhead considering that particles will never share vertices (by vertex I mean the entire 38-element array containing data for each particle). If you mean to index just the quad offsets, those aren’t even passed into the GPU — they’re just a const array defined in the vertex shader.
Logged

float

  • Captain
  • ****
  • Posts: 469
    • View Profile
Re: [0.95.1a] Particle Engine (4/10/23)
« Reply #5 on: April 11, 2023, 08:42:55 AM »

Does this work on just the Combat layer, or can it be used in both Combat and Campaign?

Just the combat layer.
Logged

Nia Tahl

  • Admiral
  • *****
  • Posts: 839
  • AI in disguise
    • View Profile
Re: [0.95.1a] Particle Engine (4/10/23)
« Reply #6 on: April 12, 2023, 02:06:54 AM »

but OTOH what is the use case for particles that don’t render on top of in-game objects?

Argument can be made for background visuals or implying volumetric effects by rendering both below and above ingame objects
Logged
My mods: Tahlan Shipworks - ScalarTech Solutions - Trailer Moments
It's all in the presentation

xenoargh

  • Admiral
  • *****
  • Posts: 5078
  • naively breaking things!
    • View Profile
Re: [0.95.1a] Particle Engine (4/11/23)
« Reply #7 on: April 13, 2023, 12:40:05 AM »

Super-neat project, and I hope that it gets used. I think a lot of people here just don't really appreciate what a custom particle system can do to make things look good. I'd strongly recommend writing a few basic "basic explosions with lots of debris" examples to show additive / normal blending methods used together. Also, the examples don't show it color-shifting over time; if that's missing, you might want to consider implementing that, it's important for a lot of basic FX stuff like "fire that looks like fire".
Logged
Please check out my SS projects :)
Xeno's Mod Pack

rogerbacon

  • Commander
  • ***
  • Posts: 154
    • View Profile
Re: [0.95.1a] Particle Engine (4/11/23)
« Reply #8 on: April 13, 2023, 03:51:19 PM »

This is going to be a great addition to the game. I'm going to try it this weekend.

What's the import line look like? I'm writing my classes in Notepad++ and there is no intellisense.

Also, is it possible to atach the emitter to an object in teh game, liek a projectile, to make the particles move with it? What event did you place your code in fro the trail emitter?
« Last Edit: April 13, 2023, 06:17:01 PM by rogerbacon »
Logged

float

  • Captain
  • ****
  • Posts: 469
    • View Profile
Re: [0.95.1a] Particle Engine (4/10/23)
« Reply #9 on: May 01, 2023, 01:22:39 PM »

Argument can be made for background visuals or implying volumetric effects by rendering both below and above ingame objects

Hmm, sure. Added.

This is going to be a great addition to the game. I'm going to try it this weekend.

What's the import line look like? I'm writing my classes in Notepad++ and there is no intellisense.

Also, is it possible to atach the emitter to an object in teh game, liek a projectile, to make the particles move with it? What event did you place your code in fro the trail emitter?

Sorry, was away for a bit. It's particleengine.Emitter and particleengine.Particles, assuming you've added the jar or the source as a library. I'd highly recommend using an IDE to manage libraries, builds, etc.

You can use Particles.stream with a StreamAction to perform custom actions in between bursts of particles, including setting the emitter's location to a new value. Use an anonymous class (not lambdas, as Starsector doesn't support language levels above 7) or just create your own class that implements Particles.StreamAction.
Logged

ApolloStarsector

  • Commander
  • ***
  • Posts: 149
    • View Profile
Re: [0.95.1a] Particle Engine (5/1/23)
« Reply #10 on: May 04, 2023, 06:05:20 PM »

Brillant idea. Anything that takes pressure off the single-CPU-core limitation is of the utmost importance! If only it were possible to apply this performance enhancement to all applicable vanilla particles.
Logged

Wispborne

  • Admiral
  • *****
  • Posts: 589
  • Discord: wispborne
    • View Profile
Re: [0.96a] Particle Engine (5/5/23)
« Reply #11 on: September 01, 2023, 11:08:53 AM »

Does this use the same version of OpenGL as vanilla? Are there any known additional hardware feature requirements beyond vanilla?

Some of my WIP content adds a *lot* of particles to the campaign layer and I've had to tone them down to avoid lag, so this might let me go wild. But...does it support the campaign layer out of the box, or would that require forking it?
Logged
Mod Manager: TriOS | Mod: Persean Chronicles | Tool: VRAM Estimator | Tool: Forum+Discord Mod Database
If I'm inactive for 3 months, anyone can use any of my work for anything (except selling it or its derivatives), but continuations must be renamed to avoid confusion.

float

  • Captain
  • ****
  • Posts: 469
    • View Profile
Re: [0.96a] Particle Engine (5/5/23)
« Reply #12 on: September 08, 2023, 10:06:42 AM »

Does this use the same version of OpenGL as vanilla? Are there any known additional hardware feature requirements beyond vanilla?

Some of my WIP content adds a *lot* of particles to the campaign layer and I've had to tone them down to avoid lag, so this might let me go wild. But...does it support the campaign layer out of the box, or would that require forking it?

It requires a version of OpenGL that supports GLSL 3.30 (so OpenGL 3.3+), which I believe is above the vanilla Starsector standard but should be supported by any graphics card made in the past 10 years or so. I'm not actually sure what version of OpenGL vanilla requires. I think I recall seeing some GL 2.0 stuff but I could be mistaken.

I'm using CombatLayeredRenderingPlugin to render the particles as that's a clean way of supporting CombatEngineLayers, and I assume that doesn't work for the campaign layer. So there's a good chance that adding particles in the campaign layer would require some additional work. But I could be wrong -- I've never tried rendering anything in the campaign layer and don't know what that would entail.
« Last Edit: September 08, 2023, 10:13:58 AM by float »
Logged

creature

  • Captain
  • ****
  • Posts: 418
    • View Profile
Re: [0.96a] Particle Engine (11/27/23)
« Reply #13 on: December 27, 2023, 12:18:04 PM »

Hi, when you get an error like this after calling .burst(), what does it mean? Ran out of memory perhaps?

11419407 [Thread-4] ERROR com.fs.starfarer.combat.CombatMain  - java.lang.NullPointerException
java.lang.NullPointerException: null
   at particleengine.ParticleAllocator.allocateParticles(ParticleAllocator.java:174) ~[?:?]
   at particleengine.Particles.burst(Particles.java:475) ~[?:?]
   at particleengine.Particles.burst(Particles.java:438) ~[?:?]
   at data.scripts.utils.yrxp_SpellcastUtils.spawnPEHitSpray(yrxp_SpellcastUtils.java:216) ~[?:?]
   at data.scripts.ai.projectiles.yrxp_ChainmineBombletAI.explodeFX(yrxp_ChainmineBombletAI.java:210) ~[?:?]
   at data.scripts.ai.projectiles.yrxp_ChainmineBombletAI.advance(yrxp_ChainmineBombletAI.java:103) ~[?:?]
   at com.fs.starfarer.combat.entities.Missile$MissileAIWrapper.advance(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.starfarer.combat.CombatEngine.advanceInner(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.starfarer.combat.CombatEngine.advance(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.starfarer.combat.CombatState.traverse(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.state.AppDriver.begin(Unknown Source) ~[fs.common_obf.jar:?]
   at com.fs.starfarer.combat.CombatMain.main(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.starfarer.StarfarerLauncher.o00000(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.starfarer.StarfarerLauncher$1.run(Unknown Source) ~[starfarer_obf.jar:?]
   at java.lang.Thread.run(Thread.java:750) [?:1.8.0_392]
Logged

float

  • Captain
  • ****
  • Posts: 469
    • View Profile
Re: [0.96a] Particle Engine (11/27/23)
« Reply #14 on: December 28, 2023, 07:53:32 PM »

Hi, when you get an error like this after calling .burst(), what does it mean? Ran out of memory perhaps?

11419407 [Thread-4] ERROR com.fs.starfarer.combat.CombatMain  - java.lang.NullPointerException
java.lang.NullPointerException: null
   at particleengine.ParticleAllocator.allocateParticles(ParticleAllocator.java:174) ~[?:?]
   at particleengine.Particles.burst(Particles.java:475) ~[?:?]
   at particleengine.Particles.burst(Particles.java:438) ~[?:?]
   at data.scripts.utils.yrxp_SpellcastUtils.spawnPEHitSpray(yrxp_SpellcastUtils.java:216) ~[?:?]
   at data.scripts.ai.projectiles.yrxp_ChainmineBombletAI.explodeFX(yrxp_ChainmineBombletAI.java:210) ~[?:?]
   at data.scripts.ai.projectiles.yrxp_ChainmineBombletAI.advance(yrxp_ChainmineBombletAI.java:103) ~[?:?]
   at com.fs.starfarer.combat.entities.Missile$MissileAIWrapper.advance(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.starfarer.combat.CombatEngine.advanceInner(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.starfarer.combat.CombatEngine.advance(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.starfarer.combat.CombatState.traverse(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.state.AppDriver.begin(Unknown Source) ~[fs.common_obf.jar:?]
   at com.fs.starfarer.combat.CombatMain.main(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.starfarer.StarfarerLauncher.o00000(Unknown Source) ~[starfarer_obf.jar:?]
   at com.fs.starfarer.StarfarerLauncher$1.run(Unknown Source) ~[starfarer_obf.jar:?]
   at java.lang.Thread.run(Thread.java:750) [?:1.8.0_392]

Looks like glMapBufferRange is returning null, which happens if it encounters an error. Out of memory is one of the possible errors.

I don't know how you encountered this issue, but if you'd like to probe into it further, you can modify ParticleAllocator.java, after line 173, test if existingBuffer is null and if so, use glGetError to get the error id. I'll also add a failsafe on my end for the next update.
Logged
Pages: [1] 2 3 4