diff --git a/Common/Content/Shaders/Compiled/CalculateSquares.comp.spv b/Common/Content/Shaders/Compiled/CalculateSquares.comp.spv index ede2b14..dce7736 100644 Binary files a/Common/Content/Shaders/Compiled/CalculateSquares.comp.spv and b/Common/Content/Shaders/Compiled/CalculateSquares.comp.spv differ diff --git a/Common/Content/Shaders/Compiled/SpriteBatch.comp.spv b/Common/Content/Shaders/Compiled/SpriteBatch.comp.spv new file mode 100644 index 0000000..1b4ee94 Binary files /dev/null and b/Common/Content/Shaders/Compiled/SpriteBatch.comp.spv differ diff --git a/Common/Content/Shaders/Compiled/TexturedQuadColor.frag.spv b/Common/Content/Shaders/Compiled/TexturedQuadColor.frag.spv new file mode 100644 index 0000000..51c143f Binary files /dev/null and b/Common/Content/Shaders/Compiled/TexturedQuadColor.frag.spv differ diff --git a/Common/Content/Shaders/Compiled/TexturedQuadColorWithMatrix.vert.spv b/Common/Content/Shaders/Compiled/TexturedQuadColorWithMatrix.vert.spv new file mode 100644 index 0000000..5aed5ae Binary files /dev/null and b/Common/Content/Shaders/Compiled/TexturedQuadColorWithMatrix.vert.spv differ diff --git a/Common/Content/Shaders/Source/CalculateSquares.comp b/Common/Content/Shaders/Source/CalculateSquares.comp index b8e2180..bc789cc 100644 --- a/Common/Content/Shaders/Source/CalculateSquares.comp +++ b/Common/Content/Shaders/Source/CalculateSquares.comp @@ -1,7 +1,7 @@ #version 450 layout (local_size_x = 8) in; -layout (set = 1, binding = 0) buffer outBuffer +layout (set = 1, binding = 0) writeonly buffer outBuffer { uint squares[]; }; diff --git a/Common/Content/Shaders/Source/SpriteBatch.comp b/Common/Content/Shaders/Source/SpriteBatch.comp new file mode 100644 index 0000000..cf7ee6f --- /dev/null +++ b/Common/Content/Shaders/Source/SpriteBatch.comp @@ -0,0 +1,79 @@ +#version 450 + +struct SpriteComputeData +{ + vec3 position; + float rotation; + vec2 scale; + vec4 color; +}; + +struct SpriteVertex +{ + vec4 position; + vec2 texcoord; + vec4 color; +}; + +layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in; +layout (std430, set = 0, binding = 0) readonly buffer inBuffer +{ + SpriteComputeData computeData[]; +}; +layout (std430, set = 1, binding = 0) writeonly buffer outBuffer +{ + SpriteVertex vertexData[]; +}; + +void main() +{ + uint n = gl_GlobalInvocationID.x; + + SpriteComputeData currentSpriteData = computeData[n]; + + mat4 Scale = mat4( + currentSpriteData.scale.x, 0, 0, 0, + 0, currentSpriteData.scale.y, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); + + float c = cos(currentSpriteData.rotation); + float s = sin(currentSpriteData.rotation); + + mat4 Rotation = mat4( + c, s, 0, 0, + -s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); + + mat4 Translation = mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + currentSpriteData.position.x, currentSpriteData.position.y, currentSpriteData.position.z, 1 + ); + + mat4 Model = Translation * Rotation * Scale; + + vec4 topLeft = vec4(0, 0, 0, 1); + vec4 topRight = vec4(1, 0, 0, 1); + vec4 bottomLeft = vec4(0, 1, 0, 1); + vec4 bottomRight = vec4(1, 1, 0, 1); + + vertexData[n*4] .position = Model * topLeft; + vertexData[n*4+1].position = Model * topRight; + vertexData[n*4+2].position = Model * bottomLeft; + vertexData[n*4+3].position = Model * bottomRight; + + vertexData[n*4] .texcoord = vec2(0, 0); + vertexData[n*4+1].texcoord = vec2(1, 0); + vertexData[n*4+2].texcoord = vec2(0, 1); + vertexData[n*4+3].texcoord = vec2(1, 1); + + vertexData[n*4] .color = currentSpriteData.color; + vertexData[n*4+1].color = currentSpriteData.color; + vertexData[n*4+2].color = currentSpriteData.color; + vertexData[n*4+3].color = currentSpriteData.color; +} diff --git a/Common/Content/Shaders/Source/TexturedQuadColor.frag b/Common/Content/Shaders/Source/TexturedQuadColor.frag new file mode 100644 index 0000000..30f58c7 --- /dev/null +++ b/Common/Content/Shaders/Source/TexturedQuadColor.frag @@ -0,0 +1,13 @@ +#version 450 + +layout (location = 0) in vec2 TexCoord; +layout (location = 1) in vec4 Color; + +layout (location = 0) out vec4 FragColor; + +layout(set = 2, binding = 0) uniform sampler2D Sampler; + +void main() +{ + FragColor = Color * texture(Sampler, TexCoord); +} diff --git a/Common/Content/Shaders/Source/TexturedQuadColorWithMatrix.vert b/Common/Content/Shaders/Source/TexturedQuadColorWithMatrix.vert new file mode 100644 index 0000000..77f494b --- /dev/null +++ b/Common/Content/Shaders/Source/TexturedQuadColorWithMatrix.vert @@ -0,0 +1,20 @@ +#version 450 + +layout (location = 0) in vec4 Position; +layout (location = 1) in vec2 TexCoord; +layout (location = 2) in vec4 Color; + +layout (location = 0) out vec2 outTexCoord; +layout (location = 1) out vec4 outColor; + +layout (set = 1, binding = 0) uniform UniformBlock +{ + mat4x4 MatrixTransform; +}; + +void main() +{ + outTexCoord = TexCoord; + outColor = Color; + gl_Position = MatrixTransform * Position; +} diff --git a/Common/VertexTypes.cs b/Common/VertexTypes.cs index cbd8eb7..620c050 100644 --- a/Common/VertexTypes.cs +++ b/Common/VertexTypes.cs @@ -14,10 +14,12 @@ public struct PositionVertex : IVertexType Position = position; } - public static VertexElementFormat[] Formats { get; } = new VertexElementFormat[1] - { + public static VertexElementFormat[] Formats { get; } = + [ VertexElementFormat.Vector3 - }; + ]; + + public static uint[] Offsets { get; } = [ 0 ]; public override string ToString() { @@ -37,11 +39,17 @@ public struct PositionColorVertex : IVertexType Color = color; } - public static VertexElementFormat[] Formats { get; } = new VertexElementFormat[2] - { + public static VertexElementFormat[] Formats { get; } = + [ VertexElementFormat.Vector3, VertexElementFormat.Color - }; + ]; + + public static uint[] Offsets { get; } = + [ + 0, + 12 + ]; public override string ToString() { @@ -67,8 +75,41 @@ public struct PositionTextureVertex : IVertexType VertexElementFormat.Vector2 }; + public static uint[] Offsets { get; } = + [ + 0, + 12 + ]; + public override string ToString() { return Position + " | " + TexCoord; } } + +[StructLayout(LayoutKind.Explicit, Size = 48)] +struct PositionTextureColorVertex : IVertexType +{ + [FieldOffset(0)] + public Vector4 Position; + + [FieldOffset(16)] + public Vector2 TexCoord; + + [FieldOffset(32)] + public Vector4 Color; + + public static VertexElementFormat[] Formats { get; } = + [ + VertexElementFormat.Vector4, + VertexElementFormat.Vector2, + VertexElementFormat.Vector4 + ]; + + public static uint[] Offsets { get; } = + [ + 0, + 16, + 32 + ]; +} \ No newline at end of file diff --git a/Examples/ComputeSpriteBatchExample.cs b/Examples/ComputeSpriteBatchExample.cs new file mode 100644 index 0000000..02b2998 --- /dev/null +++ b/Examples/ComputeSpriteBatchExample.cs @@ -0,0 +1,244 @@ +using System; +using System.Runtime.InteropServices; +using MoonWorks; +using MoonWorks.Graphics; +using MoonWorks.Input; +using MoonWorks.Math.Float; +using Buffer = MoonWorks.Graphics.Buffer; + +namespace MoonWorksGraphicsTests; + +class ComputeSpriteBatchExample : Example +{ + ComputePipeline ComputePipeline; + GraphicsPipeline RenderPipeline; + Sampler Sampler; + Texture SpriteTexture; + TransferBuffer SpriteComputeTransferBuffer; + Buffer SpriteComputeBuffer; + Buffer SpriteVertexBuffer; + Buffer SpriteIndexBuffer; + + const int MAX_SPRITE_COUNT = 8192; + + Random Random = new Random(); + + [StructLayout(LayoutKind.Explicit, Size = 48)] + struct ComputeSpriteData + { + [FieldOffset(0)] + public Vector3 Position; + + [FieldOffset(12)] + public float Rotation; + + [FieldOffset(16)] + public Vector2 Size; + + [FieldOffset(32)] + public Vector4 Color; + } + + public override unsafe void Init(Window window, GraphicsDevice graphicsDevice, Inputs inputs) + { + Window = window; + GraphicsDevice = graphicsDevice; + + Window.SetTitle("ComputeSpriteBatch"); + + Shader vertShader = new Shader( + GraphicsDevice, + TestUtils.GetShaderPath("TexturedQuadColorWithMatrix.vert"), + "main", + new ShaderCreateInfo + { + ShaderStage = ShaderStage.Vertex, + ShaderFormat = ShaderFormat.SPIRV, + UniformBufferCount = 1 + } + ); + + Shader fragShader = new Shader( + GraphicsDevice, + TestUtils.GetShaderPath("TexturedQuadColor.frag"), + "main", + new ShaderCreateInfo + { + ShaderStage = ShaderStage.Fragment, + ShaderFormat = ShaderFormat.SPIRV, + SamplerCount = 1 + } + ); + + GraphicsPipelineCreateInfo renderPipelineCreateInfo = TestUtils.GetStandardGraphicsPipelineCreateInfo( + Window.SwapchainFormat, + vertShader, + fragShader + ); + renderPipelineCreateInfo.VertexInputState = VertexInputState.CreateSingleBinding(); + + RenderPipeline = new GraphicsPipeline(GraphicsDevice, renderPipelineCreateInfo); + + ComputePipeline = new ComputePipeline( + GraphicsDevice, + TestUtils.GetShaderPath("SpriteBatch.comp"), + "main", + new ComputePipelineCreateInfo + { + ShaderFormat = ShaderFormat.SPIRV, + ReadOnlyStorageBufferCount = 1, + ReadWriteStorageBufferCount = 1, + ThreadCountX = 64, + ThreadCountY = 1, + ThreadCountZ = 1 + } + ); + + Sampler = new Sampler(GraphicsDevice, SamplerCreateInfo.PointClamp); + + // Create and populate the sprite texture + var resourceUploader = new ResourceUploader(GraphicsDevice); + + SpriteTexture = resourceUploader.CreateTexture2DFromCompressed(TestUtils.GetTexturePath("ravioli.png")); + + resourceUploader.Upload(); + resourceUploader.Dispose(); + + SpriteComputeTransferBuffer = TransferBuffer.Create( + GraphicsDevice, + TransferUsage.Buffer, + TransferBufferMapFlags.Write, + MAX_SPRITE_COUNT + ); + + SpriteComputeBuffer = Buffer.Create( + GraphicsDevice, + BufferUsageFlags.ComputeStorageRead, + MAX_SPRITE_COUNT + ); + + SpriteVertexBuffer = Buffer.Create( + GraphicsDevice, + BufferUsageFlags.ComputeStorageWrite | BufferUsageFlags.Vertex, + MAX_SPRITE_COUNT * 4 + ); + + SpriteIndexBuffer = Buffer.Create( + GraphicsDevice, + BufferUsageFlags.Index, + MAX_SPRITE_COUNT * 6 + ); + + TransferBuffer spriteIndexTransferBuffer = TransferBuffer.Create( + GraphicsDevice, + TransferUsage.Buffer, + TransferBufferMapFlags.Write, + MAX_SPRITE_COUNT * 6 + ); + + spriteIndexTransferBuffer.Map(false, out byte* mapPointer); + uint *indexPointer = (uint*) mapPointer; + + for (uint i = 0, j = 0; i < MAX_SPRITE_COUNT * 6; i += 6, j += 4) + { + indexPointer[i] = j; + indexPointer[i + 1] = j + 1; + indexPointer[i + 2] = j + 2; + indexPointer[i + 3] = j + 3; + indexPointer[i + 4] = j + 2; + indexPointer[i + 5] = j + 1; + } + spriteIndexTransferBuffer.Unmap(); + + var cmdbuf = GraphicsDevice.AcquireCommandBuffer(); + var copyPass = cmdbuf.BeginCopyPass(); + copyPass.UploadToBuffer(spriteIndexTransferBuffer, SpriteIndexBuffer, false); + cmdbuf.EndCopyPass(copyPass); + GraphicsDevice.Submit(cmdbuf); + } + + public override void Update(TimeSpan delta) + { + + } + + public override unsafe void Draw(double alpha) + { + Matrix4x4 cameraMatrix = + Matrix4x4.CreateOrthographicOffCenter( + 0, + 640, + 480, + 0, + 0, + -1f + ); + + CommandBuffer cmdbuf = GraphicsDevice.AcquireCommandBuffer(); + Texture swapchainTexture = cmdbuf.AcquireSwapchainTexture(Window); + if (swapchainTexture != null) + { + // Build sprite compute transfer + SpriteComputeTransferBuffer.Map(true, out byte* mapPointer); + ComputeSpriteData *dataPointer = (ComputeSpriteData*) mapPointer; + + for (var i = 0; i < MAX_SPRITE_COUNT; i += 1) + { + dataPointer[i] = new ComputeSpriteData + { + Position = new Vector3(Random.Next(640), Random.Next(480), 0), + Rotation = (float) (Random.NextDouble() * System.Math.PI * 2), + Size = new Vector2(32, 32), + Color = new Vector4(1f, 1f, 1f, 1f) + }; + } + SpriteComputeTransferBuffer.Unmap(); + + // Upload compute data to buffer + var copyPass = cmdbuf.BeginCopyPass(); + copyPass.UploadToBuffer(SpriteComputeTransferBuffer, SpriteComputeBuffer, true); + cmdbuf.EndCopyPass(copyPass); + + // Set up compute pass to build sprite vertex buffer + var computePass = cmdbuf.BeginComputePass(new StorageBufferReadWriteBinding + { + Buffer = SpriteVertexBuffer, + Cycle = true + }); + + computePass.BindComputePipeline(ComputePipeline); + computePass.BindStorageBuffer(SpriteComputeBuffer); + computePass.Dispatch(MAX_SPRITE_COUNT / 64, 1, 1); + + cmdbuf.EndComputePass(computePass); + + // Render sprites using vertex buffer + var renderPass = cmdbuf.BeginRenderPass( + new ColorAttachmentInfo(swapchainTexture, false, Color.Black) + ); + + renderPass.BindGraphicsPipeline(RenderPipeline); + renderPass.BindVertexBuffer(SpriteVertexBuffer); + renderPass.BindIndexBuffer(SpriteIndexBuffer, IndexElementSize.ThirtyTwo); + renderPass.BindFragmentSampler(new TextureSamplerBinding(SpriteTexture, Sampler)); + renderPass.PushVertexUniformData(cameraMatrix); + renderPass.DrawIndexedPrimitives(0, 0, MAX_SPRITE_COUNT * 2); + + cmdbuf.EndRenderPass(renderPass); + } + + GraphicsDevice.Submit(cmdbuf); + } + + public override void Destroy() + { + ComputePipeline.Dispose(); + RenderPipeline.Dispose(); + Sampler.Dispose(); + SpriteTexture.Dispose(); + SpriteComputeTransferBuffer.Dispose(); + SpriteComputeBuffer.Dispose(); + SpriteVertexBuffer.Dispose(); + SpriteIndexBuffer.Dispose(); + } +} diff --git a/MoonWorksGraphicsTests.csproj b/MoonWorksGraphicsTests.csproj index b0a6d00..a2c7b04 100644 --- a/MoonWorksGraphicsTests.csproj +++ b/MoonWorksGraphicsTests.csproj @@ -17,45 +17,6 @@ - - - $(DefaultItemExcludes);Examples\**\* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Program.cs b/Program.cs index 347f978..55bb91c 100644 --- a/Program.cs +++ b/Program.cs @@ -37,7 +37,8 @@ class Program : Game new TriangleVertexBufferExample(), new VertexSamplerExample(), new VideoPlayerExample(), - new WindowResizingExample() + new WindowResizingExample(), + new ComputeSpriteBatchExample() ]; int ExampleIndex = 0;