From accf4246997194128d54f3d4161a10d48dbe8457 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 17 Jan 2022 20:39:23 -0800 Subject: [PATCH] initial commit --- .gitattributes | 1 + .gitignore | 5 + .gitmodules | 3 + MoonWorksComputeSpriteBatch.csproj | 38 +++++ MoonWorksComputeSpriteBatch.sln | 34 +++++ content/sprite.frag.spv | Bin 0 -> 568 bytes content/sprite.vert.spv | Bin 0 -> 1300 bytes content/spritebatch.comp.spv | Bin 0 -> 2132 bytes lib/MoonWorks | 1 + moonlibs/windows/FAudio.dll | 3 + moonlibs/windows/Refresh.dll | 3 + moonlibs/windows/SDL2.dll | 3 + shaders/sprite.frag | 11 ++ shaders/sprite.vert | 17 +++ shaders/spritebatch.comp | 44 ++++++ src/CameraUniforms.cs | 11 ++ src/Program.cs | 21 +++ src/Sprite.cs | 18 +++ src/SpriteBatch.cs | 173 +++++++++++++++++++++ src/TestGame.cs | 237 +++++++++++++++++++++++++++++ src/VertexPositionTexcoord.cs | 15 ++ 21 files changed, 638 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 MoonWorksComputeSpriteBatch.csproj create mode 100644 MoonWorksComputeSpriteBatch.sln create mode 100644 content/sprite.frag.spv create mode 100644 content/sprite.vert.spv create mode 100644 content/spritebatch.comp.spv create mode 160000 lib/MoonWorks create mode 100644 moonlibs/windows/FAudio.dll create mode 100644 moonlibs/windows/Refresh.dll create mode 100644 moonlibs/windows/SDL2.dll create mode 100644 shaders/sprite.frag create mode 100644 shaders/sprite.vert create mode 100644 shaders/spritebatch.comp create mode 100644 src/CameraUniforms.cs create mode 100644 src/Program.cs create mode 100644 src/Sprite.cs create mode 100644 src/SpriteBatch.cs create mode 100644 src/TestGame.cs create mode 100644 src/VertexPositionTexcoord.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..676182a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +moonlibs/**/* filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ca70f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +.vscode/ +.vs/ +Properties/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d51e2a4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/MoonWorks"] + path = lib/MoonWorks + url = https://gitea.moonside.games/MoonsideGames/MoonWorks.git diff --git a/MoonWorksComputeSpriteBatch.csproj b/MoonWorksComputeSpriteBatch.csproj new file mode 100644 index 0000000..715009e --- /dev/null +++ b/MoonWorksComputeSpriteBatch.csproj @@ -0,0 +1,38 @@ + + + + Exe + net5.0 + true + + + + $(DefaultItemExcludes);lib\**\* + + + + + + + + + Always + + + + + + %(RecursiveDir)%(Filename)%(Extension) + PreserveNewest + + + %(RecursiveDir)%(Filename)%(Extension) + PreserveNewest + + + %(RecursiveDir)%(Filename)%(Extension) + PreserveNewest + + + + diff --git a/MoonWorksComputeSpriteBatch.sln b/MoonWorksComputeSpriteBatch.sln new file mode 100644 index 0000000..ea06904 --- /dev/null +++ b/MoonWorksComputeSpriteBatch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoonWorksComputeSpriteBatch", "MoonWorksComputeSpriteBatch.csproj", "{1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Debug|x64.ActiveCfg = Debug|Any CPU + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Debug|x64.Build.0 = Debug|Any CPU + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Debug|x86.ActiveCfg = Debug|Any CPU + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Debug|x86.Build.0 = Debug|Any CPU + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Release|Any CPU.Build.0 = Release|Any CPU + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Release|x64.ActiveCfg = Release|Any CPU + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Release|x64.Build.0 = Release|Any CPU + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Release|x86.ActiveCfg = Release|Any CPU + {1AAA8AA3-3376-4369-A9CB-1F10A51DDE07}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/content/sprite.frag.spv b/content/sprite.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..89181f8fd37585f52a0066fc0bdd2601840d3169 GIT binary patch literal 568 zcmYk2PfG%E5QZmh)wKMRQA7}0cj-_OL|r;~@mA1TP(w%;HibT4pQ=mH^QqH48q7jum4o9z}!PjasxV^iP(Ts9Vl+%i8>=X6Hc)s-E z0Po;!c;YM|D>F6Xho%H{m!E*xGt0&wFX?BRktggvBCeP7S(+{0rr)b|HWg3u*&A+} zP2dcfX8$FwS=)PstFfs2ZRKLHqk3G& ztjIla=w}5Vxbh8n9kalpjXHD$ju?)6?J7e|-JX~*f8=pgD$&B%uT$gXiPqqEvX-KfYGy{{|Wh6#(Gj= zzB>A?2v5pW57$zJ{?LOQ7OU>2e7LI@73SUHIA_||m3=t}JR@T*3v)O0Wv-TXaYu0U zo|8ul9KFHZ8yxvNRZKk^m*kn5v&k>ZkI3NJ59VIsn1$N=GP9qrsc$P{FZ0l^BTr2@ z?~!8i@bm-oE^zE$m#25ggEuPfnZ617pp(U(Ut;)%-sm4K3o`U^KQC2@u0$zjY(eWq$xwU{OT? literal 0 HcmV?d00001 diff --git a/content/spritebatch.comp.spv b/content/spritebatch.comp.spv new file mode 100644 index 0000000000000000000000000000000000000000..fc2529a4d2e023e8c52163467e0d430a4c365f3a GIT binary patch literal 2132 zcmZ9M*-jKu5QYykFd!(1AmD}w?h9^!D6*>qic#ZoYuGfCz_fwEaOsuzzK9RuOKD6@ z{J!o!*zu5}s{X1|+o_&%X{0rTrqCYR!t;=w?oa{=p(9jMUR`;;GP>K?7#$nGrAKEd zr;hq`;nMwR@A zSzFy&u76I}R~xmYzWgY)m(%!f7haI9lH~&byt&iZ++R%g>J4I)Ll4;AT7p~&gUF4Y z)%xy-WM_LhOVonpVi89cy9|F5eRo5eL(Fp!1Kb=}GdR8HkUk~5H47ECuekp!4Xh{0&W1kLG7k5CZGDrBSqw3c{YKnc?qzVOlGpi3r=ca{r0;<|0(q+V zi}@z{_rQn$)6b!Mv|oZ(pgCv>J>cgx@FqxnD%Z^+b`9Nohar#tN8sAT2g&SPWjUL}F=T5JGnTsVCuVKE z@F6J1RzD7TjNJ#<*Lb~1_8Ds!LiXG2Bj+$&-F}>9I5_fjrf zyKkhBC=d!V!jX57eA$*yQvwEu9^lx_0GL;@G={vhe`iM&t?56RZna3V} zf%pwsgMGF^@~Hc}6?JEPv7kE}e{;0Y6UCkBgxo84X_}b6aTg>$Lq_APN1NY}nD^a_ ze#l*NU!u=Ij(^9;T{#JPPy8n_{f+VbCZD!bkeIzG|6xeVVln1fWb=L{rZt{}#D*a2 zv~RKKe+^k|9&dN#2ISsYr#AiEnNdiaF`Sipj4_Fv_1YSI-v^LyZVYX)XWx7Z(*BD2 zeEaDfgB@TqImQYcV6!>qJHO;*+}kP$fIZ2vVx83-E5TnfQ+r;5ysy9ZdFppg%TSz?H99Bj7x#|uI|=;- D;nsuy literal 0 HcmV?d00001 diff --git a/lib/MoonWorks b/lib/MoonWorks new file mode 160000 index 0000000..8022cd1 --- /dev/null +++ b/lib/MoonWorks @@ -0,0 +1 @@ +Subproject commit 8022cd101124163306329913c918e379fd57aebf diff --git a/moonlibs/windows/FAudio.dll b/moonlibs/windows/FAudio.dll new file mode 100644 index 0000000..3214a4f --- /dev/null +++ b/moonlibs/windows/FAudio.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c842f655c68b0fd2c5ee7a8c21b497c135b61a730c20113758fa1f7ce379cf1 +size 307648 diff --git a/moonlibs/windows/Refresh.dll b/moonlibs/windows/Refresh.dll new file mode 100644 index 0000000..2202a38 --- /dev/null +++ b/moonlibs/windows/Refresh.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e283c69d0930dc39ff684524d8fb36e2c96d9e602dea33e6da84731b56ca72e +size 340992 diff --git a/moonlibs/windows/SDL2.dll b/moonlibs/windows/SDL2.dll new file mode 100644 index 0000000..84a14d4 --- /dev/null +++ b/moonlibs/windows/SDL2.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f65b50d693484d5d5a2bb8df1cf520628dd744e99e9a937bb936839b990943a0 +size 1561088 diff --git a/shaders/sprite.frag b/shaders/sprite.frag new file mode 100644 index 0000000..bad4382 --- /dev/null +++ b/shaders/sprite.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(set = 1, binding = 0) uniform sampler2D uniformTexture; + +layout(location = 0) in vec2 fragCoord; +layout(location = 0) out vec4 fragColor; + +void main() +{ + fragColor = texture(uniformTexture, fragCoord); +} diff --git a/shaders/sprite.vert b/shaders/sprite.vert new file mode 100644 index 0000000..ebdfbeb --- /dev/null +++ b/shaders/sprite.vert @@ -0,0 +1,17 @@ +#version 450 + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec2 inTexCoord; + +layout(location = 0) out vec2 fragCoord; + +layout(set = 2, binding = 0) uniform UBO +{ + mat4 viewProjection; +} ubo; + +void main() +{ + gl_Position = ubo.viewProjection * vec4(inPosition, 1.0); + fragCoord = inTexCoord; +} diff --git a/shaders/spritebatch.comp b/shaders/spritebatch.comp new file mode 100644 index 0000000..6a17c04 --- /dev/null +++ b/shaders/spritebatch.comp @@ -0,0 +1,44 @@ +#version 450 + +struct Vertex +{ + vec3 position; + vec2 texcoord; +}; + +// Binding 0: vertices +layout(set = 0, binding = 0) buffer Vertices +{ + Vertex vertices[ ]; +}; + +// Binding 1: transform matrices +layout(set = 0, binding = 1) buffer Transforms +{ + mat4 transforms[ ]; +}; + +layout(set = 2, binding = 0) uniform UBO +{ + uint vertexCount; +} ubo; + +layout (local_size_x = 256) in; + +void main() +{ + // Current buffer index + uint index = gl_GlobalInvocationID.x; + + // Don't write past particle count + if (index >= ubo.vertexCount) + { + return; + } + + uint transformIndex = index / 4; + mat4 transform = transforms[transformIndex]; + vec4 position = vec4(vertices[index].position, 1.0); + + vertices[index].position = vec3(transform * position); +} diff --git a/src/CameraUniforms.cs b/src/CameraUniforms.cs new file mode 100644 index 0000000..162472d --- /dev/null +++ b/src/CameraUniforms.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; +using MoonWorks.Math; + +namespace MoonWorksComputeSpriteBatch +{ + [StructLayout(LayoutKind.Sequential)] + public struct CameraUniforms + { + public Matrix4x4 viewProjectionMatrix; + } +} diff --git a/src/Program.cs b/src/Program.cs new file mode 100644 index 0000000..07e4aa6 --- /dev/null +++ b/src/Program.cs @@ -0,0 +1,21 @@ +using MoonWorks.Window; + +namespace MoonWorksComputeSpriteBatch +{ + class Program + { + static void Main(string[] args) + { + WindowCreateInfo windowCreateInfo = new WindowCreateInfo + { + WindowTitle = "Compute Sprite Batch", + WindowWidth = 1280, + WindowHeight = 720, + ScreenMode = ScreenMode.Windowed + }; + + TestGame game = new TestGame(windowCreateInfo, MoonWorks.Graphics.PresentMode.FIFO, 60, true); + game.Run(); + } + } +} diff --git a/src/Sprite.cs b/src/Sprite.cs new file mode 100644 index 0000000..f0d77e2 --- /dev/null +++ b/src/Sprite.cs @@ -0,0 +1,18 @@ +using MoonWorks.Graphics; + +namespace MoonWorksComputeSpriteBatch +{ + public struct Sprite + { + public Rect Texcoord { get; } + public uint Width { get; } + public uint Height { get; } + + public Sprite(Rect texcoord, uint width, uint height) + { + Texcoord = texcoord; + Width = width; + Height = height; + } + } +} diff --git a/src/SpriteBatch.cs b/src/SpriteBatch.cs new file mode 100644 index 0000000..1d6ba59 --- /dev/null +++ b/src/SpriteBatch.cs @@ -0,0 +1,173 @@ +using System.IO; +using System.Runtime.InteropServices; +using MoonWorks.Graphics; +using MoonWorks.Math; + +namespace MoonWorksComputeSpriteBatch +{ + [StructLayout(LayoutKind.Sequential)] + public struct SpriteBatchUniforms + { + public uint VertexCount { get; set; } + } + + public class SpriteBatch + { + private const int MAX_SPRITES = 16384; + private const int MAX_VERTICES = MAX_SPRITES * 4; + private const int MAX_INDICES = MAX_SPRITES * 6; + private const int MAX_MATRICES = MAX_SPRITES; + + private static ComputePipeline ComputePipeline = null; + + private Buffer VertexBuffer { get; } + private Buffer IndexBuffer { get; } + private Buffer TransformBuffer { get; } + + private readonly VertexPositionTexcoord[] Vertices; + private static readonly short[] Indices = GenerateIndexArray(); + private readonly Matrix4x4[] Transforms; + + private Texture CurrentTexture { get; set; } + private Sampler CurrentSampler { get; set; } + private uint VertexCount { get; set; } + private uint TransformCount { get; set; } + + public SpriteBatch(GraphicsDevice graphicsDevice) + { + if (ComputePipeline == null) + { + var computeShaderModule = new ShaderModule(graphicsDevice, Path.Combine(System.Environment.CurrentDirectory, "Content", "spritebatch.comp.spv")); + + var computeShaderState = new ShaderStageState + { + ShaderModule = computeShaderModule, + EntryPointName = "main", + UniformBufferSize = (uint) Marshal.SizeOf() + }; + + ComputePipeline = new ComputePipeline(graphicsDevice, computeShaderState, 2, 0); + } + + Vertices = new VertexPositionTexcoord[MAX_VERTICES]; + VertexBuffer = new Buffer( + graphicsDevice, + BufferUsageFlags.Vertex | BufferUsageFlags.Compute, + (uint)(MAX_VERTICES * Marshal.SizeOf()) + ); + + IndexBuffer = new Buffer( + graphicsDevice, + BufferUsageFlags.Index | BufferUsageFlags.Compute, + MAX_INDICES * sizeof(short) + ); + + Transforms = new Matrix4x4[MAX_MATRICES]; + TransformBuffer = new Buffer( + graphicsDevice, + BufferUsageFlags.Compute, + (uint)(MAX_MATRICES * Marshal.SizeOf()) + ); + + var commandBuffer = graphicsDevice.AcquireCommandBuffer(); + commandBuffer.SetBufferData(IndexBuffer, Indices); + graphicsDevice.Submit(commandBuffer); + } + + public void Start(Texture texture, Sampler sampler) + { + TransformCount = 0; + VertexCount = 0; + CurrentTexture = texture; + CurrentSampler = sampler; + } + + public void Add(Sprite sprite, Matrix4x4 transform) + { + Vertices[VertexCount].position.X = 0; + Vertices[VertexCount].position.Y = 0; + Vertices[VertexCount].texcoord.X = sprite.Texcoord.X; + Vertices[VertexCount].texcoord.Y = sprite.Texcoord.Y; + + Vertices[VertexCount + 1].position.X = sprite.Width; + Vertices[VertexCount + 1].position.Y = 0; + Vertices[VertexCount + 1].texcoord.X = sprite.Texcoord.X + sprite.Texcoord.W; + Vertices[VertexCount + 1].texcoord.Y = sprite.Texcoord.Y; + + Vertices[VertexCount + 2].position.X = 0; + Vertices[VertexCount + 2].position.Y = sprite.Height; + Vertices[VertexCount + 2].texcoord.X = sprite.Texcoord.X; + Vertices[VertexCount + 2].texcoord.Y = sprite.Texcoord.Y + sprite.Texcoord.H; + + Vertices[VertexCount + 3].position.X = sprite.Width; + Vertices[VertexCount + 3].position.Y = sprite.Height; + Vertices[VertexCount + 3].texcoord.X = sprite.Texcoord.X + sprite.Texcoord.W; + Vertices[VertexCount + 3].texcoord.Y = sprite.Texcoord.Y + sprite.Texcoord.H; + + VertexCount += 4; + + Transforms[TransformCount] = transform; + TransformCount += 1; + } + + public void Flush( + CommandBuffer commandBuffer, + RenderPass renderPass, + Framebuffer framebuffer, + Rect renderArea, + GraphicsPipeline graphicsPipeline, + CameraUniforms cameraUniforms + ) { + if (VertexCount == 0) + { + return; + } + + commandBuffer.SetBufferData(VertexBuffer, Vertices, 0, 0, VertexCount); + commandBuffer.SetBufferData(TransformBuffer, Transforms, 0, 0, TransformCount); + + commandBuffer.BindComputePipeline(ComputePipeline); + commandBuffer.BindComputeBuffers(VertexBuffer, TransformBuffer); + var offset = commandBuffer.PushComputeShaderUniforms(new SpriteBatchUniforms + { + VertexCount = VertexCount + }); + commandBuffer.DispatchCompute(VertexCount / 256, 1, 1, offset); + + commandBuffer.BeginRenderPass(renderPass, framebuffer, renderArea, Vector4.Zero); + commandBuffer.BindGraphicsPipeline(graphicsPipeline); + commandBuffer.BindVertexBuffers(VertexBuffer); + commandBuffer.BindIndexBuffer(IndexBuffer, IndexElementSize.Sixteen); + commandBuffer.BindFragmentSamplers(new TextureSamplerBinding { Texture = CurrentTexture, Sampler = CurrentSampler }); + offset = commandBuffer.PushVertexShaderUniforms(cameraUniforms); + + commandBuffer.DrawIndexedPrimitives( + 0, + 0, + VertexCount / 2, + offset, + 0 + ); + + commandBuffer.EndRenderPass(); + + VertexCount = 0; + TransformCount = 0; + } + + private static short[] GenerateIndexArray() + { + var result = new short[MAX_INDICES]; + for (int i = 0, j = 0; i < MAX_INDICES; i += 6, j += 4) + { + result[i] = (short)j; + result[i + 1] = (short)(j + 1); + result[i + 2] = (short)(j + 2); + result[i + 3] = (short)(j + 2); + result[i + 4] = (short)(j + 1); + result[i + 5] = (short)(j + 3); + } + return result; + } + } +} diff --git a/src/TestGame.cs b/src/TestGame.cs new file mode 100644 index 0000000..f73a041 --- /dev/null +++ b/src/TestGame.cs @@ -0,0 +1,237 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using MoonWorks; +using MoonWorks.Graphics; +using MoonWorks.Math; +using MoonWorks.Window; + +namespace MoonWorksComputeSpriteBatch +{ + public class TestGame : Game + { + private RenderPass mainRenderPass; + private RenderTarget mainColorTarget; + private Framebuffer mainFramebuffer; + private Rect mainRenderArea; + + private GraphicsPipeline spritePipeline; + + private SpriteBatch spriteBatch; + + private Texture whitePixel; + private Sampler sampler; + + private const int SPRITECOUNT = 8092; + private Vector3[] positions = new Vector3[SPRITECOUNT]; + + private uint windowWidth; + private uint windowHeight; + + private Random random = new Random(); + + public TestGame(WindowCreateInfo windowCreateInfo, PresentMode presentMode, int targetTimestep = 60, bool debugMode = false) : base(windowCreateInfo, presentMode, targetTimestep, debugMode) + { + windowWidth = windowCreateInfo.WindowWidth; + windowHeight = windowCreateInfo.WindowHeight; + + var vertexShaderModule = new ShaderModule(GraphicsDevice, Path.Combine(Environment.CurrentDirectory, "Content", "sprite.vert.spv")); + var fragmentShaderModule = new ShaderModule(GraphicsDevice, Path.Combine(Environment.CurrentDirectory, "Content", "sprite.frag.spv")); + + ColorTargetDescription colorTargetDescription = new ColorTargetDescription + { + Format = TextureFormat.R8G8B8A8, + MultisampleCount = SampleCount.One, + LoadOp = LoadOp.Clear, + StoreOp = StoreOp.Store + }; + + mainRenderPass = new RenderPass(GraphicsDevice, colorTargetDescription); + + mainColorTarget = RenderTarget.CreateBackedRenderTarget( + GraphicsDevice, + windowWidth, + windowHeight, + TextureFormat.R8G8B8A8, + false + ); + + mainFramebuffer = new Framebuffer( + GraphicsDevice, + windowWidth, + windowHeight, + mainRenderPass, + null, + mainColorTarget + ); + + mainRenderArea = new Rect + { + X = 0, + Y = 0, + W = (int) windowWidth, + H = (int) windowHeight + }; + + /* Pipeline */ + + ColorTargetBlendState[] colorTargetBlendStates = new ColorTargetBlendState[1] + { + ColorTargetBlendState.None + }; + + ColorBlendState colorBlendState = new ColorBlendState + { + LogicOpEnable = false, + LogicOp = LogicOp.NoOp, + BlendConstants = new BlendConstants(), + ColorTargetBlendStates = colorTargetBlendStates + }; + + DepthStencilState depthStencilState = DepthStencilState.Disable; + + ShaderStageState vertexShaderState = new ShaderStageState + { + ShaderModule = vertexShaderModule, + EntryPointName = "main", + UniformBufferSize = (uint) Marshal.SizeOf() + }; + + ShaderStageState fragmentShaderState = new ShaderStageState + { + ShaderModule = fragmentShaderModule, + EntryPointName = "main", + UniformBufferSize = 0 + }; + + MultisampleState multisampleState = MultisampleState.None; + + GraphicsPipelineLayoutInfo pipelineLayoutInfo = new GraphicsPipelineLayoutInfo + { + VertexSamplerBindingCount = 0, + FragmentSamplerBindingCount = 1 + }; + + RasterizerState rasterizerState = RasterizerState.CCW_CullNone; + + var vertexBindings = new VertexBinding[1] + { + new VertexBinding + { + Binding = 0, + InputRate = VertexInputRate.Vertex, + Stride = (uint) Marshal.SizeOf() + } + }; + + var vertexAttributes = new VertexAttribute[2] + { + new VertexAttribute + { + Binding = 0, + Location = 0, + Format = VertexElementFormat.Vector3, + Offset = 0 + }, + new VertexAttribute + { + Binding = 0, + Location = 1, + Format = VertexElementFormat.Vector2, + Offset = (uint) Marshal.OffsetOf("texcoord") + } + }; + + VertexInputState vertexInputState = new VertexInputState + { + VertexBindings = vertexBindings, + VertexAttributes = vertexAttributes + }; + + var viewports = new Viewport[1] + { + new Viewport + { + X = 0, + Y = 0, + W = windowWidth, + H = windowHeight, + MinDepth = 0, + MaxDepth = 1 + } + }; + + var scissors = new Rect[1] + { + new Rect + { + X = 0, + Y = 0, + W = (int) windowWidth, + H = (int) windowHeight + } + }; + + ViewportState viewportState = new ViewportState + { + Viewports = viewports, + Scissors = scissors + }; + + var graphicsPipelineCreateInfo = new GraphicsPipelineCreateInfo + { + ColorBlendState = colorBlendState, + DepthStencilState = depthStencilState, + VertexShaderState = vertexShaderState, + FragmentShaderState = fragmentShaderState, + MultisampleState = multisampleState, + PipelineLayoutInfo = pipelineLayoutInfo, + RasterizerState = rasterizerState, + PrimitiveType = PrimitiveType.TriangleList, + VertexInputState = vertexInputState, + ViewportState = viewportState, + RenderPass = mainRenderPass + }; + + spritePipeline = new GraphicsPipeline(GraphicsDevice, graphicsPipelineCreateInfo); + + spriteBatch = new SpriteBatch(GraphicsDevice); + + whitePixel = Texture.CreateTexture2D(GraphicsDevice, 1, 1, TextureFormat.R8G8B8A8, TextureUsageFlags.Sampler); + + var commandBuffer = GraphicsDevice.AcquireCommandBuffer(); + commandBuffer.SetTextureData(whitePixel, new Color[] { Color.White }); + GraphicsDevice.Submit(commandBuffer); + + sampler = new Sampler(GraphicsDevice, SamplerCreateInfo.PointWrap); + } + + protected override void Update(TimeSpan dt) + { + for (var i = 0; i < SPRITECOUNT; i += 1) + { + positions[i].X = (float) (random.NextDouble() * windowWidth) - 64; + positions[i].Y = (float) (random.NextDouble() * windowHeight) - 64; + } + } + + protected override void Draw(TimeSpan dt, double alpha) + { + var commandBuffer = GraphicsDevice.AcquireCommandBuffer(); + + var viewProjection = Matrix4x4.CreateLookAt(new Vector3(windowWidth / 2, windowHeight / 2, 1), new Vector3(windowWidth / 2, windowHeight / 2, 0), Vector3.Up) * Matrix4x4.CreateOrthographic(windowWidth, windowHeight, 0.1f, 1000); + spriteBatch.Start(whitePixel, sampler); + + for (var i = 0; i < SPRITECOUNT; i += 1) + { + var transform = Matrix4x4.CreateTranslation(positions[i]); + spriteBatch.Add(new Sprite(new Rect { X = 0, Y = 0, W = 0, H = 0 }, 128, 128), transform); + } + + spriteBatch.Flush(commandBuffer, mainRenderPass, mainFramebuffer, mainRenderArea, spritePipeline, new CameraUniforms { viewProjectionMatrix = viewProjection }); + + commandBuffer.QueuePresent(mainColorTarget.TextureSlice, Filter.Nearest); + GraphicsDevice.Submit(commandBuffer); + } + } +} diff --git a/src/VertexPositionTexcoord.cs b/src/VertexPositionTexcoord.cs new file mode 100644 index 0000000..9e95c45 --- /dev/null +++ b/src/VertexPositionTexcoord.cs @@ -0,0 +1,15 @@ +using MoonWorks.Math; +using System.Runtime.InteropServices; + +namespace MoonWorksComputeSpriteBatch +{ + // SPIR-V requires vectors to not cross 16-byte boundaries + [StructLayout(LayoutKind.Explicit, Size=32)] + public struct VertexPositionTexcoord + { + [FieldOffset(0)] + public Vector3 position; + [FieldOffset(16)] + public Vector2 texcoord; + } +}