• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
Telanor

GPU particles

39 posts in this topic

So I've been reading posts and articles about GPU particle systems and the general consensus for stateful systems seems to be to create 2 buffers, bind one as input, read from it in the vertex shader, then output to the other. And then in a second pass, you read from that second buffer to do your rendering.

In all the samples I've looked at (like the ones in the directx SDK), they create a couple hundred particles to start off with and then just reuse those same ones over and over. The particles never die off and new ones are never spawned. In one of the demos they teleport the particles back to the spawn point to make it look like they're dying off and being respawned.

While that works in a demo, I don't see how that can work in practice when you can have dozens of different emitters emitting different types of particles at different rates. Essentially the problem of choosing when and where to spawn particles has been completely avoided by all the samples I've seen. So my question is: how would you handling spawning particles when you have many different emitters with different properties?

Also, on the topic of gpu particles, I've seen some systems that use directcompute instead. Am I correct in assuming that only works on feature level 11 and up?
0

Share this post


Link to post
Share on other sites

A few day ago, I created a little GPU particle sample for SharpDX. The buffers start off completely empty. To spawn new particles, I copy them to a third vertex buffer. After the pass to update the particles from last frame, I just "draw" the third buffer to append the new particles to the buffer for the current frame as well.

 

Any properties that are "sufficiently random" like the particle's color or various flags, I put directly into the vertex. I didn't include this in the sample to avoid bloating it, but to handle properties that a lot of particles share, every particle also has a type ID that indexes into an array of particle types in the geometry shader.

Edited by lwm
2

Share this post


Link to post
Share on other sites
Very nice sample. So basically it has a small, 128 element buffer that the cpu can dump new particles into and calling the shader with that as the input results in it appending them to the existing list.

I was hoping to avoid the geometry shader since a lot of people have been saying it has poor performance, but I'm not sure I can see a way of doing something similar using vertex texture fetch instead. Your GS setup is so straightforward and simple though that I might just go with that.

Off-topic question: How do you disable vsync for sharpdx toolkit? I want to see what kind of performance this has.
0

Share this post


Link to post
Share on other sites


Also, on the topic of gpu particles, I've seen some systems that use directcompute instead. Am I correct in assuming that only works on feature level 11 and up?

 

Yes, that's correct. Or possibly feature level 10 depending on how you do it.

Last weekend I was knocking a little cs based gpu particle system up actually. it's not very hard.

you have 2 buffers which you use as append and consume buffers. I made them relatively large (a million particle wide in my test). Perform sim consuming from one and appending to the other. Then go through emitters and emit by append into the current buffer. Then to render use drawIsntancedIndirect using the size of the current buffer as indirect draw arguments, and expand in GS.

 

on my 580, sim and drawing of half a million particles ends up taking less than a ms. Not appropriate for everything, as all particle systems using the setup end up with the same sim code, diven by a few parameters, so if you want wildly different behaviour you might also want another approach for those. Also, it kind of gets hard to order particles with alpha objects in your scene, as they are all rendered in one batch. For this I will use it only for specific effects that need a large number of very small particles, and run a traditional cpu based particle sim for more standard fuzzy particles effects.

Here 's a quick video of what it looks like without textures / anything fancy:
http://www.youtube.com/watch?v=7PREQRapBiA
 

2

Share this post


Link to post
Share on other sites

Off-topic question: How do you disable vsync for sharpdx toolkit? I want to see what kind of performance this has.
this.graphicsDeviceManager.SynchronizeWithVerticalRetrace = false;
this.IsFixedTimeStep = false;

On my system (Nvidia GTX 460), updating and drawing 16k (1024 * 16) particles takes about 0.3 milliseconds.

Edited by lwm
0

Share this post


Link to post
Share on other sites


I was hoping to avoid the geometry shader since a lot of people have been saying it has poor performance,

 

Actually, on some cards at least 1:4 geometry expansion (typical particle scenario) is specifically handled so that it doesn't result as poor performance.

 

Cheers!

1

Share this post


Link to post
Share on other sites

 


I was hoping to avoid the geometry shader since a lot of people have been saying it has poor performance,

 

Actually, on some cards at least 1:4 geometry expansion (typical particle scenario) is specifically handled so that it doesn't result as poor performance.

 

Cheers!

 

In the particle storm sample in Hieroglyph 3 I use 1:4 expansion in the GS, and it still runs pretty darn fast.  It isn't perfect, but even on older DX11 hardware it handles 100k+ particles with no problems...

1

Share this post


