diff --git a/Core b/Core
index fea80322..1aefd840 160000
--- a/Core
+++ b/Core
@@ -1 +1 @@
-Subproject commit fea803224dfb1c7b23240b0a8a6a9fe692281354
+Subproject commit 1aefd840b3f9639aa2ad874e28208345638e971f
diff --git a/Engine/AutoGeneratedCoreBindings.cs b/Engine/AutoGeneratedCoreBindings.cs
index 81d40aff..1e28c2f7 100644
--- a/Engine/AutoGeneratedCoreBindings.cs
+++ b/Engine/AutoGeneratedCoreBindings.cs
@@ -5141,14 +5141,6 @@ public static CommandList TryGetFromCache(IntPtr native)
[EditorBrowsable(EditorBrowsableState.Never)]
private static extern void cbg_CommandList_ResumeRenderPass(IntPtr selfPtr);
- [DllImport("Altseed2_Core")]
- [EditorBrowsable(EditorBrowsableState.Never)]
- private static extern void cbg_CommandList_UploadBuffer(IntPtr selfPtr, IntPtr buffer);
-
- [DllImport("Altseed2_Core")]
- [EditorBrowsable(EditorBrowsableState.Never)]
- private static extern void cbg_CommandList_ReadbackBuffer(IntPtr selfPtr, IntPtr buffer);
-
[DllImport("Altseed2_Core")]
[EditorBrowsable(EditorBrowsableState.Never)]
private static extern void cbg_CommandList_CopyBuffer(IntPtr selfPtr, IntPtr src, IntPtr dst);
@@ -5177,6 +5169,10 @@ public static CommandList TryGetFromCache(IntPtr native)
[EditorBrowsable(EditorBrowsableState.Never)]
private static extern void cbg_CommandList_SetIndexBuffer(IntPtr selfPtr, IntPtr ib, int stride, int offset);
+ [DllImport("Altseed2_Core")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ private static extern void cbg_CommandList_SetMaterialWithConstantBuffer(IntPtr selfPtr, IntPtr material, IntPtr constantBuffer);
+
[DllImport("Altseed2_Core")]
[EditorBrowsable(EditorBrowsableState.Never)]
private static extern void cbg_CommandList_BeginComputePass(IntPtr selfPtr);
@@ -5187,7 +5183,11 @@ public static CommandList TryGetFromCache(IntPtr native)
[DllImport("Altseed2_Core")]
[EditorBrowsable(EditorBrowsableState.Never)]
- private static extern void cbg_CommandList_SetComputeBuffer(IntPtr selfPtr, IntPtr buffer, int stride, int unit);
+ private static extern void cbg_CommandList_SetComputeBuffer(IntPtr selfPtr, IntPtr buffer, int stride, int unit, int shaderStage);
+
+ [DllImport("Altseed2_Core")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ private static extern void cbg_CommandList_SetComputePipelineStateWithConstantBuffer(IntPtr selfPtr, IntPtr computePipelineState, IntPtr constantBuffer);
[DllImport("Altseed2_Core")]
[EditorBrowsable(EditorBrowsableState.Never)]
@@ -5327,16 +5327,6 @@ public void ResumeRenderPass()
cbg_CommandList_ResumeRenderPass(selfPtr);
}
- internal void UploadBuffer(Buffer buffer)
- {
- cbg_CommandList_UploadBuffer(selfPtr, buffer != null ? buffer.selfPtr : IntPtr.Zero);
- }
-
- internal void ReadbackBuffer(Buffer buffer)
- {
- cbg_CommandList_ReadbackBuffer(selfPtr, buffer != null ? buffer.selfPtr : IntPtr.Zero);
- }
-
internal void CopyBuffer(Buffer src, Buffer dst)
{
cbg_CommandList_CopyBuffer(selfPtr, src != null ? src.selfPtr : IntPtr.Zero, dst != null ? dst.selfPtr : IntPtr.Zero);
@@ -5386,6 +5376,11 @@ internal void SetIndexBuffer(Buffer ib, int stride, int offset)
cbg_CommandList_SetIndexBuffer(selfPtr, ib != null ? ib.selfPtr : IntPtr.Zero, stride, offset);
}
+ internal void SetMaterialWithConstantBuffer(Material material, Buffer constantBuffer)
+ {
+ cbg_CommandList_SetMaterialWithConstantBuffer(selfPtr, material != null ? material.selfPtr : IntPtr.Zero, constantBuffer != null ? constantBuffer.selfPtr : IntPtr.Zero);
+ }
+
public void BeginComputePass()
{
cbg_CommandList_BeginComputePass(selfPtr);
@@ -5396,9 +5391,14 @@ public void EndComputePass()
cbg_CommandList_EndComputePass(selfPtr);
}
- internal void SetComputeBuffer(Buffer buffer, int stride, int unit)
+ internal void SetComputeBuffer(Buffer buffer, int stride, int unit, ShaderStage shaderStage)
{
- cbg_CommandList_SetComputeBuffer(selfPtr, buffer != null ? buffer.selfPtr : IntPtr.Zero, stride, unit);
+ cbg_CommandList_SetComputeBuffer(selfPtr, buffer != null ? buffer.selfPtr : IntPtr.Zero, stride, unit, (int)shaderStage);
+ }
+
+ internal void SetComputePipelineStateWithConstantBuffer(ComputePipelineState computePipelineState, Buffer constantBuffer)
+ {
+ cbg_CommandList_SetComputePipelineStateWithConstantBuffer(selfPtr, computePipelineState != null ? computePipelineState.selfPtr : IntPtr.Zero, constantBuffer != null ? constantBuffer.selfPtr : IntPtr.Zero);
}
public void Dispatch(int x, int y, int z)
@@ -5512,11 +5512,19 @@ public static Graphics TryGetFromCache(IntPtr native)
[DllImport("Altseed2_Core")]
[EditorBrowsable(EditorBrowsableState.Never)]
- private static extern void cbg_Graphics_ExecuteCommandList(IntPtr selfPtr);
+ private static extern void cbg_Graphics_ExecuteCommandList_(IntPtr selfPtr);
+
+ [DllImport("Altseed2_Core")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ private static extern void cbg_Graphics_ExecuteCommandList_CommandList(IntPtr selfPtr, IntPtr commandList);
[DllImport("Altseed2_Core")]
[EditorBrowsable(EditorBrowsableState.Never)]
- private static extern void cbg_Graphics_WaitFinish(IntPtr selfPtr);
+ private static extern void cbg_Graphics_WaitFinish_(IntPtr selfPtr);
+
+ [DllImport("Altseed2_Core")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ private static extern void cbg_Graphics_WaitFinish_CommandList(IntPtr selfPtr, IntPtr commandList);
[DllImport("Altseed2_Core")]
[EditorBrowsable(EditorBrowsableState.Never)]
@@ -5611,12 +5619,22 @@ public static void Terminate()
public void ExecuteCommandList()
{
- cbg_Graphics_ExecuteCommandList(selfPtr);
+ cbg_Graphics_ExecuteCommandList_(selfPtr);
+ }
+
+ public void ExecuteCommandList(CommandList commandList)
+ {
+ cbg_Graphics_ExecuteCommandList_CommandList(selfPtr, commandList != null ? commandList.selfPtr : IntPtr.Zero);
}
public void WaitFinish()
{
- cbg_Graphics_WaitFinish(selfPtr);
+ cbg_Graphics_WaitFinish_(selfPtr);
+ }
+
+ public void WaitFinish(CommandList commandList)
+ {
+ cbg_Graphics_WaitFinish_CommandList(selfPtr, commandList != null ? commandList.selfPtr : IntPtr.Zero);
}
///
diff --git a/Engine/CorePartial/Buffer.cs b/Engine/CorePartial/Buffer.cs
new file mode 100644
index 00000000..888059de
--- /dev/null
+++ b/Engine/CorePartial/Buffer.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Altseed2
+{
+ ///
+ /// CPU/GPUバッファ
+ ///
+ ///
+ public class Buffer where T : struct
+ {
+ internal Buffer InternalBuffer { get; }
+
+ ///
+ /// バッファ内の要素数
+ ///
+ public int Count { get; }
+
+ ///
+ /// バッファのサイズ
+ ///
+ public int Size => InternalBuffer.Size;
+
+ ///
+ /// バッファの使途
+ ///
+ public BufferUsageType BufferUsage => InternalBuffer.BufferUsage;
+
+ Buffer(Buffer internalBuffer, int count)
+ {
+ InternalBuffer = internalBuffer;
+ Count = count;
+ }
+
+ ///
+ /// バッファを生成します
+ ///
+ /// バッファの使途
+ /// 要素数
+ /// のインスタンス
+ public static Buffer Create(BufferUsageType usage, int count)
+ {
+ var internalBuffer = Buffer.Create(usage, Marshal.SizeOf() * count);
+ if (internalBuffer is null)
+ {
+ Engine.Log.Error(LogCategory.Engine, "Buffer::Create: bufferの作成に失敗しました。");
+ return null;
+ }
+
+ return new Buffer(internalBuffer, count);
+ }
+
+ ///
+ /// バッファを生成します
+ ///
+ /// バッファの使途
+ /// 要素数
+ /// のインスタンス
+ /// バッファの作成に失敗
+ public static Buffer CreateStrict(BufferUsageType usage, int count)
+ {
+ var internalBuffer = Buffer.Create(usage, Marshal.SizeOf() * count);
+ if (internalBuffer is null)
+ {
+ throw new ArgumentException("Bufferの作成に失敗しました。");
+ }
+
+ return new Buffer(internalBuffer, count);
+ }
+
+ ///
+ /// バッファのデータへアクセスする
+ ///
+ /// バッファのデータを持つ
+ public ReadContainer ReadLock()
+ {
+ unsafe
+ {
+ var lockPtr = InternalBuffer.Lock();
+
+ if (lockPtr == IntPtr.Zero
+ || ((InternalBuffer.BufferUsage & BufferUsageType.MapRead) == 0
+ && (InternalBuffer.BufferUsage & BufferUsageType.MapWrite) == 0)
+ )
+ {
+ return new ReadContainer(null, null);
+ }
+ return new ReadContainer(this, new ReadOnlySpan(lockPtr.ToPointer(), Count));
+ }
+ }
+
+ ///
+ /// バッファのデータへアクセスする。
+ ///
+ /// バッファのデータを持つ
+ /// バッファのロックに失敗
+ public ReadContainer ReadLockStrict()
+ {
+ var container = ReadLock();
+
+ if (container.Span == null)
+ {
+ throw new InvalidOperationException("バッファのロックに失敗しました。");
+ }
+
+ return container;
+ }
+
+ ///
+ /// バッファのデータへアクセスする
+ ///
+ /// バッファのデータを持つ
+ public WriteContainer WriteLock()
+ {
+ unsafe
+ {
+ var lockPtr = InternalBuffer.Lock();
+
+ if (lockPtr == IntPtr.Zero || ((InternalBuffer.BufferUsage & BufferUsageType.MapWrite) == 0))
+ {
+ return new WriteContainer(null, null);
+ }
+ return new WriteContainer(this, new Span(lockPtr.ToPointer(), Count));
+ }
+ }
+
+ ///
+ /// バッファのデータへアクセスする。
+ ///
+ /// バッファのデータを持つ
+ /// バッファのロックに失敗
+ public WriteContainer WriteLockStrict()
+ {
+ var container = WriteLock();
+
+ if (container.Span == null)
+ {
+ throw new InvalidOperationException("バッファのロックに失敗しました。");
+ }
+
+ return container;
+ }
+
+ ///
+ /// バッファへ書き込みを行うための情報を保持する構造体。
+ ///
+ ///
+ public ref struct ReadContainer
+ {
+ private readonly Buffer buffer;
+
+ ///
+ /// バッファのデータ
+ ///
+ ///
+ public ReadOnlySpan Span { get; private init; }
+
+ ///
+ /// がnullではない。
+ ///
+ public bool IsValid => Span != null;
+
+ internal ReadContainer(Buffer buffer, ReadOnlySpan span)
+ {
+ this.buffer = buffer;
+ Span = span;
+ }
+
+ ///
+ /// バッファのアンロックを行う
+ ///
+ public void Dispose() => buffer?.InternalBuffer.Unlock();
+ }
+
+ ///
+ /// バッファへ書き込みを行うための情報を保持する構造体。
+ ///
+ ///
+ public ref struct WriteContainer
+ {
+ private readonly Buffer buffer;
+
+ ///
+ /// バッファのデータ
+ ///
+ ///
+ public Span Span { get; private init; }
+
+ ///
+ /// がnullではない。
+ ///
+ public bool IsValid => Span != null;
+
+ internal WriteContainer(Buffer buffer, Span span)
+ {
+ this.buffer = buffer;
+ Span = span;
+ }
+
+ ///
+ /// バッファのアンロックを行う
+ ///
+ public void Dispose() => buffer?.InternalBuffer.Unlock();
+ }
+ }
+}
diff --git a/Engine/CorePartial/CommandList.cs b/Engine/CorePartial/CommandList.cs
new file mode 100644
index 00000000..30cc3448
--- /dev/null
+++ b/Engine/CorePartial/CommandList.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Altseed2
+{
+ public partial class CommandList
+ {
+ private const int TextureSlotMax = 8;
+
+ ///
+ /// GPUのデータをコピーする
+ ///
+ ///
+ ///
+ ///
+ public void CopyBuffer(Buffer src, Buffer dst) where T : struct
+ {
+ CopyBuffer(src.InternalBuffer, dst.InternalBuffer);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void SetIndexBuffer(Buffer indexBuffer, int offset = 0) where T : struct
+ {
+ int stride = Marshal.SizeOf();
+ SetIndexBuffer(indexBuffer.InternalBuffer, stride, offset);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void SetVertexBuffer(Buffer vertexBuffer, int offset = 0) where T : struct
+ {
+ int stride = Marshal.SizeOf();
+ SetVertexBuffer(vertexBuffer.InternalBuffer, stride, offset);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void SetComputeBuffer(Buffer computeBuffer, int unit, ShaderStage shaderStage = ShaderStage.Compute) where T : struct
+ {
+ if (unit >= TextureSlotMax) throw new ArgumentOutOfRangeException(nameof(unit), $"");
+ int stride = Marshal.SizeOf();
+ SetComputeBuffer(computeBuffer.InternalBuffer, stride, unit, shaderStage);
+ }
+
+ ///
+ ///
+ ///
+ public Material Material
+ {
+ set
+ {
+ value.SetMatrix44F("matView", Engine.CurrentCamera.ViewMatrix);
+ value.SetMatrix44F("matProjection", Engine.CurrentCamera.ProjectionMatrix);
+ cbg_CommandList_SetMaterial(this.selfPtr, value != null ? value.selfPtr : IntPtr.Zero);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void SetMaterialWithConstantBuffer(Material material, Buffer constantBuffer) where T : struct
+ {
+ cbg_CommandList_SetMaterialWithConstantBuffer(
+ this.selfPtr,
+ material != null ? material.selfPtr : IntPtr.Zero,
+ constantBuffer != null ? constantBuffer.InternalBuffer.selfPtr : IntPtr.Zero);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void SetComputePipelineStateWithConstantBuffer(ComputePipelineState computePipelineState, Buffer constantBuffer) where T : struct
+ {
+ cbg_CommandList_SetComputePipelineStateWithConstantBuffer(
+ this.selfPtr,
+ computePipelineState != null ? computePipelineState.selfPtr : IntPtr.Zero,
+ constantBuffer != null ? constantBuffer.InternalBuffer.selfPtr : IntPtr.Zero);
+ }
+ }
+}
diff --git a/Engine/CorePartial/Shader.cs b/Engine/CorePartial/Shader.cs
index a86e39f3..1bec9142 100644
--- a/Engine/CorePartial/Shader.cs
+++ b/Engine/CorePartial/Shader.cs
@@ -59,6 +59,44 @@ public static string TryCreateFromFile(string name, string path, ShaderStage sha
return result.Message;
}
+ ///
+ /// コードをコンパイルしての新しいインスタンスを生成します。
+ ///
+ /// シェーダにつける名前
+ /// シェーダのコード
+ /// シェーダの種類
+ /// またはがnull
+ ///
+ ///
+ public static Shader CreateStrict(string name, string code, ShaderStage shaderStage)
+ {
+ var result = Compile(name, code, shaderStage);
+ if (result.Value is Shader shader)
+ {
+ return shader;
+ }
+ throw new SystemException(result.Message);
+ }
+
+ ///
+ /// ファイルに書かれたコードをコンパイルしての新しいインスタンスを生成します。
+ ///
+ /// シェーダにつける名前
+ /// シェーダのコード
+ /// シェーダの種類
+ /// またはがnull
+ ///
+ ///
+ public static Shader CreateFromFileStrict(string name, string path, ShaderStage shaderStage)
+ {
+ var result = CompileFromFile(name, path, shaderStage);
+ if (result.Value is Shader shader)
+ {
+ return shader;
+ }
+ throw new SystemException(result.Message);
+ }
+
partial void Deserialize_GetPtr(ref IntPtr ptr, SerializationInfo info)
{
Shader_Unsetter_Deserialize(info, out var code, out var name, out var stage);
diff --git a/Engine/Engine.cs b/Engine/Engine.cs
index 49e19ccf..6c9909ae 100644
--- a/Engine/Engine.cs
+++ b/Engine/Engine.cs
@@ -220,6 +220,8 @@ internal static bool UpdateComponents(bool drawDefaultCameraGroup, bool drawCust
_tool.Render();
}
+ _currentCamera = null;
+
// 描画を終了
if (!Graphics.EndFrame()) return false;
return true;
@@ -227,6 +229,7 @@ internal static bool UpdateComponents(bool drawDefaultCameraGroup, bool drawCust
internal static void DrawCameraGroup(RenderedCamera camera, SortedDictionary> drawns)
{
+ _currentCamera = camera;
Renderer.Camera = camera;
// カリングの結果
@@ -246,6 +249,9 @@ internal static void DrawCameraGroup(RenderedCamera camera, SortedDictionary n.IsDrawnActually)) などとしない
+ if (!node.IsDrawnActually) continue;
+
if (node is PostEffectNode)
{
if (requireRender)
@@ -262,15 +268,17 @@ internal static void DrawCameraGroup(RenderedCamera camera, SortedDictionary n.IsDrawnActually)) などとしない
if (cullingIds.BinarySearch(cdrawn.CullingId) < 0) continue;
node.Draw();
requireRender = true;
}
- else throw new InvalidOperationException();
+ else
+ {
+ node.Draw();
+ requireRender = true;
+ }
}
}
@@ -457,6 +465,12 @@ public static void Resume()
public static Profiler Profiler => _profiler ?? throw new InvalidOperationException("Profiler機能が初期化されていません。");
private static Profiler _profiler;
+ ///
+ /// 現在使用しているカメラ
+ ///
+ internal static RenderedCamera CurrentCamera => _currentCamera;
+ private static RenderedCamera _currentCamera;
+
#endregion
#region Node
diff --git a/Engine/Node/CommandDrawnNode.cs b/Engine/Node/CommandDrawnNode.cs
new file mode 100644
index 00000000..fec94436
--- /dev/null
+++ b/Engine/Node/CommandDrawnNode.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Altseed2
+{
+ ///
+ /// を呼び出すことで描画するノードを表します。
+ ///
+ [Serializable]
+ public abstract class CommandDrawnNode : Node, IDrawn
+ {
+ ///
+ /// の新しいインスタンスを生成します。
+ ///
+ /// Graphics機能が初期化されていない。
+ public CommandDrawnNode()
+ {
+ if (!Engine.Config.EnabledCoreModules.HasFlag(CoreModules.Graphics))
+ {
+ throw new InvalidOperationException("Graphivs機能が初期化されていません。");
+ }
+ }
+
+ #region IDrawn
+ ///
+ /// カメラグループを取得または設定します。
+ ///
+ public ulong CameraGroup
+ {
+ get => _CameraGroup;
+ set
+ {
+ if (_CameraGroup == value) return;
+ var old = _CameraGroup;
+ _CameraGroup = value;
+
+ if (IsRegistered)
+ Engine.UpdateDrawnCameraGroup(this, old);
+ }
+ }
+ private ulong _CameraGroup;
+
+ ///
+ /// 描画時の重ね順を取得または設定します。
+ ///
+ public int ZOrder {
+ get => _ZOrder;
+ set
+ {
+ if (value == _ZOrder) return;
+
+ var old = _ZOrder;
+ _ZOrder = value;
+
+ if (IsRegistered)
+ Engine.UpdateDrawnZOrder(this, old);
+ }
+ }
+ private int _ZOrder;
+
+ ///
+ /// このノードを描画するかどうかを取得または設定します。
+ ///
+ [PartsTreeSystem.SerializeField]
+ public bool IsDrawn
+ {
+ get => _IsDrawn;
+ set
+ {
+ if (_IsDrawn == value) return;
+ _IsDrawn = value;
+ this.UpdateIsDrawnActuallyOfDescendants();
+ }
+ }
+ private bool _IsDrawn = true;
+
+ ///
+ /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
+ ///
+ [PartsTreeSystem.SerializeField]
+ public bool IsDrawnActually => (this as IDrawn).IsDrawnActually;
+ [PartsTreeSystem.SerializeField]
+ bool IDrawn.IsDrawnActually { get; set; } = true;
+
+ #endregion
+ #region Node
+
+ internal override void Registered()
+ {
+ base.Registered();
+ Engine.RegisterDrawn(this);
+ }
+
+ internal override void Unregistered()
+ {
+ base.Unregistered();
+ Engine.UnregisterDrawn(this);
+ }
+
+ #endregion
+
+ ///
+ /// 描画時に実行されます.
+ ///
+ protected abstract void Draw();
+
+ void IDrawn.Draw() => Draw();
+ }
+}
diff --git a/Engine/Node/IDrawn.cs b/Engine/Node/IDrawn.cs
index 53e897ee..15d030cf 100644
--- a/Engine/Node/IDrawn.cs
+++ b/Engine/Node/IDrawn.cs
@@ -20,6 +20,16 @@ public interface IDrawn
/// 描画時の重ね順を取得または設定します。
///
int ZOrder { get; set; }
+
+ ///
+ /// このノードを描画するかどうかを取得または設定します。
+ ///
+ bool IsDrawn { get; set; }
+
+ ///
+ /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
+ ///
+ bool IsDrawnActually { get; internal set; }
}
///
@@ -31,16 +41,6 @@ public interface ICullableDrawn : IDrawn
internal int CullingId { get; }
- ///
- /// このノードを描画するかどうかを取得または設定します。
- ///
- bool IsDrawn { get; set; }
-
- ///
- /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
- ///
- bool IsDrawnActually { get; internal set; }
-
///
/// コンテンツのサイズを取得します。
///
@@ -53,13 +53,13 @@ internal static class DrawnExtension
/// 自身と子孫ノードの IsDrawnActually プロパティを更新します。
///
internal static void UpdateIsDrawnActuallyOfDescendants(this T node)
- where T : Node, ICullableDrawn
+ where T : Node, IDrawn
{
static void UpdateRecursive(Node n, bool ancestorsIsDawnActually)
{
var isDrawnActually = ancestorsIsDawnActually;
- if (n is ICullableDrawn dn)
+ if (n is IDrawn dn)
{
isDrawnActually = dn.IsDrawn && ancestorsIsDawnActually;
dn.IsDrawnActually = isDrawnActually;
@@ -71,14 +71,14 @@ static void UpdateRecursive(Node n, bool ancestorsIsDawnActually)
}
}
- var ancestorsIsDrawnActually = node.GetAncestorSpecificNode()?.IsDrawnActually ?? true;
+ var ancestorsIsDrawnActually = node.GetAncestorSpecificNode()?.IsDrawnActually ?? true;
UpdateRecursive(node, ancestorsIsDrawnActually);
}
internal static void UpdateIsDrawnActuallyFromAncestors(this T node)
- where T : Node, ICullableDrawn
+ where T : Node, IDrawn
{
- var ancestorsIsDrawnActually = node.GetAncestorSpecificNode()?.IsDrawnActually ?? true;
+ var ancestorsIsDrawnActually = node.GetAncestorSpecificNode()?.IsDrawnActually ?? true;
node.IsDrawnActually = ancestorsIsDrawnActually && node.IsDrawn;
}
}
diff --git a/Engine/Node/PolygonNode.cs b/Engine/Node/PolygonNode.cs
index 6e4a4984..a75a8c2f 100644
--- a/Engine/Node/PolygonNode.cs
+++ b/Engine/Node/PolygonNode.cs
@@ -96,10 +96,10 @@ public bool IsDrawn
private bool _IsDrawn = true;
///
- /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
+ /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
///
- public bool IsDrawnActually => (this as ICullableDrawn).IsDrawnActually;
- bool ICullableDrawn.IsDrawnActually { get; set; } = true;
+ public bool IsDrawnActually => (this as IDrawn).IsDrawnActually;
+ bool IDrawn.IsDrawnActually { get; set; } = true;
#endregion
diff --git a/Engine/Node/PostEffect/PostEffectNode.cs b/Engine/Node/PostEffect/PostEffectNode.cs
index f208c848..9dc941e2 100644
--- a/Engine/Node/PostEffect/PostEffectNode.cs
+++ b/Engine/Node/PostEffect/PostEffectNode.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
namespace Altseed2
@@ -84,6 +84,30 @@ public int ZOrder
}
private int _ZOrder = 0;
+ ///
+ /// このノードを描画するかどうかを取得または設定します。
+ ///
+ [PartsTreeSystem.SerializeField]
+ public bool IsDrawn
+ {
+ get => _IsDrawn;
+ set
+ {
+ if (_IsDrawn == value) return;
+ _IsDrawn = value;
+ this.UpdateIsDrawnActuallyOfDescendants();
+ }
+ }
+ private bool _IsDrawn = true;
+
+ ///
+ /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
+ ///
+ [PartsTreeSystem.SerializeField]
+ public bool IsDrawnActually => (this as IDrawn).IsDrawnActually;
+ [PartsTreeSystem.SerializeField]
+ bool IDrawn.IsDrawnActually { get; set; } = true;
+
///
/// の新しいインスタンスを生成します。
///
diff --git a/Engine/Node/ShapeNodes/ShapeNode.cs b/Engine/Node/ShapeNodes/ShapeNode.cs
index 1507db29..c3ef42b8 100644
--- a/Engine/Node/ShapeNodes/ShapeNode.cs
+++ b/Engine/Node/ShapeNodes/ShapeNode.cs
@@ -97,12 +97,12 @@ public bool IsDrawn
private bool _IsDrawn = true;
///
- /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
+ /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
///
[PartsTreeSystem.SerializeField]
- public bool IsDrawnActually => (this as ICullableDrawn).IsDrawnActually;
+ public bool IsDrawnActually => (this as IDrawn).IsDrawnActually;
[PartsTreeSystem.SerializeField]
- bool ICullableDrawn.IsDrawnActually { get; set; } = true;
+ bool IDrawn.IsDrawnActually { get; set; } = true;
#endregion
diff --git a/Engine/Node/SpriteNode.cs b/Engine/Node/SpriteNode.cs
index 75b1a388..f319ae0f 100644
--- a/Engine/Node/SpriteNode.cs
+++ b/Engine/Node/SpriteNode.cs
@@ -94,12 +94,12 @@ public bool IsDrawn
private bool _IsDrawn = true;
///
- /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
+ /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
///
[PartsTreeSystem.SerializeField]
- public bool IsDrawnActually => (this as ICullableDrawn).IsDrawnActually;
+ public bool IsDrawnActually => (this as IDrawn).IsDrawnActually;
[PartsTreeSystem.SerializeField]
- bool ICullableDrawn.IsDrawnActually { get; set; } = true;
+ bool IDrawn.IsDrawnActually { get; set; } = true;
#endregion
diff --git a/Engine/Node/TextNode.cs b/Engine/Node/TextNode.cs
index e62e0ab7..9b923ccc 100644
--- a/Engine/Node/TextNode.cs
+++ b/Engine/Node/TextNode.cs
@@ -93,12 +93,12 @@ public bool IsDrawn
private bool _IsDrawn = true;
///
- /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
+ /// 先祖のを考慮して、このノードを描画するかどうかを取得します。
///
[PartsTreeSystem.SerializeField]
- public bool IsDrawnActually => (this as ICullableDrawn).IsDrawnActually;
+ public bool IsDrawnActually => (this as IDrawn).IsDrawnActually;
[PartsTreeSystem.SerializeField]
- bool ICullableDrawn.IsDrawnActually { get; set; } = true;
+ bool IDrawn.IsDrawnActually { get; set; } = true;
#endregion
diff --git a/Samples/ComputeShader/Fluid.cs b/Samples/ComputeShader/Fluid.cs
new file mode 100644
index 00000000..c4b17658
--- /dev/null
+++ b/Samples/ComputeShader/Fluid.cs
@@ -0,0 +1,856 @@
+using Altseed2;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sample
+{
+ public class Fluid
+ {
+ [StructLayout(LayoutKind.Sequential, Size = 32)]
+ public struct Particle
+ {
+ public Vector2F Current;
+ public Vector2F Next;
+ public Vector2F Velocity;
+ [MarshalAs(UnmanagedType.R4)]
+ public float Pscl;
+ [MarshalAs(UnmanagedType.U1)]
+ public bool IsFix;
+ };
+
+ class ParticleNode : CommandDrawnNode
+ {
+ public bool Step { get; set; } = false;
+
+ public int IterationCount { get; set; } = 1;
+
+ private const float EffectiveRadius = 6;
+ private const float ParticleRadius = 2;
+ private const int ParticlesCount = 2048;
+
+ private readonly Material material;
+
+ private readonly Buffer particleComputeIndex;
+ private readonly Buffer particleComputeVertex;
+ private readonly Buffer particleIndex;
+ private readonly Buffer particleVertex;
+ private readonly Buffer particles;
+ private readonly Buffer gridTable;
+ private readonly Buffer gridIndicesTable;
+
+ private readonly Buffer particlesToBeCopied;
+
+ private readonly ComputePipelineState calcExternalPipeline;
+ private readonly ComputePipelineState buildGridPipeline;
+ private readonly ComputePipelineState bitonicSortPipeline;
+ private readonly ComputePipelineState clearGridIndicesPipeline;
+ private readonly ComputePipelineState buildGridIndicesPipeline;
+ private readonly ComputePipelineState calcScalingFactorPipeline;
+ private readonly ComputePipelineState calcCorrectPositionPipeline;
+ private readonly ComputePipelineState integratePipeline;
+ private readonly ComputePipelineState buildVBIB;
+
+ public static float CalcRestDensity(float h)
+ {
+ var a = 4f / (MathF.PI * MathF.Pow(h, 8f));
+ var r0 = 0.0f;
+ var l = 2 * ParticleRadius;
+ int n = (int)MathF.Ceiling(h / l) + 1;
+ for (int x = -n; x <= n; ++x)
+ {
+ for (int y = -n; y <= n; ++y)
+ {
+ var rij = new Vector2F(x * l, y * l);
+ var r = rij.Length;
+ if (r >= 0.0 && r <= h)
+ {
+ var q = h * h - r * r;
+ r0 += a * q * q * q;
+ }
+ }
+ }
+ return r0;
+ }
+
+ const string CommonCode = @"
+struct Particle
+{
+ float2 Current;
+ float2 Next;
+ float2 Velocity;
+ float Pscl;
+ bool IsFix;
+};
+
+#define EMPTY_CELL 0x7fffffff
+
+int GetHash(int2 gridPos, int num)
+{
+ return gridPos.x + gridPos.y * num;
+}
+
+int2 GetGridPos(float2 pos, float2 gridSize)
+{
+ return pos / gridSize;
+}
+";
+ private readonly Vector2I gridNum = new(60, 60);
+ private readonly Vector4F gridSize = new(5, 5, 0, 0);
+ private readonly float density = CalcRestDensity(EffectiveRadius);
+ private readonly float eps = 0.1f;
+ private readonly float dt = 1.0f / 60;
+ private readonly float wpoly6 = 4f / (MathF.PI * MathF.Pow(EffectiveRadius, 8f));
+ private readonly float gwspiky = -30f / (MathF.PI * MathF.Pow(EffectiveRadius, 5f));
+
+ public ParticleNode()
+ {
+ particles = Buffer.CreateStrict(BufferUsageType.Compute | BufferUsageType.CopyDst, ParticlesCount);
+ gridTable = Buffer.CreateStrict(BufferUsageType.Compute | BufferUsageType.CopySrc, ParticlesCount);
+ gridIndicesTable = Buffer.CreateStrict(BufferUsageType.Compute | BufferUsageType.CopySrc, gridNum.X * gridNum.Y);
+
+ particleComputeVertex = Buffer.CreateStrict(BufferUsageType.Compute | BufferUsageType.CopySrc, ParticlesCount * 4);
+ particleComputeIndex = Buffer.CreateStrict(BufferUsageType.Compute | BufferUsageType.CopySrc, ParticlesCount * 6);
+
+ particleVertex = Buffer.CreateStrict(BufferUsageType.Vertex, ParticlesCount * 4);
+ particleIndex = Buffer.CreateStrict(BufferUsageType.Index, ParticlesCount * 6);
+
+ particlesToBeCopied = Buffer.CreateStrict(BufferUsageType.CopyDst | BufferUsageType.MapRead, ParticlesCount);
+
+ const string csCalcExternalForces = CommonCode + @"
+cbuffer CB : register(b0)
+{
+ float4 Force;
+ float4 Gravity;
+ float4 Dt;
+ float4 GridNum;
+ float4 GridSize;
+};
+
+RWStructuredBuffer particles : register(u0);
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ if (particles[dtid.x].IsFix) return;
+ particles[dtid.x].Velocity += (Gravity.xy + Force.xy) * Dt.x;
+
+ float2 pos = particles[dtid.x].Current + particles[dtid.x].Velocity * Dt.x;
+ pos.x = clamp(pos.x, 8, GridNum.x * GridSize.x - 8);
+ pos.y = clamp(pos.y, 8, GridNum.y * GridSize.y - 8);
+
+ particles[dtid.x].Next = pos;
+}
+";
+
+ calcExternalPipeline = ComputePipelineState.Create();
+ calcExternalPipeline.Shader = Shader.CreateStrict("csCalcExternalForces", csCalcExternalForces, ShaderStage.Compute);
+ calcExternalPipeline.SetVector4F("Force", new Vector4F(0, 0, 0, 0));
+ calcExternalPipeline.SetVector4F("Gravity", new Vector4F(0, 100, 0, 0));
+ calcExternalPipeline.SetVector4F("Dt", new Vector4F(dt, 0, 0, 0));
+ calcExternalPipeline.SetVector4F("GridNum", new Vector4F(gridNum.X, gridNum.Y, 0, 0));
+ calcExternalPipeline.SetVector4F("GridSize", gridSize);
+
+ const string csBuildGrid = CommonCode + @"
+cbuffer CB : register(b0)
+{
+ float4 GridNum;
+ float4 GridSize;
+ float4 ParticleNum;
+};
+
+RWStructuredBuffer particles : register(u0);
+RWStructuredBuffer gridTable : register(u1);
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ if (dtid.x < ParticleNum.x)
+ {
+ gridTable[dtid.x].x = GetHash(particles[dtid.x].Next / GridSize.xy, GridNum.x);
+ gridTable[dtid.x].y = dtid.x;
+ }
+ else
+ {
+ gridTable[dtid.x].x = EMPTY_CELL;
+ gridTable[dtid.x].y = dtid.x;
+ }
+}
+";
+
+ buildGridPipeline = ComputePipelineState.Create();
+ buildGridPipeline.Shader = Shader.CreateStrict("csBuildGrid", csBuildGrid, ShaderStage.Compute);
+ buildGridPipeline.SetVector4F("GridSize", gridSize);
+ buildGridPipeline.SetVector4F("GridNum", new Vector4F(gridNum.X, gridNum.Y, 0, 0));
+ buildGridPipeline.SetVector4F("ParticleNum", new Vector4F(ParticlesCount, 0, 0, 0));
+
+ const string csBitonicSort = @"
+cbuffer CB : register(b0)
+{
+ float4 Inc;
+ float4 Dir;
+};
+
+RWStructuredBuffer gridTable : register(u0);
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ int inc = (int)Inc.x;
+ int dir = (int)Dir.x;
+ int t = dtid.x; // thread index
+ int low = t & (inc - 1); // low order bits (below INC)
+ int i = (t << 1) - low; // insert 0 at position INC
+ bool reverse = ((dir & i) == 0);
+
+ // Load
+ int2 x0 = gridTable[i];
+ int2 x1 = gridTable[inc + i];
+
+ // Sort
+ int2 auxa = x0;
+ int2 auxb = x1;
+ if ((x0.x < x1.x) ^ reverse) { x0 = auxb; x1 = auxa; }
+
+ // Store
+ gridTable[i] = x0;
+ gridTable[inc + i] = x1;
+}
+";
+
+ bitonicSortPipeline = ComputePipelineState.Create();
+ bitonicSortPipeline.Shader = Shader.CreateStrict("csBitonicSort", csBitonicSort, ShaderStage.Compute);
+
+ const string csClearGridIndices = CommonCode + @"
+RWStructuredBuffer gridIndicesTable : register(u0);
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ gridIndicesTable[dtid.x] = int2(EMPTY_CELL, EMPTY_CELL);
+}
+";
+
+ clearGridIndicesPipeline = ComputePipelineState.Create();
+ clearGridIndicesPipeline.Shader = Shader.CreateStrict("clearGridIndicesPipeline", csClearGridIndices, ShaderStage.Compute);
+
+ const string csBuildGridIndices = @"
+cbuffer CB : register(b0)
+{
+ float4 ParticleNum;
+};
+
+RWStructuredBuffer gridTable : register(u0);
+RWStructuredBuffer gridIndicesTable : register(u1);
+
+int getValidId(int id) {
+ int n = ParticleNum.x;
+ return fmod(id + n, n);
+}
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ int id = dtid.x;
+ int idPrev = getValidId(id - 1);
+ int idNext = getValidId(id + 1);
+
+ int cell = gridTable[id].x;
+ int cellPrev = gridTable[idPrev].x;
+ int cellNext = gridTable[idNext].x;
+
+ if (cell != cellPrev)
+ {
+ gridIndicesTable[cell].x = id;
+ }
+
+ if (cell != cellNext)
+ {
+ gridIndicesTable[cell].y = id + 1;
+ }
+}
+";
+
+ buildGridIndicesPipeline = ComputePipelineState.Create();
+ buildGridIndicesPipeline.Shader = Shader.CreateStrict("csBuildGridIndices", csBuildGridIndices, ShaderStage.Compute);
+ buildGridIndicesPipeline.SetVector4F("ParticleNum", new Vector4F(ParticlesCount, 0, 0, 0));
+
+ const string csCalcScalingFactor = CommonCode + @"
+cbuffer CB : register(b0)
+{
+ float4 GridNum;
+ float4 GridSize;
+ float4 EffectiveRadius;
+ float4 Density;
+ float4 Eps;
+ float4 Dt;
+ float4 Wpoly6;
+ float4 GWspiky;
+};
+
+RWStructuredBuffer particles : register(u0);
+RWStructuredBuffer gridTable : register(u1);
+RWStructuredBuffer gridIndicesTable : register(u2);
+
+float2 CalcDensityCellPB(int2 gridPos, int i, float2 pos0)
+{
+ int gridHash = GetHash(gridPos, GridNum.x);
+ int startIndex = gridIndicesTable[gridHash].x;
+
+ float h2 = EffectiveRadius.x * EffectiveRadius.x;
+ float dens = 0.0f;
+ if(startIndex != EMPTY_CELL){ // セルが空でないかのチェック
+ // セル内のパーティクルで反復
+ int endIndex = gridIndicesTable[gridHash].y;
+ for(int j = startIndex; j < endIndex; ++j){
+ // if(j == i) continue;
+
+ float2 pos1 = particles[gridTable[j].y].Next;
+
+ float r2 = dot(pos0 - pos1, pos0 - pos1);
+
+ if(r2 <= h2){
+ float q = h2 - r2;
+ dens += Wpoly6.x * q * q * q;
+ }
+ }
+ }
+
+ return dens;
+}
+
+float CalcScalingFactorCell(int2 gridPos, int i, float2 pos0)
+{
+ int gridHash = GetHash(gridPos, GridNum.x);
+ int startIndex = gridIndicesTable[gridHash].x;
+
+ float h = EffectiveRadius.x;
+ float r0 = Density.x;
+ float sd = 0.0f;
+ float2 sd2 = float2(0.0f, 0.0f);
+ if(startIndex != EMPTY_CELL){ // セルが空でないかのチェック
+ // セル内のパーティクルで反復
+ uint endIndex = gridIndicesTable[gridHash].y;
+ for(uint j = startIndex; j < endIndex; ++j){
+
+ float2 pos1 = particles[gridTable[j].y].Next;
+
+ float2 rij = pos0 - pos1;
+ float r = length(rij);
+
+ if(r <= h && r > 0.0){
+ float q = h - r;
+
+ // Spikyカーネルで位置変動を計算
+ float2 dp = (GWspiky.x * q * q * rij / r) / r0;
+ sd2 += dp;
+ sd += dot(dp, dp);
+ }
+ }
+ }
+ return sd + dot(sd2, sd2);
+}
+
+void CalcScalingFactor(int id)
+{
+ float2 pos = particles[id].Next; // パーティクル位置
+ float h = EffectiveRadius.x;
+ float r0 = Density.x;
+
+ // パーティクル周囲のグリッド
+ int2 grid_pos0 = GetGridPos(pos-float2(h, h), GridSize.xy);
+ int2 grid_pos1 = GetGridPos(pos+float2(h, h), GridSize.xy);
+
+
+ float dens = 0.0f;
+ float sd = 0.0f;
+ for(int y = grid_pos0.y; y <= grid_pos1.y; ++y){
+ for(int x = grid_pos0.x; x <= grid_pos1.x; ++x){
+ int2 n_grid_pos = int2(x, y);
+ // 周囲のグリッドも含めて近傍探索,密度計算
+ dens += CalcDensityCellPB(n_grid_pos, id, pos);
+ // 周囲のグリッドも含めて近傍探索,スケーリングファクタの分母項計算
+ sd += CalcScalingFactorCell(n_grid_pos, id, pos);
+ }
+ }
+
+ // 密度拘束条件
+ float C = dens/r0-1.0;
+
+ // スケーリングファクタの計算
+ particles[id].Pscl = -C/(sd+Eps.x);
+}
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ int id = dtid.x;
+ CalcScalingFactor(id);
+}
+";
+
+ calcScalingFactorPipeline = ComputePipelineState.Create();
+ calcScalingFactorPipeline.Shader = Shader.CreateStrict("csCalcScalingFactor", csCalcScalingFactor, ShaderStage.Compute);
+ calcScalingFactorPipeline.SetVector4F("GridNum", new Vector4F(gridNum.X, gridNum.Y, 0, 0));
+ calcScalingFactorPipeline.SetVector4F("GridSize", gridSize);
+ calcScalingFactorPipeline.SetVector4F("EffectiveRadius", new Vector4F(EffectiveRadius, 0, 0, 0));
+ calcScalingFactorPipeline.SetVector4F("Density", new Vector4F(density, 0, 0, 0));
+ calcScalingFactorPipeline.SetVector4F("Eps", new Vector4F(eps, 0, 0, 0));
+ calcScalingFactorPipeline.SetVector4F("Dt", new Vector4F(dt, 0, 0, 0));
+ calcScalingFactorPipeline.SetVector4F("Wpoly6", new Vector4F(wpoly6, 0, 0, 0));
+ calcScalingFactorPipeline.SetVector4F("GWspiky", new Vector4F(gwspiky, 0, 0, 0));
+
+ const string csCalcCorrectPosition = CommonCode + @"
+cbuffer CB : register(b0)
+{
+ float4 GridNum;
+ float4 GridSize;
+ float4 EffectiveRadius;
+ float4 Density;
+ float4 Eps;
+ float4 Dt;
+ float4 Wpoly6;
+ float4 GWspiky;
+};
+
+RWStructuredBuffer particles : register(u0);
+RWStructuredBuffer gridTable : register(u1);
+RWStructuredBuffer gridIndicesTable : register(u2);
+
+float2 CalcPositionCorrectionCell(int2 gridPos, int i, float2 pos0)
+{
+ int gridHash = GetHash(gridPos, GridNum.x);
+ int startIndex = gridIndicesTable[gridHash].x;
+
+ float h = EffectiveRadius.x;
+ float r0 = Density.x;
+ float2 dp = float2(0.0f, 0.0f);
+
+ float dt = Dt.x;
+
+ float si = particles[i].Pscl;
+
+ if(startIndex != EMPTY_CELL){ // セルが空でないかのチェック
+ // セル内のパーティクルで反復
+ uint endIndex = gridIndicesTable[gridHash].y;
+ for(uint j = startIndex; j < endIndex; ++j){
+ if(gridTable[j].y == i) continue;
+
+ float2 pos1 = particles[gridTable[j].y].Next;
+
+ float2 rij = pos0 - pos1;
+ float r = length(rij);
+
+ if(r <= h && r > 0.0){
+ float scorr = 0;
+ {
+ float q = h * h - r * r;
+ float q2 = h * h - 0.04 * h * h;
+
+ float ww = Wpoly6.x * q * q * q / (Wpoly6.x * q2 * q2 * q2);
+ scorr = -0.1 * pow(ww, 4) * dt * dt;
+ }
+
+ {
+ float q = h - r;
+ float sj = particles[gridTable[j].y].Pscl;
+
+ // Spikyカーネルで位置修正量を計算
+ dp += (si + sj + scorr) * (GWspiky.x * q * q * rij / r) / r0;
+ }
+ }
+ }
+ }
+
+ return dp;
+}
+
+float2 CalcPositionCorrection(int id)
+{
+ float2 pos = particles[id].Next; // パーティクル位置
+ float h = EffectiveRadius.x;
+
+ // パーティクル周囲のグリッド
+ int2 grid_pos0 = GetGridPos(pos - h, GridSize.xy);
+ int2 grid_pos1 = GetGridPos(pos + h, GridSize.xy);
+
+ // 周囲のグリッドも含めて近傍探索,位置修正量を計算
+ float2 dpij = float2(0.0f, 0.0f);
+ for(int y = grid_pos0.y; y <= grid_pos1.y; ++y){
+ for(int x = grid_pos0.x; x <= grid_pos1.x; ++x){
+ int2 n_grid_pos = int2(x, y);
+ dpij += CalcPositionCorrectionCell(n_grid_pos, id, pos);
+ }
+ }
+
+ return dpij;
+}
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ int id = dtid.x;
+ if (particles[id].IsFix) return;
+ particles[id].Next += CalcPositionCorrection(id);
+}
+";
+
+ calcCorrectPositionPipeline = ComputePipelineState.Create();
+ calcCorrectPositionPipeline.Shader = Shader.CreateStrict("csCalcCorrectPosition", csCalcCorrectPosition, ShaderStage.Compute);
+ calcCorrectPositionPipeline.SetVector4F("GridNum", new Vector4F(gridNum.X, gridNum.Y, 0, 0));
+ calcCorrectPositionPipeline.SetVector4F("GridSize", gridSize);
+ calcCorrectPositionPipeline.SetVector4F("EffectiveRadius", new Vector4F(EffectiveRadius, 0, 0, 0));
+ calcCorrectPositionPipeline.SetVector4F("Density", new Vector4F(density, 0, 0, 0));
+ calcCorrectPositionPipeline.SetVector4F("Eps", new Vector4F(eps, 0, 0, 0));
+ calcCorrectPositionPipeline.SetVector4F("Dt", new Vector4F(dt, 0, 0, 0));
+ calcCorrectPositionPipeline.SetVector4F("Wpoly6", new Vector4F(wpoly6, 0, 0, 0));
+ calcCorrectPositionPipeline.SetVector4F("GWspiky", new Vector4F(gwspiky, 0, 0, 0));
+
+ const string csIntegrate = CommonCode + @"
+cbuffer CB : register(b0)
+{
+ float4 Dt;
+};
+
+RWStructuredBuffer particles : register(u0);
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ particles[dtid.x].Velocity = (particles[dtid.x].Next - particles[dtid.x].Current) / Dt.x;
+ particles[dtid.x].Current = particles[dtid.x].Next;
+}
+";
+
+ integratePipeline = ComputePipelineState.Create();
+ integratePipeline.Shader = Shader.CreateStrict("csIntegrate", csIntegrate, ShaderStage.Compute);
+ integratePipeline.SetVector4F("Dt", new Vector4F(dt, 0, 0, 0));
+
+ const string csBuildVBIB = CommonCode + @"
+struct Vertex
+{
+ float3 Position;
+ int Color;
+ float2 UV1;
+ float2 UV2;
+};
+
+cbuffer CB : register(b0)
+{
+ float4 ParticleRadius;
+ float4 Color;
+ float4 FixedColor;
+};
+
+RWStructuredBuffer particles : register(u0);
+RWStructuredBuffer vertex_ : register(u1);
+RWStructuredBuffer index : register(u2);
+
+int DecodeFloatRGBA(float4 rgba) {
+ int res = 0;
+ res += (int)(rgba.a * 255);
+ res = res << 8;
+ res += (int)(rgba.b * 255);
+ res = res << 8;
+ res += (int)(rgba.g * 255);
+ res = res << 8;
+ res += (int)(rgba.r * 255);
+ return res;
+}
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ int c = DecodeFloatRGBA(lerp(Color, FixedColor, particles[dtid.x].IsFix));
+ for (int i = 0; i < 4; i++)
+ {
+ vertex_[dtid.x * 4 + i].Color = c;
+ }
+
+ float3 pos = float3(particles[dtid.x].Current, 0.5);
+
+ vertex_[dtid.x * 4].Position = pos + float3(-1, -1, 0) * ParticleRadius.x * 3;
+ vertex_[dtid.x * 4 + 1].Position = pos + float3(1, -1, 0) * ParticleRadius.x * 3;
+ vertex_[dtid.x * 4 + 2].Position = pos + float3(1, 1, 0) * ParticleRadius.x * 3;
+ vertex_[dtid.x * 4 + 3].Position = pos + float3(-1, 1, 0) * ParticleRadius.x * 3;
+
+ vertex_[dtid.x * 4].UV1 = float2(0, 0);
+ vertex_[dtid.x * 4 + 1].UV1 = float2(1, 0);
+ vertex_[dtid.x * 4 + 2].UV1 = float2(1, 1);
+ vertex_[dtid.x * 4 + 3].UV1 = float2(0, 1);
+
+ index[dtid.x * 6] = dtid.x * 4;
+ index[dtid.x * 6 + 1] = dtid.x * 4 + 1;
+ index[dtid.x * 6 + 2] = dtid.x * 4 + 2;
+ index[dtid.x * 6 + 3] = dtid.x * 4;
+ index[dtid.x * 6 + 4] = dtid.x * 4 + 2;
+ index[dtid.x * 6 + 5] = dtid.x * 4 + 3;
+}
+";
+
+ buildVBIB = ComputePipelineState.Create();
+ buildVBIB.Shader = Shader.CreateStrict("csIntegrate", csBuildVBIB, ShaderStage.Compute);
+ buildVBIB.SetVector4F("ParticleRadius", new Vector4F(ParticleRadius, 0, 0, 0));
+ buildVBIB.SetVector4F("Color", new Vector4F(0.2f, 0.2f, 1f, 1f));
+ buildVBIB.SetVector4F("FixedColor", new Vector4F(0f, 0f, 0f, 1f));
+
+ material = Material.Create();
+ var blend = AlphaBlend.Add;
+ blend.BlendEquationRGB = BlendEquation.Max;
+ material.AlphaBlend = blend;
+
+ const string psCode = @"
+struct PS_INPUT
+{
+ float4 Position : SV_POSITION;
+ float4 Color : COLOR0;
+ float2 UV1 : UV0;
+ float2 UV2 : UV1;
+};
+float4 main(PS_INPUT input) : SV_TARGET
+{
+ float4 c;
+ float r = length((input.UV1 - 0.5) * 2);
+ float a = r > 1 ? 0 : (-4 / 9 * pow(r, 6) + 17 / 9 * pow(r, 4) - 22 / 9 * pow(r, 2) + 1);
+ c = float4(input.Color.rgb, input.Color.a * a);
+ return c;
+}";
+ material.SetShader(Shader.CreateStrict("ps", psCode, ShaderStage.Pixel));
+ }
+
+ protected override void OnAdded()
+ {
+ var particlesInput = Buffer.CreateStrict(BufferUsageType.MapWrite | BufferUsageType.CopySrc, ParticlesCount);
+ using (var data = particlesInput.WriteLockStrict())
+ {
+ int ind = 0;
+ for (int y = 0; y < 300 / (ParticleRadius * 2); y++)
+ {
+ for (int x = 0; x < 300 / (ParticleRadius * 2); x++)
+ {
+ if (x * (ParticleRadius * 2) + ParticleRadius < 8 ||
+ x * (ParticleRadius * 2) + ParticleRadius > 300 - 8 ||
+ y * (ParticleRadius * 2) + ParticleRadius < 8 ||
+ y * (ParticleRadius * 2) + ParticleRadius > 300 - 8)
+ {
+ data.Span[ind++] = new Particle()
+ {
+ Current = new Vector2F(x * (ParticleRadius * 2) + ParticleRadius, y * (ParticleRadius * 2) + ParticleRadius),
+ Next = new Vector2F(x * (ParticleRadius * 2) + ParticleRadius, y * (ParticleRadius * 2) + ParticleRadius),
+ Velocity = new Vector2F(0, 0),
+ Pscl = 0,
+ IsFix = true,
+ };
+ }
+ }
+ }
+
+ for (int i = ind; i < particlesInput.Count; i++)
+ {
+ data.Span[i] = new Particle()
+ {
+ Current = new Vector2F(i % 50 * 4 + 10, i / 50 * 4 + 20),
+ Velocity = new Vector2F(0, 0),
+ Pscl = 0,
+ };
+ }
+ }
+
+ Engine.Graphics.CommandList.Begin();
+
+ Engine.Graphics.CommandList.CopyBuffer(particlesInput, particles);
+
+ Engine.Graphics.CommandList.End();
+ Engine.Graphics.ExecuteCommandList();
+ Engine.Graphics.WaitFinish();
+ }
+
+ protected override void OnUpdate()
+ {
+ var commandList = Engine.Graphics.CommandList;
+
+ commandList.Begin();
+ commandList.BeginComputePass();
+
+ if (Step)
+ {
+ commandList.ComputePipelineState = calcExternalPipeline;
+ commandList.SetComputeBuffer(particles, 0);
+ commandList.Dispatch(ParticlesCount, 1, 1);
+ commandList.ResetComputeBuffers();
+
+ for (int l = 0; l < IterationCount; l++)
+ {
+ // TODO: ResourceBarrierの関係でDirectX12ではこれを入れるとdispatchの完了を待つので正しく動く
+ commandList.CopyBuffer(particles, particlesToBeCopied);
+
+ commandList.ComputePipelineState = buildGridPipeline;
+ commandList.SetComputeBuffer(particles, 0);
+ commandList.SetComputeBuffer(gridTable, 1);
+ commandList.Dispatch(gridTable.Count, 1, 1);
+ commandList.ResetComputeBuffers();
+
+ commandList.SetComputeBuffer(gridTable, 0);
+
+ int nlog = (int)MathF.Log(gridTable.Count, 2);
+ int inc;
+
+ for (int i = 0; i < nlog; i++)
+ {
+ inc = 1 << i;
+
+ bitonicSortPipeline.SetVector4F("Dir", new Vector4F(2 << i, 0, 0, 0));
+
+ for (int j = 0; j < i + 1; j++)
+ {
+ bitonicSortPipeline.SetVector4F("Inc", new Vector4F(inc, 0, 0, 0));
+ commandList.ComputePipelineState = bitonicSortPipeline;
+ commandList.Dispatch(gridTable.Count / 2, 1, 1);
+ inc /= 2;
+ }
+ }
+ commandList.ResetComputeBuffers();
+
+ commandList.ComputePipelineState = clearGridIndicesPipeline;
+ commandList.SetComputeBuffer(gridIndicesTable, 0);
+ commandList.Dispatch(gridIndicesTable.Count, 1, 1);
+ commandList.ResetComputeBuffers();
+
+ commandList.ComputePipelineState = buildGridIndicesPipeline;
+ commandList.SetComputeBuffer(gridTable, 0);
+ commandList.SetComputeBuffer(gridIndicesTable, 1);
+ commandList.Dispatch(gridTable.Count, 1, 1);
+ commandList.ResetComputeBuffers();
+
+ commandList.ComputePipelineState = calcScalingFactorPipeline;
+ commandList.SetComputeBuffer(particles, 0);
+ commandList.SetComputeBuffer(gridTable, 1);
+ commandList.SetComputeBuffer(gridIndicesTable, 2);
+ commandList.Dispatch(ParticlesCount, 1, 1);
+ commandList.ResetComputeBuffers();
+
+ commandList.ComputePipelineState = calcCorrectPositionPipeline;
+ commandList.SetComputeBuffer(particles, 0);
+ commandList.SetComputeBuffer(gridTable, 1);
+ commandList.SetComputeBuffer(gridIndicesTable, 2);
+ commandList.Dispatch(ParticlesCount, 1, 1);
+ commandList.ResetComputeBuffers();
+ }
+
+ commandList.ComputePipelineState = integratePipeline;
+ commandList.SetComputeBuffer(particles, 0);
+ commandList.Dispatch(ParticlesCount, 1, 1);
+ commandList.ResetComputeBuffers();
+ }
+
+ commandList.ComputePipelineState = buildVBIB;
+ commandList.SetComputeBuffer(particles, 0);
+ commandList.SetComputeBuffer(particleComputeVertex, 1);
+ commandList.SetComputeBuffer(particleComputeIndex, 2);
+ commandList.Dispatch(ParticlesCount, 1, 1);
+ commandList.ResetComputeBuffers();
+
+ commandList.EndComputePass();
+
+ commandList.CopyBuffer(particleComputeIndex, particleIndex);
+ commandList.CopyBuffer(particleComputeVertex, particleVertex);
+
+ commandList.End();
+ Engine.Graphics.ExecuteCommandList();
+ Engine.Graphics.WaitFinish();
+ }
+
+ protected override void Draw()
+ {
+ Engine.Graphics.CommandList.Material = material;
+ Engine.Graphics.CommandList.SetIndexBuffer(particleIndex);
+ Engine.Graphics.CommandList.SetVertexBuffer(particleVertex);
+ Engine.Graphics.CommandList.Draw(particleIndex.Count / 3);
+ }
+ }
+
+ public static void Main(string[] args)
+ {
+ Engine.Initialize("Fluids Sim", 640, 480);
+ Engine.FramerateMode = FramerateMode.Variable;
+ Engine.TargetFPS = 240;
+
+ var renderTexture = RenderTexture.Create(new Vector2I(300, 300), TextureFormat.R8G8B8A8_UNORM);
+
+ var camera = new CameraNode()
+ {
+ Group = 1,
+ IsColorCleared = true,
+ ClearColor = new Color(0, 0, 0, 0),
+ TargetTexture = renderTexture,
+ };
+ Engine.AddNode(camera);
+
+ var camera2 = new CameraNode()
+ {
+ IsColorCleared = true,
+ Group = 2,
+ };
+ Engine.AddNode(camera2);
+
+ var particlesNode = new ParticleNode()
+ {
+ CameraGroup = 1,
+ IterationCount = 4,
+ Step = false,
+ };
+
+ Engine.AddNode(particlesNode);
+
+ var finalizeNode = new SpriteNode()
+ {
+ Texture = renderTexture,
+ Position = Engine.WindowSize / 2 - renderTexture.Size / 2,
+ CameraGroup = 2,
+ };
+ finalizeNode.Material = Material.Create();
+ const string psCode2 = @"
+Texture2D mainTex : register(t0);
+SamplerState mainSamp : register(s0);
+struct PS_INPUT
+{
+ float4 Position : SV_POSITION;
+ float4 Color : COLOR0;
+ float2 UV1 : UV0;
+ float2 UV2 : UV1;
+};
+float4 main(PS_INPUT input) : SV_TARGET
+{
+ float4 c;
+ if (mainTex.Sample(mainSamp, input.UV1).a < 0.5)
+ discard;
+ c = float4(mainTex.Sample(mainSamp, input.UV1).rgb, 1);
+ return c;
+}";
+ finalizeNode.Material.SetShader(Shader.CreateStrict("ps2", psCode2, ShaderStage.Pixel));
+ finalizeNode.Material.SetTexture("mainTex", renderTexture);
+ Engine.AddNode(finalizeNode);
+
+ var font = Font.LoadDynamicFont("TestData/Font/mplus-1m-regular.ttf", 64);
+ var text = new TextNode() { Font = font, FontSize = 30, Text = "", ZOrder = 10, CameraGroup = 2 };
+ Engine.AddNode(text);
+
+ while (Engine.DoEvents())
+ {
+ if (Engine.Keyboard.GetKeyState(Key.Space) == ButtonState.Push)
+ {
+ particlesNode.Step = !particlesNode.Step;
+ }
+
+ text.Text = $"FPS: {Engine.CurrentFPS}\nIterationCount:{particlesNode.IterationCount}\nStep: {particlesNode.Step}";
+
+ Engine.Update();
+ }
+
+ Engine.Terminate();
+ }
+ }
+}
diff --git a/Samples/Viewer.cs b/Samples/Viewer.cs
index 53e28f55..7e917511 100644
--- a/Samples/Viewer.cs
+++ b/Samples/Viewer.cs
@@ -47,6 +47,7 @@ static void Main(string[] args)
Samples.Add(new Sample("PostEffect/LightBloom", "ライトブルームのポストエフェクトを適用します。", typeof(PostEffectLightBloom)));
Samples.Add(new Sample("CustomPostEffect", "自作のポストエフェクトを適用します。", typeof(CustomPostEffect)));
Samples.Add(new Sample("Movie", "映像を再生します。", typeof(Movie)));
+ Samples.Add(new Sample("Fluid", "2D流体シミュレーションを行います.", typeof(Fluid)));
SamplesString = string.Join('\t', Samples.Select(s => s.Name));
@@ -147,7 +148,14 @@ public void Run()
{
var type = Type.GetType(TypeName);
var mainMethod = type?.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
- mainMethod?.Invoke(null, new[] { new string[] { } });
+ try
+ {
+ mainMethod?.Invoke(null, new[] { new string[] { } });
+ }
+ catch (TargetInvocationException e)
+ {
+ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e.InnerException).Throw();
+ }
}
}
}
diff --git a/Test/ComputeShader.cs b/Test/ComputeShader.cs
new file mode 100644
index 00000000..10c77d60
--- /dev/null
+++ b/Test/ComputeShader.cs
@@ -0,0 +1,392 @@
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Altseed2.Test
+{
+ [TestFixture]
+ class ComputeShader
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ public struct InputData
+ {
+ public float value1;
+ public float value2;
+ };
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct OutputData
+ {
+ public float value;
+ };
+
+ [Test, Apartment(ApartmentState.STA)]
+ public void Buffer()
+ {
+ var tc = new TestCore();
+ tc.Init();
+
+ int dataSize = 256;
+
+ var input = Buffer.CreateStrict(BufferUsageType.MapWrite | BufferUsageType.CopySrc, dataSize);
+ Assert.NotNull(input);
+
+ using (var data = input.WriteLockStrict())
+ {
+ for (int i = 0; i < input.Count; i++)
+ {
+ data.Span[i] = new InputData()
+ {
+ value1 = i * 2,
+ value2 = i * 2 + 1,
+ };
+ }
+ }
+
+ tc.End();
+ }
+
+ [Test, Apartment(ApartmentState.STA)]
+ public void ComputeShaderBasic()
+ {
+ var tc = new TestCore();
+ tc.Init();
+
+ string csCode1 = @"
+struct CS_INPUT{
+ float value1;
+ float value2;
+};
+
+struct CS_OUTPUT{
+ float value;
+};
+
+cbuffer CB : register(b0)
+{
+ float4 offset;
+};
+
+RWStructuredBuffer read : register(u0);
+RWStructuredBuffer write : register(u1);
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ write[dtid.x].value = read[dtid.x].value1 * read[dtid.x].value2 + offset.x;
+}
+";
+
+ string csCode2 = @"
+struct CS_INPUT{
+ float value1;
+ float value2;
+};
+
+struct CS_OUTPUT{
+ float value;
+};
+
+cbuffer CB : register(b0)
+{
+ float4 offset;
+};
+
+RWStructuredBuffer read : register(u0);
+RWStructuredBuffer write : register(u1);
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ write[dtid.x].value = read[dtid.x].value1 * read[dtid.x].value2 * offset.x;
+}
+";
+
+ int offset = 100;
+
+ var pip1 = ComputePipelineState.Create();
+ pip1.Shader = Shader.Create("cs1", csCode1, ShaderStage.Compute);
+ pip1.SetVector4F("offset", new Vector4F(offset, 0, 0, 0));
+
+ var pip2 = ComputePipelineState.Create();
+ pip2.Shader = Shader.Create("cs2", csCode2, ShaderStage.Compute);
+ pip2.SetVector4F("offset", new Vector4F(offset, 0, 0, 0));
+
+ int dataSize = 256;
+
+ var input = Buffer.CreateStrict(BufferUsageType.MapWrite | BufferUsageType.CopySrc, dataSize);
+ Assert.NotNull(input);
+
+ using (var data = input.WriteLockStrict())
+ {
+ for (int i = 0; i < input.Count; i++)
+ {
+ data.Span[i] = new InputData()
+ {
+ value1 = i * 2,
+ value2 = i * 2 + 1,
+ };
+ }
+ }
+
+ var inputBuffer = Buffer.CreateStrict(BufferUsageType.Compute | BufferUsageType.CopyDst, dataSize);
+
+ var write1 = Buffer.CreateStrict(BufferUsageType.Compute | BufferUsageType.CopySrc, dataSize);
+ var write2 = Buffer.CreateStrict(BufferUsageType.Compute | BufferUsageType.CopySrc, dataSize);
+
+ Assert.NotNull(inputBuffer);
+ Assert.NotNull(write1);
+ Assert.NotNull(write2);
+
+ var write1Dst = Buffer.Create(BufferUsageType.MapRead | BufferUsageType.CopyDst, dataSize);
+ var write2Dst = Buffer.Create(BufferUsageType.MapRead | BufferUsageType.CopyDst, dataSize);
+
+ Assert.NotNull(write1Dst);
+ Assert.NotNull(write2Dst);
+
+ Engine.Graphics.CommandList.Begin();
+
+ Engine.Graphics.CommandList.CopyBuffer(input, inputBuffer);
+
+ Engine.Graphics.CommandList.BeginComputePass();
+
+ Engine.Graphics.CommandList.ComputePipelineState = pip1;
+ Engine.Graphics.CommandList.SetComputeBuffer(inputBuffer, 0);
+ Engine.Graphics.CommandList.SetComputeBuffer(write1, 1);
+ Engine.Graphics.CommandList.Dispatch(dataSize, 1, 1);
+ Engine.Graphics.CommandList.ResetComputeBuffers();
+
+ Engine.Graphics.CommandList.ComputePipelineState = pip2;
+ Engine.Graphics.CommandList.SetComputeBuffer(inputBuffer, 0);
+ Engine.Graphics.CommandList.SetComputeBuffer(write2, 1);
+ Engine.Graphics.CommandList.Dispatch(dataSize, 1, 1);
+ Engine.Graphics.CommandList.ResetComputeBuffers();
+
+ Engine.Graphics.CommandList.EndComputePass();
+
+ Engine.Graphics.CommandList.CopyBuffer(write1, write1Dst);
+ Engine.Graphics.CommandList.CopyBuffer(write2, write2Dst);
+
+ Engine.Graphics.CommandList.End();
+ Engine.Graphics.ExecuteCommandList();
+ Engine.Graphics.WaitFinish();
+
+ using (var readValues = input.ReadLockStrict())
+ using (var write1Values = write1Dst.ReadLockStrict())
+ using (var write2Values = write2Dst.ReadLockStrict())
+ {
+ for (int i = 0; i < readValues.Span.Length; i++)
+ {
+ Assert.AreEqual(
+ write1Values.Span[i].value,
+ readValues.Span[i].value1 * readValues.Span[i].value2 + offset);
+ Assert.AreEqual(
+ write2Values.Span[i].value,
+ readValues.Span[i].value1 * readValues.Span[i].value2 * offset);
+ }
+ }
+
+ tc.End();
+ }
+
+ [Test, Apartment(ApartmentState.STA)]
+ public void ComputeTiming()
+ {
+ var tc = new TestCore();
+ tc.Init();
+
+ const string code = @"
+cbuffer CB : register(b0)
+{
+ float4 Value;
+};
+
+RWStructuredBuffer myData : register(u0);
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ myData[dtid.x] += Value.x;
+}
+";
+ var pipeline = ComputePipelineState.Create();
+ pipeline.Shader = Shader.CreateStrict("csBitonicSort", code, ShaderStage.Compute);
+
+ const int Count = 1;
+ var input = Buffer.CreateStrict(BufferUsageType.CopySrc | BufferUsageType.MapWrite, Count);
+ var computeBuffer = Buffer.CreateStrict(BufferUsageType.CopyDst | BufferUsageType.Compute, Count);
+ var output1 = Buffer.CreateStrict(BufferUsageType.CopyDst | BufferUsageType.MapRead, Count);
+ var output2 = Buffer.CreateStrict(BufferUsageType.CopyDst | BufferUsageType.MapRead, Count);
+
+ using (var c = input.WriteLockStrict())
+ {
+ for (int i = 0; i < Count; i++)
+ {
+ c.Span[i] = 0;
+ }
+ }
+
+ Engine.Graphics.CommandList.Begin();
+ Engine.Graphics.CommandList.CopyBuffer(input, computeBuffer);
+
+ Engine.Graphics.CommandList.BeginComputePass();
+
+ pipeline.SetVector4F("Value", new Vector4F(1, 0, 0, 0));
+ Engine.Graphics.CommandList.ComputePipelineState = pipeline;
+ Engine.Graphics.CommandList.SetComputeBuffer(computeBuffer, 0);
+ Engine.Graphics.CommandList.Dispatch(computeBuffer.Count, 1, 1);
+ Engine.Graphics.CommandList.ResetComputeBuffers();
+
+ Engine.Graphics.CommandList.CopyBuffer(computeBuffer, output1);
+
+ pipeline.SetVector4F("Value", new Vector4F(2, 0, 0, 0));
+ Engine.Graphics.CommandList.ComputePipelineState = pipeline;
+ Engine.Graphics.CommandList.SetComputeBuffer(computeBuffer, 0);
+ Engine.Graphics.CommandList.Dispatch(computeBuffer.Count, 1, 1);
+ Engine.Graphics.CommandList.ResetComputeBuffers();
+
+ Engine.Graphics.CommandList.CopyBuffer(computeBuffer, output2);
+
+ Engine.Graphics.CommandList.EndComputePass();
+
+ Engine.Graphics.CommandList.End();
+ Engine.Graphics.ExecuteCommandList();
+ Engine.Graphics.WaitFinish();
+
+ using (var c1 = output1.ReadLockStrict())
+ using (var c2 = output2.ReadLockStrict())
+ {
+ for (int i = 0; i < Count - 1; i++)
+ {
+ Assert.AreEqual(1, c1.Span[i], $"output1[{i}]");
+ Assert.AreEqual(3, c2.Span[i], $"output2[{i}]");
+ }
+ }
+
+ tc.End();
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct MyStruct
+ {
+ [MarshalAs(UnmanagedType.I4)]
+ public int Key;
+ [MarshalAs(UnmanagedType.I4)]
+ public int Index;
+ }
+ [Test, Apartment(ApartmentState.STA)]
+ public void BitonicSort()
+ {
+ var tc = new TestCore();
+ tc.Init();
+
+ const string csBitonicSort = @"
+cbuffer CB : register(b0)
+{
+ float4 Inc_Dir;
+};
+
+struct MyStruct
+{
+ int key;
+ int index;
+};
+
+bool compareMyStruct(MyStruct a, MyStruct b) { return a.key < b.key; }
+
+RWStructuredBuffer myData : register(u0);
+
+[numthreads(1, 1, 1)]
+void main(uint3 dtid : SV_DispatchThreadID)
+{
+ int inc = (int)Inc_Dir.x;
+ int dir = (int)Inc_Dir.y;
+ int t = dtid.x; // thread index
+ int low = t & (inc - 1); // low order bits (below INC)
+ int i = (t << 1) - low; // insert 0 at position INC
+ bool reverse = ((dir & i) == 0);
+
+ // Load
+ MyStruct x0 = myData[i];
+ MyStruct x1 = myData[inc + i];
+
+ // Sort
+ MyStruct auxa = x0;
+ MyStruct auxb = x1;
+ if (compareMyStruct(x0, x1) ^ reverse) { x0 = auxb; x1 = auxa; }
+
+ // Store
+ myData[i] = x0;
+ myData[inc + i] = x1;
+}
+";
+ var bitonicSortPipeline = ComputePipelineState.Create();
+ bitonicSortPipeline.Shader = Shader.CreateStrict("csBitonicSort", csBitonicSort, ShaderStage.Compute);
+
+ const int Count = 2048;
+ var input = Buffer.CreateStrict(BufferUsageType.CopySrc | BufferUsageType.MapWrite, Count);
+ var computeBuffer = Buffer.CreateStrict(BufferUsageType.CopyDst | BufferUsageType.Compute, Count);
+ var output = Buffer.CreateStrict(BufferUsageType.CopyDst | BufferUsageType.MapRead, Count);
+
+ using (var c = input.WriteLockStrict())
+ {
+ var rand = new Random();
+
+ for (int i = 0; i < Count; i++)
+ {
+ c.Span[i] = new MyStruct { Key = rand.Next(), Index = i };
+ }
+ }
+
+ Engine.Graphics.CommandList.Begin();
+ Engine.Graphics.CommandList.CopyBuffer(input, computeBuffer);
+
+ Engine.Graphics.CommandList.BeginComputePass();
+
+ Engine.Graphics.CommandList.SetComputeBuffer(computeBuffer, 0);
+
+ for (int i = 0; i < (int)MathF.Log(computeBuffer.Count, 2); i++)
+ {
+ var inc = 1 << i;
+ var dir = 2 << i;
+ for (int j = 0; j < i + 1; j++)
+ {
+ bitonicSortPipeline.SetVector4F("Inc_Dir", new Vector4F(inc, dir, 0, 0));
+ Engine.Graphics.CommandList.ComputePipelineState = bitonicSortPipeline;
+ Engine.Graphics.CommandList.Dispatch(computeBuffer.Count / 2, 1, 1);
+
+ inc /= 2;
+ }
+ }
+
+ Engine.Graphics.CommandList.ResetComputeBuffers();
+
+ Engine.Graphics.CommandList.EndComputePass();
+
+ Engine.Graphics.CommandList.CopyBuffer(computeBuffer, output);
+
+ Engine.Graphics.CommandList.End();
+ Engine.Graphics.ExecuteCommandList();
+ Engine.Graphics.WaitFinish();
+
+ using (var c = output.ReadLockStrict())
+ {
+ var prev = c.Span[0].Key;
+ for (int i = 0; i < Count; i++)
+ {
+ var current = c.Span[i].Key;
+ Console.WriteLine($"{i}: {current}");
+ Assert.GreaterOrEqual(current, prev);
+ prev = current;
+ }
+ }
+
+ tc.End();
+ }
+ }
+}