Jump to content
  • Advertisement
Sign in to follow this  
trileletri

optimizing particle engine

This topic is 4014 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

here is how my particle systems work: i have objects like: enemy, bullet, player, explosion and each of them is assigned one or more temporary or permanent particle systems. i do not have particle manager. each particle system cooperates with its "owner" (bullet, enemy etc.). draw_particle_system call is made in owner.draw(). all calculating of vertex data is placed in owner.update(float delta_time). NVidia PerfHUD tells me there is a long idle GPU period and I don't have much of FPS when 4-5 objects are rendered with particle systems active. each particle system has its own vertex buffer. one lock call is places for vertex buffer and for index buffer per frame. how to speed up this? i know i need to partially lock vertex buffer and update only portions of vertex data but each vertex buffer holds no more then 12 vertices! this should be no problem... each particle system uses (alpha) textured billboards, i don't use point sprites. the question is, how should i optimize my bottleneck? here is the most important part of particlesystem.update() verts = (CustomVertex.PositionColoredTextured[])vb.Lock(0,LockFlags.Discard); int index = 0; Matrix m = new Matrix(); Matrix view = new Matrix(); view = de.Transform.View; view.Invert(); Matrix csPos1 = new Matrix(); Vector3 csPos = new Vector3(); foreach(Particle p in particles) { if(!p.isDead) { m.M11 = p.pos.X; m.M22 = p.pos.Y; m.M33 = p.pos.Z; m.M44 = 1.0f; csPos1 = view * m; csPos.X = -csPos1.M11; csPos.Y = -csPos1.M22; csPos.Z = -csPos1.M33; if(this.sysType == ParticleSystemType.smoke) { pos1 = Vector3.Add(csPos,new Vector3(-p.size,p.size,0)); pos2 = Vector3.Add(csPos,new Vector3(p.size,p.size,0)); pos3 = Vector3.Add(csPos,new Vector3(p.size,-p.size,0)); pos4 = Vector3.Add(csPos,new Vector3(-p.size,-p.size,0)); } else if(this.sysType == ParticleSystemType.jetfire) { Vector3 dirV = new Vector3(0,0,10.0f + (float)rnd.Next(80)); Vector3 newVector = Vector3.Add(p.pos, dirV); pos1 = Vector3.Add(csPos,new Vector3(-p.size,p.size,0)); pos2 = Vector3.Add(csPos,new Vector3(p.size,p.size,0)); Matrix m2 = new Matrix(); m2.M11 = newVector.X; m2.M22 = newVector.Y; m2.M33 = newVector.Z; m2.M44 = 1.0f; Matrix csPos11 = view * m2; Vector3 csPos3 = new Vector3(); csPos3.X = -csPos11.M11; csPos3.Y = -csPos11.M22; csPos3.Z = -csPos11.M33; pos3 = Vector3.Add(csPos3,new Vector3(p.size,-p.size,0)); pos4 = Vector3.Add(csPos3,new Vector3(-p.size,-p.size,0)); } else if(this.sysType == ParticleSystemType.debrissmoke) { pos1 = Vector3.Add(csPos,new Vector3(-p.size,p.size,0)); pos2 = Vector3.Add(csPos,new Vector3(p.size,p.size,0)); pos3 = Vector3.Add(csPos,new Vector3(p.size,-p.size,0)); pos4 = Vector3.Add(csPos,new Vector3(-p.size,-p.size,0)); } else if(this.sysType == ParticleSystemType.sparks) { //TODO: proper sparks!!! Vector3 dirV = Vector3.Scale(p.vel,-p.SparkLength); Vector3 newVector = Vector3.Add(p.pos,dirV); Matrix m2 = new Matrix(); m2.M11 = newVector.X; m2.M22 = newVector.Y; m2.M33 = newVector.Z; m2.M44 = 1.0f; Matrix csPos11 = view * m2; Vector3 csPos3 = new Vector3(); csPos3.X = csPos11.M11; csPos3.Y = csPos11.M22; csPos3.Z = csPos11.M33; if(rnd.NextDouble() > 0.5d) { pos1 = Vector3.Add(csPos3,new Vector3(-p.size,p.size,0)); pos2 = Vector3.Add(csPos3,new Vector3(p.size,p.size,0)); pos3 = Vector3.Add(csPos,new Vector3(p.size,-p.size,0)); pos4 = Vector3.Add(csPos,new Vector3(-p.size,-p.size,0)); } else { pos1 = Vector3.Add(csPos,new Vector3(-p.size,p.size,0)); pos2 = Vector3.Add(csPos,new Vector3(p.size,p.size,0)); pos3 = Vector3.Add(csPos3,new Vector3(p.size,-p.size,0)); pos4 = Vector3.Add(csPos3,new Vector3(-p.size,-p.size,0)); } } verts[index].SetPosition(pos1); verts[index + 1].SetPosition(pos2); verts[index + 2].SetPosition(pos3); verts[index + 3].SetPosition(pos4); float alphaChannel = p.LTC * 255.0f; col = System.Drawing.Color.FromArgb((int)alphaChannel,p.col); verts[index].Tu = 0.0f; verts[index].Tv = 0.0f; verts[index].Color = col.ToArgb(); verts[index + 1].Tu = 1.0f; verts[index + 1].Tv = 0.0f; verts[index + 1].Color = col.ToArgb(); verts[index + 2].Tu = 1.0f; verts[index + 2].Tv = 1.0f; verts[index + 2].Color = col.ToArgb(); verts[index + 3].Tu = 0.0f; verts[index + 3].Tv = 1.0f; verts[index + 3].Color = col.ToArgb(); index += 4; } } vb.Unlock(); this.vb.SetData(this.verts,0, LockFlags.None); index = 0; indices = (short[])ib.Lock(0,LockFlags.Discard); for(int i = 0; i < this.aliveParticles*4; i+=4) { indices[index] = (short)i; indices[index+1] = (short)(i+1); indices[index+2] = (short)(i+2); indices[index+3] = (short)(i); indices[index+4] = (short)(i+2); indices[index+5] = (short)(i+3); index += 6; } ib.Unlock(); ib.SetData(indices,0, LockFlags.None); and here is my particlesystem.draw() call: public void Draw(Device de, Vector3 ownerPos) { de.RenderState.ZBufferEnable = true; de.RenderState.ZBufferWriteEnable = false; de.VertexFormat = CustomVertex.PositionColoredTextured.Format; de.RenderState.CullMode = Cull.None; de.RenderState.Lighting = false; de.RenderState.AlphaBlendEnable = true; de.RenderState.SourceBlend = Blend.SourceAlpha; de.RenderState.DestinationBlend = Blend.InvSourceAlpha; if(this.aliveParticles > 0) { de.Indices = this.ib; de.SetStreamSource(0,this.vb,0); de.SetTexture(0,this.systemTexture); //de.Transform.World = Matrix.Identity; de.Transform.World = Matrix.Translation(ownerPos);//da ide do vlasnika :) de.TextureState[0].ColorOperation = TextureOperation.Modulate; de.TextureState[0].ColorArgument0 = TextureArgument.Diffuse; de.TextureState[0].ColorArgument1 = TextureArgument.TextureColor; de.TextureState[0].AlphaOperation = TextureOperation.Modulate; de.TextureState[0].AlphaArgument0 = TextureArgument.Diffuse; de.TextureState[0].AlphaArgument1 = TextureArgument.TextureColor; //de.DrawPrimitives(PrimitiveType.TriangleList,0,this.aliveParticles*2); de.DrawIndexedPrimitives(PrimitiveType.TriangleList,0,0,this.aliveParticles*4,0,this.aliveParticles * 2); } de.RenderState.AlphaBlendEnable = false; de.RenderState.Lighting = true; de.RenderState.ZBufferWriteEnable = true; de.RenderState.ZBufferEnable = true; }

Share this post


Link to post
Share on other sites
Advertisement
[ code ] [ /code ]


Anyhow why on earth would you make a 12 particle buffer?

"i know i need to partially lock vertex buffer and update only portions of vertex data but each vertex buffer holds no more then 12 vertices! this should be no problem..."

So increase the size, make it hold 1000+, make them all use the same buffer.
Having a seperate buffer for 12 particles just wont work..

Share this post


Link to post
Share on other sites
A few points:
  • First, please use [ source ] tags when posting large blocks of code. It makes it take up less vertical page space, colours the text correctly, and preserves whitespace.
  • You don't need to lock your index buffer each frame, you could use a static index buffer. The order of the vertices doesn't ever change, just the number of primitives. You could probably get down to one index buffer for all particle systems too, instead of one per system.
  • You really shouldn't always lock with Discard. That tells D3D to throw away the contents of the buffer, so the driver can give you a pointer to a new section of memory if it wants. Discarding every frame could affect performance. Instead, create a vertex buffer that's large enough for several frames (2-4 usually, probably with an upper limit on size), and lock it in chunks (As the SDK docs describe, and as I think you understand).
  • If all your particle systems have the same vertex format, then you should only have one vertex buffer. That reduces the number of vertex buffer and index buffer switches, which can allow you to reduce the number of draw calls too.
  • I'm not a C# person (I assume this is C#?), but if the SetData() function allows you to specify Write Only as a flag, you should use that.
  • NVPerfHUD won't be very accurate with so few particles. When you add more, the GPU will have more work to do and will spend less time idle. Currently, you're submitting tiny batches, and calling Draw[Indexed]Primitive[UP] is a very expensive thing to do, so the cost of the draw calls is more difficult to measure.

    Share this post


    Link to post
    Share on other sites
    Ok. I changed my code. It works but it needs some tunning.

    I create one large vertex buffer:

    [ source ]
    public void PrepareBuffers(Device de)
    {
    CustomVertex.PositionColoredTextured[] verts = new CustomVertex.PositionColoredTextured[VBSize];
    for(int i = 0; i < VBSize; i++)
    {
    verts = new CustomVertex.PositionColoredTextured(new Vector3(),0,0.0f,0.0f);
    }

    vb = new VertexBuffer(typeof(CustomVertex.PositionColoredTextured),VBSize,de,Usage.Dynamic,CustomVertex.PositionColoredTextured.Format,Pool.Default);
    vb.SetData(verts,0, LockFlags.None);
    int index = 0;
    short[] indices = new short[IBSize];
    for(int i = 0; i < VBSize; i+=4)
    {
    indices[index] = (short)i;
    indices[index+1] = (short)(i+1);
    indices[index+2] = (short)(i+2);
    indices[index+3] = (short)(i);
    indices[index+4] = (short)(i+2);
    indices[index+5] = (short)(i+3);
    index += 6;
    }
    ib = new IndexBuffer(typeof(short),IBSize,de,Usage.WriteOnly,Pool.Managed);
    ib.SetData(indices,0, LockFlags.None);
    }
    [ /source ]

    then I update particle positions (separate Update function),
    draw primitives, partially lock vertex buffer and update information in it, and unlock it:

    [ source ]
    public void DrawALLParticles(Device de, ArrayList bullets, ArrayList explosions)
    {
    if(NPD > 0)
    {

    de.RenderState.ZBufferEnable = true;
    de.RenderState.ZBufferWriteEnable = false;
    de.VertexFormat = CustomVertex.PositionColoredTextured.Format;
    de.RenderState.CullMode = Cull.None;
    de.RenderState.Lighting = false;

    de.RenderState.AlphaBlendEnable = true;
    de.RenderState.SourceBlend = Blend.SourceAlpha;
    de.RenderState.DestinationBlend = Blend.InvSourceAlpha;

    de.Indices = this.ib;
    de.SetStreamSource(0,this.vb,0);
    de.SetTexture(0,this.systemTexture);

    //de.Transform.World = Matrix.Translation(ownerPos);//da ide do vlasnika :)
    de.Transform.World = Matrix.Identity;

    de.TextureState[0].ColorOperation = TextureOperation.Modulate;
    de.TextureState[0].ColorArgument0 = TextureArgument.Diffuse;
    de.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;

    de.TextureState[0].AlphaOperation = TextureOperation.Modulate;
    de.TextureState[0].AlphaArgument0 = TextureArgument.Diffuse;
    de.TextureState[0].AlphaArgument1 = TextureArgument.TextureColor;
    if(startVertex - batchSize >= 0)
    {
    de.DrawIndexedPrimitives(PrimitiveType.TriangleList,startVertex - batchSize,0,12000,0,NPD*2);
    startVertex += batchSize;
    if(startVertex >= VBSize)
    {
    startVertex = 0;
    }
    }
    else if (startVertex == 0)
    {
    de.DrawIndexedPrimitives(PrimitiveType.TriangleList,startVertex,0,12000,0,NPD*2);
    startVertex += batchSize;
    if(startVertex >= VBSize)
    {
    startVertex = 0;
    }
    }
    //de.DrawPrimitives(PrimitiveType.TriangleList,0,this.aliveParticles*2);
    de.RenderState.AlphaBlendEnable = false;
    de.RenderState.Lighting = true;
    de.RenderState.ZBufferWriteEnable = true;
    de.RenderState.ZBufferEnable = true;
    }

    CustomVertex.PositionColoredTextured[] verts = (CustomVertex.PositionColoredTextured[])vb.Lock(startVertex * DXHelp.GetTypeSize(typeof(CustomVertex.PositionColoredTextured)),typeof(CustomVertex.PositionColoredTextured),(startVertex != 0) ? LockFlags.NoOverwrite : LockFlags.Discard,this.batchSize);
    int index = 0;
    NPD = 0;


    #region BULLETS
    foreach(bullet b in bullets)
    {
    if(!b.ReadyToRemove)
    {
    foreach(ParticleSystem ps in b.effects)
    {
    if(ps.aliveParticles > 0)
    {
    foreach(Particle p in ps.particles)
    {
    if(!p.isDead)
    {
    if(NPD >= 1000)
    break;
    NPD++;

    //Fill particle data here !!!!!!!
    }
    }
    }
    }
    }
    }
    #endregion
    #region EXPLOSIONS
    foreach(explosion e in explosions)
    {
    if(!e.ReadyToRemove)
    {
    foreach(particle p in e.particles)
    {
    foreach(ParticleSystem ps in p.effects)
    {
    if(ps.aliveParticles > 0)
    {
    foreach(Particle pp in ps.particles)
    {
    if(!pp.isDead)
    {
    if(NPD >= 1000)
    break;

    NPD++;
    //Fill particle data here !!!!!!!
    }
    }
    }
    }
    }
    }
    }

    #endregion



    vb.Unlock();
    }
    [ /source ]

    NPD is "Number of Particles to Draw" variable

    and also Fill Vertex data... I transform world coordinates into view using this block of code:
    [ source ]
    Matrix m = new Matrix();
    m.M11 = -p.pos.X;
    m.M22 = -p.pos.Y;
    m.M33 = -p.pos.Z;
    m.M44 = 1.0f;

    Matrix view = new Matrix();
    view = de.Transform.View;
    view.Invert();
    Matrix csPos1 = new Matrix();
    csPos1 = view * m;
    Vector3 csPos = new Vector3();
    csPos.X = -csPos1.M11;
    csPos.Y = -csPos1.M22;
    csPos.Z = -csPos1.M33;

    [ /source ]
    is this the right way of doing it in Managed DirectX and C#?

    Share this post


    Link to post
    Share on other sites
    Unless you're targeting very old GPU's, you should be using some form of instancing to draw your particles. Locking a vertex buffer and modifying the positions manually is a sure-fire way to cause your GPU to stall, unless you double-buffer your VB.

    Humus has a good demo on particle instancing you should really check out. It's in C++, but you shouldn't have a problem seeing what he's doing.

    Share this post


    Link to post
    Share on other sites
    Sign in to follow this  

    • Advertisement
    ×

    Important Information

    By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

    We are the game development community.

    Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

    Sign me up!