Link to post
Share on other sites
It's actually the GS-based update pass I was hoping to avoid more so than the point->quad expansion. There were some posts I read here on gamedev about how using the GS ended up being slower than some other method. I don't exactly remember what it said, I'd have to go find the link again. In any case, I'm in the process of porting the sample to my project. Even if it uses the GS it'll be faster than the CPU implementation I have now.
0

Share this post


Link to post
Share on other sites


While that works in a demo, I don't see how that can work in practice when you can have dozens of different emitters emitting different types of particles at different rates.

Particle pooling works quite nicely. Just use some metadata like number of active paricles etc. and a usage factor or similar. Once you approach the upper limit of active particles(e.g. 80%), your emitters will start to emit less particles. This will auto-adjust quite nicely as long as you don't have some extreme situtations.

 


There were some posts I read here on gamedev about how using the GS ended up being slower than some other method.

Yes, the GS is not the best idea to instanciate larger models (trees, rocks ..). In this case standard instancing works better. But for very low expansion (1:3/1:4), the geometry shader seems to work much better, especially when considering what MJP said, that the GPU hardware supports this special case.

 

Particle systems often suffer more from pixel overdraw (alpha blending, no early-z etc.)  than from vertex performance. Still it depends on lot of factors  (blend mode, particle size, attributes: when simulating the particle on the GPU, you will have lot of attributes per vertex). Considering this, trimming the particle like what humus sugguested in one of his presentations (Graphics Gems for Games - Findings from Avalanche Studios) might give you an additional performance boost ( if needed smile.png  )

1

Share this post


Link to post
Share on other sites
Well... I ported it over but I can't see any particles. The DX debug layer is giving me a warning when I try to run the VS graphics debugger:
 
Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

