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(); + } + } +}