4 using System.Collections.Generic;
6 using System.Runtime.InteropServices;
7 using SiliconStudio.Paradox.Effects.Modules;
8 using SiliconStudio.Paradox.Engine;
9 using SiliconStudio.Paradox.Games;
10 using SiliconStudio.Paradox.Graphics;
11 using SiliconStudio.Core;
12 using SiliconStudio.Core.Collections;
13 using SiliconStudio.Core.Mathematics;
14 using SiliconStudio.Paradox.Shaders;
18 namespace SiliconStudio.
Paradox.Effects
22 private int capacityCount;
24 private int updatersCount;
40 private Buffer globalBuffer;
44 private readonly List<ParticleUpdaterState> updatersToRemove;
46 private readonly List<ParticleUpdaterState> currentUpdaters;
48 private readonly RenderPassListEnumerator meshesToRender;
50 private EffectMesh effectMeshCopyToSortBuffer;
52 private EffectMesh effectMeshSort1Pass1;
53 private EffectMesh effectMeshSort1Pass2;
54 private EffectMesh[] effectMeshBitonicSort2;
55 private EffectMesh effectMeshRender;
57 private const int MaximumThreadPerGroup = 512;
58 private const int MaximumDepthLevel = 512;
60 private EffectOld effectCopyToSortBuffer;
61 private EffectOld effectBitonicSort1Pass1;
62 private EffectOld effectBitonicSort1Pass2;
64 private EffectOld[] effectBitonicSort2;
66 private int currentParticleCount = 0;
83 meshesToRender =
new RenderPassListEnumerator();
84 Updaters =
new TrackingCollection<ParticleEmitterComponent>();
85 currentUpdaters =
new List<ParticleUpdaterState>();
86 updatersToRemove =
new List<ParticleUpdaterState>();
87 Updaters.CollectionChanged += UpdatersOnCollectionChanged;
98 public int CapacityCount
102 return capacityCount;
107 throw new ArgumentException(
"CapacityCount must be a power of 2");
109 if (value < updatersCount)
110 throw new ArgumentException(
string.Format(
"Cannot change Maximum Count [{0}] to be lower than the sum of Updaters.MaximumCount [{1}]", value,
113 capacityCount = value;
117 public int StructureSize {
get; set; }
119 public string StructureName {
get; set; }
127 public bool EnableSorting {
get; set; }
137 public TrackingCollection<ParticleEmitterComponent> Updaters {
get;
private set; }
150 bitonicSort1Pass1 =
new RenderPass(
"BitonicSort1Pass1");
151 bitonicSort1Pass2 =
new RenderPass(
"BitonicSort1Pass2");
152 bitonicSort2Passes =
new RenderPass[(int)Math.Log(MaximumDepthLevel, 2) - 1];
153 effectBitonicSort2 =
new EffectOld[bitonicSort2Passes.Length];
162 if (
string.IsNullOrEmpty(StructureName) || StructureSize == 0)
163 throw new InvalidOperationException(
"StructureName and StructureSize must be setup on ParticlePlugin");
166 RenderPass.AddPass(updatePasses, copyPass, sortPass, renderPass);
169 effectCopyToSortBuffer = this.EffectSystemOld.BuildEffect(
"CopyToSortBuffer").Using(
170 new ComputeShaderPlugin(
new ShaderClassSource(
"ParticleSortInitializer"), MaximumThreadPerGroup, 1, 1)
173 Macros = {
new ShaderMacro(
"PARTICLE_STRUCT", StructureName) }
175 effectCopyToSortBuffer.KeepAliveBy(ActiveObjects);
176 effectCopyToSortBuffer.Parameters.AddSources(MainPlugin.ViewParameters);
177 effectMeshCopyToSortBuffer =
new EffectMesh(effectCopyToSortBuffer);
180 copyPass.StartPass.AddFirst = (context) =>
182 if (CapacityCount > 0)
185 context.GraphicsDevice.ClearReadWrite(sortBuffer,
new UInt4(0xFF7FFFFF));
190 effectBitonicSort1Pass1 = this.EffectSystemOld.BuildEffect(
"ParticleBitonicSort1-Pass1").Using(
191 new ComputeShaderPlugin(
new ShaderClassSource(
"ParticleBitonicSort1"), MaximumThreadPerGroup, 1, 1)
194 Macros = {
new ShaderMacro(
"PARTICLE_STRUCT", StructureName),
198 effectBitonicSort1Pass1.KeepAliveBy(
this);
199 effectMeshSort1Pass1 =
new EffectMesh(effectBitonicSort1Pass1);
202 effectBitonicSort1Pass2 = this.EffectSystemOld.BuildEffect(
"ParticleBitonicSort1-Pass2").Using(
203 new ComputeShaderPlugin(
new ShaderClassSource(
"ParticleBitonicSort1"), MaximumThreadPerGroup, 1, 1)
206 Macros = {
new ShaderMacro(
"PARTICLE_STRUCT", StructureName),
210 effectBitonicSort1Pass2.KeepAliveBy(
this);
211 effectMeshSort1Pass2 =
new EffectMesh(effectBitonicSort1Pass2);
214 var currentDepth = MaximumDepthLevel;
215 for (
int i = 0; i < bitonicSort2Passes.Length; i++)
217 var bitonicShader =
new RenderPass(
string.Format(
"Bitonic-{0}", currentDepth));
218 bitonicSort2Passes[i] = bitonicShader;
221 effectBitonicSort2[i] = this.EffectSystemOld.BuildEffect(
"ParticleBitonicSort2-" + currentDepth).Using(
222 new ComputeShaderPlugin(
new ShaderClassSource(
"ParticleBitonicSort2", currentDepth), MaximumThreadPerGroup, 1, 1)
225 Macros = {
new ShaderMacro(
"PARTICLE_STRUCT", StructureName) }
227 effectBitonicSort2[i].KeepAliveBy(
this);
231 effectMeshBitonicSort2 =
new EffectMesh[bitonicSort2Passes.Length];
232 for (
int i = 0; i < effectMeshBitonicSort2.Length; i++)
233 effectMeshBitonicSort2[i] =
new EffectMesh(effectBitonicSort2[i]);
236 EffectOld particleRenderEffect = this.EffectSystemOld.BuildEffect(
"ParticleRender")
238 .Using(
new BasicShaderPlugin(RenderShader)
241 Macros = {
new ShaderMacro(
"PARTICLE_STRUCT", StructureName) }
243 particleRenderEffect.KeepAliveBy(
this);
245 particleRenderEffect.Parameters.AddSources(this.Parameters);
246 particleRenderEffect.Parameters.AddSources(MainPlugin.ViewParameters);
247 particleRenderEffect.Parameters.Set(EffectPlugin.BlendStateKey, graphicsDeviceService.GraphicsDevice.BlendStates.AlphaBlend);
248 particleRenderEffect.Parameters.Set(EffectPlugin.DepthStencilStateKey, MainTargetPlugin.DepthStencilState);
249 particleRenderEffect.Parameters.Set(RenderTargetKeys.DepthStencilSource, MainTargetPlugin.DepthStencil.Texture);
251 effectMeshRender =
new EffectMesh(particleRenderEffect);
253 effectMeshRender.Render += (context) =>
255 if (currentParticleCount > 0)
257 context.GraphicsDevice.SetVertexArrayObject(null);
258 context.GraphicsDevice.SetViewport(MainTargetPlugin.Viewport);
261 context.GraphicsDevice.SetRenderTargets((MainTargetPlugin.DepthStencilReadOnly != null) ? MainTargetPlugin.DepthStencilReadOnly : MainTargetPlugin.DepthStencil,
RenderTarget);
265 context.GraphicsDevice.Draw(PrimitiveType.PointList, currentParticleCount);
270 if (OfflineCompilation)
275 OnCapacityCountChange();
278 RenderSystem.RenderPassEnumerators.Add(meshesToRender);
281 RenderSystem.GlobalPass.StartPass += OnFrameUpdate;
284 sortPass.EndPass.Set = ComputeBitonicSort;
289 if (!OfflineCompilation)
300 private void OnCapacityCountChange()
303 bool allocateGlobalBuffer =
false;
304 if (globalBuffer == null)
306 allocateGlobalBuffer =
true;
308 else if (globalBuffer.ElementCount != CapacityCount)
311 allocateGlobalBuffer =
true;
313 globalBuffer.Release();
314 sortBuffer.Release();
318 if (allocateGlobalBuffer)
320 if (CapacityCount > 0)
322 globalBuffer = Buffer.Structured.New(graphicsDeviceService.GraphicsDevice, CapacityCount, StructureSize,
true);
323 globalBuffer.Name =
"ParticleGlobalBuffer";
324 sortBuffer = Buffer.Structured.New(graphicsDeviceService.GraphicsDevice, CapacityCount,
sizeof(int) * 2,
true);
325 sortBuffer.Name =
"ParticleSortBuffer";
330 private static void InvokeMeshPass(EffectMesh mesh, ThreadContext context,
bool skipEffectPass =
false)
332 var effectPass = mesh.EffectPass;
336 effectPass.StartPass.Invoke(context);
339 context.EffectMesh = mesh;
340 mesh.Render.Invoke(context);
341 context.EffectMesh = null;
345 effectPass.EndPass.Invoke(context);
348 private void OnFrameUpdate(ThreadContext context)
350 updatePasses.Enabled =
true;
353 foreach (var particleUpdater
in Updaters)
355 ParticleUpdaterState particleUpdaterState = null;
356 foreach (ParticleUpdaterState state
in currentUpdaters)
358 if (ReferenceEquals(state.UserState, particleUpdater))
360 particleUpdaterState = state;
364 if (particleUpdaterState == null)
365 currentUpdaters.Add(
new ParticleUpdaterState() { UserState = particleUpdater, StructureSize = StructureSize});
369 foreach (var particleUpdater
in currentUpdaters)
374 if (ReferenceEquals(state, particleUpdater.UserState))
376 particleUpdaterState = state;
380 if (particleUpdaterState == null)
381 updatersToRemove.Add(particleUpdater);
385 foreach (var particleUpdaterState
in updatersToRemove)
387 currentUpdaters.Remove(particleUpdaterState);
390 meshesToRender.RemoveMesh(particleUpdaterState.MeshUpdater);
393 particleUpdaterState.DisposeBuffers();
397 if (updatersCount > capacityCount)
399 CapacityCount = updatersCount;
400 OnCapacityCountChange();
403 int particleUpdaterIndex = 1;
405 currentParticleCount = 0;
408 for (
int i = 0; i < currentUpdaters.Count; i++)
410 var currentState = currentUpdaters[i];
411 var userState = currentState.UserState;
413 if (!userState.IsDynamicEmitter)
416 if (currentState.IsDynamicEmitter)
417 currentState.DisposeBuffers();
419 else if (userState.Count > currentState.Count)
423 currentState.AllocateBuffers(context.GraphicsDevice);
427 if (!ReferenceEquals(userState.Shader, currentState.Shader) || currentState.Count != userState.Count)
430 updatePasses.RemovePass(currentState.EffectPass);
433 currentState.DisposeEffects();
436 if (currentState.MeshUpdater != null)
438 meshesToRender.RemoveMesh(currentState.MeshUpdater);
439 currentState.MeshUpdater = null;
443 if (userState.Shader != null)
445 string name = userState.Name ?? string.Format(
"{0}{1}", userState.Shader.ClassName, particleUpdaterIndex++);
447 currentState.EffectPass =
new RenderPass(name);
448 updatePasses.AddPass(currentState.EffectPass);
451 int dispatchCount = MaximumThreadPerGroup;
452 while (dispatchCount > 0)
455 if ((userState.Count & (dispatchCount - 1)) == 0)
461 currentState.EffectUpdater = this.EffectSystemOld.BuildEffect(name).Using(
462 new ComputeShaderPlugin(userState.Shader, dispatchCount, 1, 1)
464 RenderPass = currentState.EffectPass,
465 Macros = {
new ShaderMacro(
"PARTICLE_STRUCT", StructureName) }
469 var meshParams =
new ParameterCollection(name);
470 currentState.MeshUpdater =
new EffectMesh(currentState.EffectUpdater,
new Mesh { Parameters = meshParams }).Dispatch(userState.Count / dispatchCount, 1, 1);
471 currentState.MeshUpdater.Parameters.AddSources(Parameters);
472 currentState.MeshUpdater.Parameters.AddSources(MainPlugin.ViewParameters);
473 currentState.MeshUpdater.Parameters.AddSources(userState.Parameters);
476 if (userState.IsDynamicEmitter)
478 currentState.MeshUpdater.Parameters.Set(ParticleBaseKeys.ParticleInputBuffer, currentState.ConsumeBuffer);
479 currentState.MeshUpdater.Parameters.Set(ParticleBaseKeys.ParticleOutputBuffer, currentState.AppendBuffer);
480 currentState.MeshUpdater.Parameters.Set(ParticleBaseKeys.ParticleStartIndex, (uint)0);
485 currentState.MeshUpdater.Parameters.Set(ParticleBaseKeys.ParticleGlobalBuffer, globalBuffer);
486 currentState.MeshUpdater.Parameters.Set(ParticleBaseKeys.ParticleStartIndex, (uint)startIndex);
490 meshesToRender.AddMesh(currentState.MeshUpdater);
495 if (currentState.MeshUpdater != null && !userState.IsDynamicEmitter && currentState.StartIndex != startIndex)
498 currentState.MeshUpdater.Parameters.Set(ParticleBaseKeys.ParticleStartIndex, (uint)startIndex);
502 if (!ReferenceEquals(userState.ParticleData, currentState.ParticleData)
503 || userState.UpdateNextBuffer
505 || currentState.StartIndex != startIndex
506 || currentState.Count != userState.Count
510 userState.OnUpdateData();
512 if (userState.ParticleData != null)
514 var handle = GCHandle.Alloc(userState.ParticleData, GCHandleType.Pinned);
515 globalBuffer.SetData(context.GraphicsDevice,
new DataPointer(handle.AddrOfPinnedObject(), userState.Count * StructureSize), startIndex * StructureSize);
518 userState.UpdateNextBuffer =
false;
522 currentState.Type = userState.Type;
523 currentState.Shader = userState.Shader;
524 currentState.Count = userState.Count;
525 currentState.ParticleElementSize = userState.ParticleElementSize;
526 currentState.ParticleData = userState.ParticleData;
527 currentState.StartIndex = startIndex;
530 currentParticleCount += userState.Count;
533 startIndex += userState.Count;
537 if (updatersCount > 0)
539 if (!isParticleRenderingEnabled)
541 isParticleRenderingEnabled =
true;
543 meshesToRender.AddMesh(effectMeshRender);
544 meshesToRender.AddMesh(effectMeshCopyToSortBuffer);
549 isParticleRenderingEnabled =
false;
550 meshesToRender.RemoveMesh(effectMeshRender);
551 meshesToRender.RemoveMesh(effectMeshCopyToSortBuffer);
555 PrepareForRendering();
558 private bool isParticleRenderingEnabled;
562 private void PrepareForRendering()
564 effectMeshCopyToSortBuffer.Parameters.Set(ComputeShaderPlugin.ThreadGroupCount,
new Int3(currentParticleCount / MaximumThreadPerGroup, 1, 1));
565 effectMeshCopyToSortBuffer.Parameters.Set(ParticleBaseKeys.ParticleCount, (uint)currentParticleCount);
567 effectMeshCopyToSortBuffer.Parameters.Set(ParticleBaseKeys.ParticleSortBuffer, sortBuffer);
568 effectMeshCopyToSortBuffer.Parameters.Set(ParticleBaseKeys.ParticleGlobalBuffer, globalBuffer);
570 effectMeshRender.Parameters.Set(ParticleBaseKeys.ParticleSortBuffer, sortBuffer);
571 effectMeshRender.Parameters.Set(ParticleBaseKeys.ParticleGlobalBuffer, globalBuffer);
572 effectMeshRender.Parameters.Set(ParticleBaseKeys.ParticleCount, (uint)currentParticleCount);
584 private void ComputeBitonicSort(ThreadContext context)
586 if (!EnableSorting || !isParticleRenderingEnabled)
591 int particleCount = updatersCount;
594 var depthLevelCount = (int)Math.Log(particleCount, 2);
595 var optimLevelStart = (int)Math.Log(MaximumDepthLevel, 2);
597 int numberOfGroupForOptim = particleCount / MaximumThreadPerGroup;
598 int numberOfGroups = numberOfGroupForOptim / 2;
600 for (
int i = 0; i < depthLevelCount; i++, blockSize *= 2)
603 effectMeshSort1Pass1.Parameters.Set(ParticleBaseKeys.ParticleStartIndex, (uint)blockSize);
604 effectMeshSort1Pass1.Parameters.Set(ParticleBaseKeys.ParticleSortBuffer, sortBuffer);
605 effectMeshSort1Pass1.Parameters.Set(ComputeShaderPlugin.ThreadGroupCount,
new Int3(numberOfGroups, 1, 1));
608 InvokeMeshPass(effectMeshSort1Pass1, context);
611 var subOffset = blockSize;
613 for (
int j = 0; j < i; j++, subOffset /= 2)
616 int k = optimLevelStart - i + j;
617 if (k >= 0 && k < effectMeshBitonicSort2.Length)
619 var subMesh = effectMeshBitonicSort2[k];
621 subMesh.Parameters.Set(ParticleBaseKeys.ParticleSortBuffer, sortBuffer);
622 subMesh.Parameters.Set(ComputeShaderPlugin.ThreadGroupCount,
new Int3(numberOfGroupForOptim, 1, 1));
624 InvokeMeshPass(subMesh, context);
629 effectMeshSort1Pass2.Parameters.Set(ParticleBaseKeys.ParticleStartIndex, (uint)(subOffset / 2));
630 effectMeshSort1Pass2.Parameters.Set(ParticleBaseKeys.ParticleSortBuffer, sortBuffer);
631 effectMeshSort1Pass2.Parameters.Set(ComputeShaderPlugin.ThreadGroupCount,
new Int3(numberOfGroups, 1, 1));
634 InvokeMeshPass(effectMeshSort1Pass2, context, j > 0);
641 updatersCount = ParticleUtils.CalculateMaximumPowerOf2Count(Updaters.Sum(particleUpdater => particleUpdater.Count));
648 public Buffer AppendBuffer;
650 public Buffer ConsumeBuffer;
652 public RenderPass EffectPass;
654 public EffectOld EffectUpdater;
656 public EffectMesh MeshUpdater;
658 public int StartIndex;
660 public int StructureSize;
669 AppendBuffer = Buffer.StructuredAppend.New(graphicsDevice, UserState.Count, StructureSize);
670 ConsumeBuffer = Buffer.StructuredAppend.New(graphicsDevice, UserState.Count, StructureSize);
673 public void DisposeEffects()
675 if (EffectUpdater != null)
677 EffectUpdater.Release();
678 EffectUpdater = null;
682 public void DisposeBuffers()
684 if (AppendBuffer != null)
686 AppendBuffer.Release();
690 if (ConsumeBuffer != null)
692 ConsumeBuffer.Release();
693 ConsumeBuffer = null;
SiliconStudio.Paradox.Shaders.ShaderMacro ShaderMacro
ParticlePlugin(string name)
Initializes a new instance of the ParticlePlugin class.
Service providing method to access GraphicsDevice life-cycle.
SiliconStudio.Paradox.Graphics.Buffer Buffer
A renderable texture view.
ParticlePlugin()
Initializes a new instance of the ParticlePlugin class.
Basic shader plugin built directly from shader source file.
Represents a three dimensional mathematical vector.
All-in-One Buffer class linked SharpDX.Direct3D11.Buffer.
Performs primitive-based rendering, creates resources, handles system-level variables, adjusts gamma ramp levels, and creates shaders. See The+GraphicsDevice+class to learn more about the class.
Plugin used for the main rendering view.
static int CalculateMaximumPowerOf2Count(int value)
Level10 render pass using a depth buffer and a render target.
override void Initialize()
Represents a four dimensional mathematical vector.
RenderPass is a hierarchy that defines how to collect and render meshes.
A shader class used for mixin.