D3D11 WARNING: ID3D11DeviceContext::Begin: Begin is being invoked on a Query, where the previous results have not been obtained with GetData. This is valid; but unusual. The previous results are being abandoned, and new Query results will be generated. [ EXECUTION WARNING #408: QUERY_BEGIN_ABANDONING_PREVIOUS_RESULTS]
I'm not even using queries. If I turn off the debug layer, it stops on the line above it:
 
Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

"Additional information: External component has thrown an exception."

Full code:
[spoiler]
using Craft.Camera;
using Craft.Main;
using Craft.Misc;
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using System;
using System.Runtime.InteropServices;
using Buffer = SharpDX.Direct3D11.Buffer;
using MapFlags = SharpDX.Direct3D11.MapFlags;

namespace Craft.Game.Particles
{
	public struct ParticleVertex
	{
		public static readonly InputElement[] VertexDeclaration = new[]
		{
			new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
			new InputElement("NORMAL", 0, Format.R32G32B32_Float, 0),
			new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 0),
			new InputElement("TEXCOORD", 0, Format.R32G32_Float, 0),
			new InputElement("TEXCOORD", 1, Format.R32_UInt, 0),
			new InputElement("TEXCOORD", 2, Format.R32G32_Float, 0)
		};

		public static readonly int SizeInBytes = Marshal.SizeOf(typeof(ParticleVertex));

		public Vector3 Position;
		public Vector3 Velocity;
		public Vector4 Color;
		public Vector2 TimerLifetime;
		public uint Flags;
		public Vector2 SizeStartEnd;

		public ParticleVertex(Vector3 position, Vector3 velocity, Vector4 color, Vector2 timerLifetime, uint flags, Vector2 sizeStartEnd) : this()
		{
			Position = position;
			Velocity = velocity;
			Color = color;
			TimerLifetime = timerLifetime;
			Flags = flags;
			SizeStartEnd = sizeStartEnd;
		}
	}

	public static class GPUParticles
	{
		private const int MaxParticles = 1048576;
		private const int MaxNew = 128;

		[Flags]
		enum ParticleFlags
		{
			Constrained = 1,
			FastFade = 2
		}

		private static Buffer spawnBuffer;
		private static Buffer drawFromBuffer;
		private static Buffer streamToBuffer;

		private static Effect effect;
		private static EffectPass updatePass;
		private static EffectPass renderPass;
		private static InputLayout layout;

		private static EffectMatrixVariable evView;
		private static EffectMatrixVariable evProjection;
		private static EffectMatrixVariable evLookAt;
		private static EffectScalarVariable evTime;
		private static EffectVectorVariable evCamDir;
		private static EffectVectorVariable evGravity;
		private static EffectShaderResourceVariable evTexture;
		private static ParticleVertex[] newParticles;
		private static int numNew;

		internal static void Initialize()
		{
			newParticles = new ParticleVertex[MaxNew];

			effect = ContentLoader.LoadEffect(@"Content\Effects\GPUParticles.fxo");
			updatePass = effect.GetTechniqueByName("UpdateTeq").GetPassByIndex(0);
			renderPass = effect.GetTechniqueByName("RenderTeq").GetPassByIndex(0);
			layout = ContentLoader.CreateInputLayout(updatePass, ParticleVertex.VertexDeclaration);

			spawnBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxNew, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0));
			drawFromBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));
			streamToBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));

			evView = effect.GetVariableByName("View").AsMatrix();
			evProjection = effect.GetVariableByName("Projection").AsMatrix();
			evLookAt = effect.GetVariableByName("LookAt").AsMatrix();
			evTime = effect.GetVariableByName("Time").AsScalar();
			evCamDir = effect.GetVariableByName("CamDir").AsVector();
			evGravity = effect.GetVariableByName("Gravity").AsVector();
			evTexture = effect.GetVariableByName("Texture").AsShaderResource();

			evTexture.SetResource(ContentLoader.LoadTexture(@"Content\Textures\particles\sparkle.png"));
		}

		public static void Spawn(Vector3 position, Vector3 velocity, Color color, float sizeStart, float sizeEnd, float lifetime, bool constrained, bool fastFade)
		{
			// discard particle if buffer is full
			if (numNew >= MaxNew)
				return;

			// create particle struct
			var v = new ParticleVertex(position, velocity, color.ToVector4(), new Vector2(0, lifetime), 0, new Vector2(sizeStart, sizeEnd));

			// set the particle's flags
			if (constrained) v.Flags |= (uint) ParticleFlags.Constrained;
			if (fastFade) v.Flags |= (uint) ParticleFlags.FastFade;

			// append to buffer
			newParticles[numNew++] = v;
		}

		internal static void Draw(ICamera camera)
		{
			var cameraForward = camera.Forward;
			var lookAtMatrix = Matrix.Billboard(Vector3.Zero, -cameraForward, Vector3.UnitY, cameraForward);

			evView.SetMatrix(camera.View);
			evProjection.SetMatrix(camera.Projection);
			evLookAt.SetMatrix(lookAtMatrix);
			evTime.Set((float) Core.GameTime.TotalGameTime.TotalSeconds);
			evCamDir.Set(cameraForward);
			evGravity.Set(9.81f);

			Advance();
			Render();
		}

		private static void Advance()
		{
			// Use particleDrawFrom as input and stream output to particleStreamTo
			Engine.Context.InputAssembler.InputLayout = layout;
			Engine.Context.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));
			Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

			updatePass.Apply(Engine.Context);

			// Update particles on the gpu
			Engine.Context.DrawAuto();

			// See if we have any newly-spawned particles waiting
			if (numNew > 0)
			{
				// Copy new particles to vertex buffer and draw them to append them to stream output target
				DataStream stream;
				Engine.Context.MapSubresource(spawnBuffer, MapMode.WriteDiscard, MapFlags.None, out stream);
				stream.WriteRange(newParticles);
				Engine.Context.UnmapSubresource(spawnBuffer, 0);

				Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(spawnBuffer, ParticleVertex.SizeInBytes, 0));
				Engine.Context.Draw(numNew, 0);
				numNew = 0;
			}

			Engine.Context.StreamOutput.SetTargets(null);

			// Swap vertex buffers
			Utilities.Swap(ref drawFromBuffer, ref streamToBuffer);
		}

		private static void Render()
		{
			// Since we just swapped the vertex buffers, particleDrawFrom contains the current state
			Engine.Context.OutputMerger.SetBlendState(DeviceStates.BlendStateAdditive);
			Engine.Context.OutputMerger.SetDepthStencilState(DeviceStates.DepthStencilStateNone);
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

			renderPass.Apply(Engine.Context);
			Engine.Context.DrawAuto();

			Engine.Context.GeometryShader.Set(null);
		}

		internal static void Shutdown()
		{
			spawnBuffer.Dispose();
			drawFromBuffer.Dispose();
			streamToBuffer.Dispose();
		}
	}
}
[/spoiler]
0

Share this post


Link to post
Share on other sites

Is there some official information about the point->quat expansion special-case handling?

0

Share this post


Link to post
Share on other sites

http://directtovideo.wordpress.com/2009/10/06/a-thoroughly-modern-particle-system/

Quite good reading about gpu particle systems using just dx9.

Just creating lot of particles is fun but not that usefull. Making particles look good and fitting for scene you usually want shading/shadowing match the rest of the scene. Also you want simulate some collisions and maybe even particle to particle forces. Also some sorting is must for good looking alpha blending.

0

Share this post


Link to post
Share on other sites

http://directtovideo.wordpress.com/2009/10/06/a-thoroughly-modern-particle-system/

Quite good reading about gpu particle systems using just dx9.

Just creating lot of particles is fun but not that usefull. Making particles look good and fitting for scene you usually want shading/shadowing match the rest of the scene. Also you want simulate some collisions and maybe even particle to particle forces. Also some sorting is must for good looking alpha blending.

For particle lighting, I generate a volume texture mapped to the camera projection with lighting information in each voxel (I use HL2 basis) which I can then sample with a few texture fetches in my particle PS to resolve reasonably accurate lighting. When I have too much overdraw/particles, I revert to just sampling a fixed approximated lighting value instead of resolving directional lighting. Works well, and is really quite fast, once you pay the cost to generate the map. (atm takes 2-3 ms in cs with 500 lights visible). Maybe it would be possible to move the generation to CPU while the gbuffer / whatnot is being rendered?

1

Share this post


Link to post
Share on other sites

Well... I ported it over but I can't see any particles. The DX debug layer is giving me a warning when I try to run the VS graphics debugger:
 

Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

D3D11 WARNING: ID3D11DeviceContext::Begin: Begin is being invoked on a Query, where the previous results have not been obtained with GetData. This is valid; but unusual. The previous results are being abandoned, and new Query results will be generated. [ EXECUTION WARNING #408: QUERY_BEGIN_ABANDONING_PREVIOUS_RESULTS]
I'm not even using queries. If I turn off the debug layer, it stops on the line above it:
 
Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

"Additional information: External component has thrown an exception."
Full code:
[spoiler]
using Craft.Camera;
using Craft.Main;
using Craft.Misc;
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using System;
using System.Runtime.InteropServices;
using Buffer = SharpDX.Direct3D11.Buffer;
using MapFlags = SharpDX.Direct3D11.MapFlags;

namespace Craft.Game.Particles
{
	public struct ParticleVertex
	{
		public static readonly InputElement[] VertexDeclaration = new[]
		{
			new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
			new InputElement("NORMAL", 0, Format.R32G32B32_Float, 0),
			new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 0),
			new InputElement("TEXCOORD", 0, Format.R32G32_Float, 0),
			new InputElement("TEXCOORD", 1, Format.R32_UInt, 0),
			new InputElement("TEXCOORD", 2, Format.R32G32_Float, 0)
		};

		public static readonly int SizeInBytes = Marshal.SizeOf(typeof(ParticleVertex));

		public Vector3 Position;
		public Vector3 Velocity;
		public Vector4 Color;
		public Vector2 TimerLifetime;
		public uint Flags;
		public Vector2 SizeStartEnd;

		public ParticleVertex(Vector3 position, Vector3 velocity, Vector4 color, Vector2 timerLifetime, uint flags, Vector2 sizeStartEnd) : this()
		{
			Position = position;
			Velocity = velocity;
			Color = color;
			TimerLifetime = timerLifetime;
			Flags = flags;
			SizeStartEnd = sizeStartEnd;
		}
	}

	public static class GPUParticles
	{
		private const int MaxParticles = 1048576;
		private const int MaxNew = 128;

		[Flags]
		enum ParticleFlags
		{
			Constrained = 1,
			FastFade = 2
		}

		private static Buffer spawnBuffer;
		private static Buffer drawFromBuffer;
		private static Buffer streamToBuffer;

		private static Effect effect;
		private static EffectPass updatePass;
		private static EffectPass renderPass;
		private static InputLayout layout;

		private static EffectMatrixVariable evView;
		private static EffectMatrixVariable evProjection;
		private static EffectMatrixVariable evLookAt;
		private static EffectScalarVariable evTime;
		private static EffectVectorVariable evCamDir;
		private static EffectVectorVariable evGravity;
		private static EffectShaderResourceVariable evTexture;
		private static ParticleVertex[] newParticles;
		private static int numNew;

		internal static void Initialize()
		{
			newParticles = new ParticleVertex[MaxNew];

			effect = ContentLoader.LoadEffect(@"Content\Effects\GPUParticles.fxo");
			updatePass = effect.GetTechniqueByName("UpdateTeq").GetPassByIndex(0);
			renderPass = effect.GetTechniqueByName("RenderTeq").GetPassByIndex(0);
			layout = ContentLoader.CreateInputLayout(updatePass, ParticleVertex.VertexDeclaration);

			spawnBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxNew, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0));
			drawFromBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));
			streamToBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));

			evView = effect.GetVariableByName("View").AsMatrix();
			evProjection = effect.GetVariableByName("Projection").AsMatrix();
			evLookAt = effect.GetVariableByName("LookAt").AsMatrix();
			evTime = effect.GetVariableByName("Time").AsScalar();
			evCamDir = effect.GetVariableByName("CamDir").AsVector();
			evGravity = effect.GetVariableByName("Gravity").AsVector();
			evTexture = effect.GetVariableByName("Texture").AsShaderResource();

			evTexture.SetResource(ContentLoader.LoadTexture(@"Content\Textures\particles\sparkle.png"));
		}

		public static void Spawn(Vector3 position, Vector3 velocity, Color color, float sizeStart, float sizeEnd, float lifetime, bool constrained, bool fastFade)
		{
			// discard particle if buffer is full
			if (numNew >= MaxNew)
				return;

			// create particle struct
			var v = new ParticleVertex(position, velocity, color.ToVector4(), new Vector2(0, lifetime), 0, new Vector2(sizeStart, sizeEnd));

			// set the particle's flags
			if (constrained) v.Flags |= (uint) ParticleFlags.Constrained;
			if (fastFade) v.Flags |= (uint) ParticleFlags.FastFade;

			// append to buffer
			newParticles[numNew++] = v;
		}

		internal static void Draw(ICamera camera)
		{
			var cameraForward = camera.Forward;
			var lookAtMatrix = Matrix.Billboard(Vector3.Zero, -cameraForward, Vector3.UnitY, cameraForward);

			evView.SetMatrix(camera.View);
			evProjection.SetMatrix(camera.Projection);
			evLookAt.SetMatrix(lookAtMatrix);
			evTime.Set((float) Core.GameTime.TotalGameTime.TotalSeconds);
			evCamDir.Set(cameraForward);
			evGravity.Set(9.81f);

			Advance();
			Render();
		}

		private static void Advance()
		{
			// Use particleDrawFrom as input and stream output to particleStreamTo
			Engine.Context.InputAssembler.InputLayout = layout;
			Engine.Context.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));
			Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

			updatePass.Apply(Engine.Context);

			// Update particles on the gpu
			Engine.Context.DrawAuto();

			// See if we have any newly-spawned particles waiting
			if (numNew > 0)
			{
				// Copy new particles to vertex buffer and draw them to append them to stream output target
				DataStream stream;
				Engine.Context.MapSubresource(spawnBuffer, MapMode.WriteDiscard, MapFlags.None, out stream);
				stream.WriteRange(newParticles);
				Engine.Context.UnmapSubresource(spawnBuffer, 0);

				Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(spawnBuffer, ParticleVertex.SizeInBytes, 0));
				Engine.Context.Draw(numNew, 0);
				numNew = 0;
			}

			Engine.Context.StreamOutput.SetTargets(null);

			// Swap vertex buffers
			Utilities.Swap(ref drawFromBuffer, ref streamToBuffer);
		}

		private static void Render()
		{
			// Since we just swapped the vertex buffers, particleDrawFrom contains the current state
			Engine.Context.OutputMerger.SetBlendState(DeviceStates.BlendStateAdditive);
			Engine.Context.OutputMerger.SetDepthStencilState(DeviceStates.DepthStencilStateNone);
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

			renderPass.Apply(Engine.Context);
			Engine.Context.DrawAuto();

			Engine.Context.GeometryShader.Set(null);
		}

		internal static void Shutdown()
		{
			spawnBuffer.Dispose();
			drawFromBuffer.Dispose();
			streamToBuffer.Dispose();
		}
	}
}
[/spoiler]

 

 

I'm currently having this exact same issue in my SlimDX code. Did you ever find a solution to that problem?

0

Share this post


Link to post
Share on other sites

A few day ago, I created a little GPU particle sample for SharpDX. The buffers start off completely empty. To spawn new particles, I copy them to a third vertex buffer. After the pass to update the particles from last frame, I just "draw" the third buffer to append the new particles to the buffer for the current frame as well.

 

Any properties that are "sufficiently random" like the particle's color or various flags, I put directly into the vertex. I didn't include this in the sample to avoid bloating it, but to handle properties that a lot of particles share, every particle also has a type ID that indexes into an array of particle types in the geometry shader.

Hello!  I  have  see  your  sample !  It's  very Good  But  I use  SharpDX11 to do this, not  Tookit!  The  Shader  your  sample  can't  recognize about

"StreamOutput = "SV_POSITION.xyz; NORMAL.xyz; COLOR.xyzw; TEXCOORD0.xy; TEXCOORD1.x; TEXCOORD2.xy";

 StreamOutputRasterizedStream = 0;"

 

I  can't  find  right  way  to  express  this  in  Shader model5.0  of Technique11!

 

Oh!   Sorry !  My English  is  bad!  hople you can  understand  me!  if  you  can  help  me  I am Very  thanks  to  you!

0

Share this post


Link to post
Share on other sites

Well... I ported it over but I can't see any particles. The DX debug layer is giving me a warning when I try to run the VS graphics debugger:
 

Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

D3D11 WARNING: ID3D11DeviceContext::Begin: Begin is being invoked on a Query, where the previous results have not been obtained with GetData. This is valid; but unusual. The previous results are being abandoned, and new Query results will be generated. [ EXECUTION WARNING #408: QUERY_BEGIN_ABANDONING_PREVIOUS_RESULTS]
I'm not even using queries. If I turn off the debug layer, it stops on the line above it:
 
Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

"Additional information: External component has thrown an exception."
Full code:
[spoiler]
using Craft.Camera;
using Craft.Main;
using Craft.Misc;
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using System;
using System.Runtime.InteropServices;
using Buffer = SharpDX.Direct3D11.Buffer;
using MapFlags = SharpDX.Direct3D11.MapFlags;

namespace Craft.Game.Particles
{
	public struct ParticleVertex
	{
		public static readonly InputElement[] VertexDeclaration = new[]
		{
			new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
			new InputElement("NORMAL", 0, Format.R32G32B32_Float, 0),
			new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 0),
			new InputElement("TEXCOORD", 0, Format.R32G32_Float, 0),
			new InputElement("TEXCOORD", 1, Format.R32_UInt, 0),
			new InputElement("TEXCOORD", 2, Format.R32G32_Float, 0)
		};

		public static readonly int SizeInBytes = Marshal.SizeOf(typeof(ParticleVertex));

		public Vector3 Position;
		public Vector3 Velocity;
		public Vector4 Color;
		public Vector2 TimerLifetime;
		public uint Flags;
		public Vector2 SizeStartEnd;

		public ParticleVertex(Vector3 position, Vector3 velocity, Vector4 color, Vector2 timerLifetime, uint flags, Vector2 sizeStartEnd) : this()
		{
			Position = position;
			Velocity = velocity;
			Color = color;
			TimerLifetime = timerLifetime;
			Flags = flags;
			SizeStartEnd = sizeStartEnd;
		}
	}

	public static class GPUParticles
	{
		private const int MaxParticles = 1048576;
		private const int MaxNew = 128;

		[Flags]
		enum ParticleFlags
		{
			Constrained = 1,
			FastFade = 2
		}

		private static Buffer spawnBuffer;
		private static Buffer drawFromBuffer;
		private static Buffer streamToBuffer;

		private static Effect effect;
		private static EffectPass updatePass;
		private static EffectPass renderPass;
		private static InputLayout layout;

		private static EffectMatrixVariable evView;
		private static EffectMatrixVariable evProjection;
		private static EffectMatrixVariable evLookAt;
		private static EffectScalarVariable evTime;
		private static EffectVectorVariable evCamDir;
		private static EffectVectorVariable evGravity;
		private static EffectShaderResourceVariable evTexture;
		private static ParticleVertex[] newParticles;
		private static int numNew;

		internal static void Initialize()
		{
			newParticles = new ParticleVertex[MaxNew];

			effect = ContentLoader.LoadEffect(@"Content\Effects\GPUParticles.fxo");
			updatePass = effect.GetTechniqueByName("UpdateTeq").GetPassByIndex(0);
			renderPass = effect.GetTechniqueByName("RenderTeq").GetPassByIndex(0);
			layout = ContentLoader.CreateInputLayout(updatePass, ParticleVertex.VertexDeclaration);

			spawnBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxNew, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0));
			drawFromBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));
			streamToBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));

			evView = effect.GetVariableByName("View").AsMatrix();
			evProjection = effect.GetVariableByName("Projection").AsMatrix();
			evLookAt = effect.GetVariableByName("LookAt").AsMatrix();
			evTime = effect.GetVariableByName("Time").AsScalar();
			evCamDir = effect.GetVariableByName("CamDir").AsVector();
			evGravity = effect.GetVariableByName("Gravity").AsVector();
			evTexture = effect.GetVariableByName("Texture").AsShaderResource();

			evTexture.SetResource(ContentLoader.LoadTexture(@"Content\Textures\particles\sparkle.png"));
		}

		public static void Spawn(Vector3 position, Vector3 velocity, Color color, float sizeStart, float sizeEnd, float lifetime, bool constrained, bool fastFade)
		{
			// discard particle if buffer is full
			if (numNew >= MaxNew)
				return;

			// create particle struct
			var v = new ParticleVertex(position, velocity, color.ToVector4(), new Vector2(0, lifetime), 0, new Vector2(sizeStart, sizeEnd));

			// set the particle's flags
			if (constrained) v.Flags |= (uint) ParticleFlags.Constrained;
			if (fastFade) v.Flags |= (uint) ParticleFlags.FastFade;

			// append to buffer
			newParticles[numNew++] = v;
		}

		internal static void Draw(ICamera camera)
		{
			var cameraForward = camera.Forward;
			var lookAtMatrix = Matrix.Billboard(Vector3.Zero, -cameraForward, Vector3.UnitY, cameraForward);

			evView.SetMatrix(camera.View);
			evProjection.SetMatrix(camera.Projection);
			evLookAt.SetMatrix(lookAtMatrix);
			evTime.Set((float) Core.GameTime.TotalGameTime.TotalSeconds);
			evCamDir.Set(cameraForward);
			evGravity.Set(9.81f);

			Advance();
			Render();
		}

		private static void Advance()
		{
			// Use particleDrawFrom as input and stream output to particleStreamTo
			Engine.Context.InputAssembler.InputLayout = layout;
			Engine.Context.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));
			Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

			updatePass.Apply(Engine.Context);

			// Update particles on the gpu
			Engine.Context.DrawAuto();

			// See if we have any newly-spawned particles waiting
			if (numNew > 0)
			{
				// Copy new particles to vertex buffer and draw them to append them to stream output target
				DataStream stream;
				Engine.Context.MapSubresource(spawnBuffer, MapMode.WriteDiscard, MapFlags.None, out stream);
				stream.WriteRange(newParticles);
				Engine.Context.UnmapSubresource(spawnBuffer, 0);

				Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(spawnBuffer, ParticleVertex.SizeInBytes, 0));
				Engine.Context.Draw(numNew, 0);
				numNew = 0;
			}

			Engine.Context.StreamOutput.SetTargets(null);

			// Swap vertex buffers
			Utilities.Swap(ref drawFromBuffer, ref streamToBuffer);
		}

		private static void Render()
		{
			// Since we just swapped the vertex buffers, particleDrawFrom contains the current state
			Engine.Context.OutputMerger.SetBlendState(DeviceStates.BlendStateAdditive);
			Engine.Context.OutputMerger.SetDepthStencilState(DeviceStates.DepthStencilStateNone);
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

			renderPass.Apply(Engine.Context);
			Engine.Context.DrawAuto();

			Engine.Context.GeometryShader.Set(null);
		}

		internal static void Shutdown()
		{
			spawnBuffer.Dispose();
			drawFromBuffer.Dispose();
			streamToBuffer.Dispose();
		}
	}
}
[/spoiler]

 

Do you  solve  your  problem!  I  have  use  your  code in  another  XOBOX  forum    The  same  you  say  I  can't  see  any  partile   If  you  solve  the  problem  please Hlep I will  very  thanks  to  you! Sorry   My  english  is  very  bad!   hople you can  understand  me! 

0

Share this post


Link to post
Share on other sites

 

I  can't  find  right  way  to  express  this  in  Shader model5.0  of Technique11!

 

Here is MSDN's description of the Stream Out Syntax. You might also want to take a look at the ParticlesGS sample from the DirectX SDK.

 

Yes  I  has  see  it  But  it  does't  work    

The  modified code  is?

 

 GeometryShader pGSComp = CompileShader(gs_5_0, GS_Update());
 GeometryShader gsStreamOut = ConstructGSWithSO( pGSComp, "0:SV_POSITION.xyz;1:NORMAL.xyz;2:COLOR.xyzw;3:TEXCOORD0.xy;4:TEXCOORD1.x;5:TEXCOORD2.xy;",NULL,NULL,0);
technique11 UpdateTeq
{
    pass Pass0
    {
     SetVertexShader( CompileShader( vs_5_0, VS_Passthrough() ) );
     SetGeometryShader(gsStreamOut);
    SetPixelShader( NULL);
    }
}
 
It  senms  to  right  !  but VS Prompt Me!
  D:\Coding\???\ResourceEditer(2013.07.15DX11????)\ResourceEditer\ResourceEditer\bin\Debug\MoonLight.Material.ParticleEffect.fx(91,31): error X3000: parameter count mismatch (ConstructGSWithSO)
 
I  don't konw  why!  because MSDN  write like this!! Oh  !  I am  very  happy  you  can  reply Me!
0

Share this post


Link to post
Share on other sites

 

I  can't  find  right  way  to  express  this  in  Shader model5.0  of Technique11!

 

Here is MSDN's description of the Stream Out Syntax. You might also want to take a look at the ParticlesGS sample from the DirectX SDK.

 

I  use  the  same  modified  code  in your sample  It  also  can't  work!  I feel it's a simle  error  ! But  I  has  solve it  for  2  days...Now,Still  failed!  

0

Share this post


Link to post
Share on other sites

I'm currently having this exact same issue in my SlimDX code. Did you ever find a solution to that problem?


No I never found a solution. The error makes no sense to me and I can't find any info about it on google, so I'm stuck with CPU particles for the time being.
0

Share this post


Link to post
Share on other sites

 

I'm currently having this exact same issue in my SlimDX code. Did you ever find a solution to that problem?


No I never found a solution. The error makes no sense to me and I can't find any info about it on google, so I'm stuck with CPU particles for the time being.

 

Oh! I am  very  sad  to  hear  this.....  we  both  use  sharpdx11.......I also  can't  find  any info  about  this!!  

0

Share this post


Link to post
Share on other sites
The info is in the MSDN doc, you're just misreading the compiler error. Admittedly, setting up SO in the effect file is a little cryptic.
 

parameter count mismatch (ConstructGSWithSO)

There are up to four streams from your geometry shader you can select from, not just three. You're missing a NULL.

Also, if I get the sample right, it uses one output stream only, so you can omit the (optional) buffer index altogether (an index of >3 e.g. doesn't make sense anyway, you only got 4 output streams).

This should work (at least it compiles)

GeometryShader gsStreamOut = ConstructGSWithSO( pGSComp, "SV_POSITION.xyz;NORMAL.xyz;COLOR.xyzw;TEXCOORD0.xy;TEXCOORD1.x;TEXCOORD2.xy;",NULL,NULL,NULL,-1);
Note the -1 at the end. This is actually D3D11_SO_NO_RASTERIZED_STREAM, since you don't need rasterization for the particle update. Not sure this will work, try 0 first instead (PixelShader is NULL anyway).
0

Share this post


Link to post
Share on other sites

The info is in the MSDN doc, you're just misreading the compiler error. Admittedly, setting up SO in the effect file is a little cryptic.
 

parameter count mismatch (ConstructGSWithSO)

There are up to four streams from your geometry shader you can select from, not just three. You're missing a NULL.

Also, if I get the sample right, it uses one output stream only, so you can omit the (optional) buffer index altogether (an index of >3 e.g. doesn't make sense anyway, you only got 4 output streams).

This should work (at least it compiles)

GeometryShader gsStreamOut = ConstructGSWithSO( pGSComp, "SV_POSITION.xyz;NORMAL.xyz;COLOR.xyzw;TEXCOORD0.xy;TEXCOORD1.x;TEXCOORD2.xy;",NULL,NULL,NULL,-1);
Note the -1 at the end. This is actually D3D11_SO_NO_RASTERIZED_STREAM, since you don't need rasterization for the particle update. Not sure this will work, try 0 first instead (PixelShader is NULL anyway).

 

Tanks!  I have  write that like  you  said  But  sitll  failed!  I  find the  particle  but it only  one pixel!  I don't know  why   Can you analysis  it by your Experience! 

0

Share this post


Link to post
Share on other sites

There are two techniques (and two geometry shaders) in that example. One for the particle update, one for drawing. You probably misuse the update for drawing. As said, disable rasterization for the update technique.

Streamout uses point streams, that's why you get those single pixels. The draw pass expands those points to quads (two triangles).

 

Since I'm now caught here:

 

@Telanor: Two concerns: Grant your ParticleVertex a proper StructLayout (like in the original code), otherwise Marshal.SizeOf may go wrong. Also: Why is GPUParticles a static class ?

Edited by unbird
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0