diff --git a/MoonWorks.Test.Common/Content/Shaders/Compiled/InstancedSpriteBatch.vert.refresh b/MoonWorks.Test.Common/Content/Shaders/Compiled/InstancedSpriteBatch.vert.refresh
new file mode 100644
index 0000000..6e5a784
Binary files /dev/null and b/MoonWorks.Test.Common/Content/Shaders/Compiled/InstancedSpriteBatch.vert.refresh differ
diff --git a/MoonWorks.Test.Common/Content/Shaders/Source/InstancedSpriteBatch.vert b/MoonWorks.Test.Common/Content/Shaders/Source/InstancedSpriteBatch.vert
new file mode 100644
index 0000000..43031a4
--- /dev/null
+++ b/MoonWorks.Test.Common/Content/Shaders/Source/InstancedSpriteBatch.vert
@@ -0,0 +1,45 @@
+#version 450
+
+layout (location = 0) in vec3 Position;
+layout (location = 1) in vec3 Translation;
+layout (location = 2) in float Rotation;
+layout (location = 3) in vec2 Scale;
+layout (location = 4) in vec4 Color;
+layout (location = 5) in vec2[4] UV;
+
+layout (location = 0) out vec2 outTexCoord;
+layout (location = 1) out vec4 outVertexColor;
+
+layout (binding = 0, set = 2) uniform UniformBlock
+{
+ mat4x4 View;
+ mat4x4 Projection;
+};
+
+void main()
+{
+ mat4 Scale = mat4(
+ Scale.x, 0, 0, 0,
+ 0, Scale.y, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+ );
+ float c = cos(Rotation);
+ float s = sin(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, Position.x,
+ 0, 1, 0, Position.y,
+ 0, 0, 1, Position.z,
+ 0, 0, 0, 1
+ );
+ mat4 Model = Scale * Rotation * Translation;
+ gl_Position = Model * View * Projection * vec4(Position, 1);
+ outTexCoord = UV[gl_VertexIndex % 4];
+ outVertexColor = Color;
+}
diff --git a/MoonWorksGraphicsTests.sln b/MoonWorksGraphicsTests.sln
index 2773e38..d34d756 100644
--- a/MoonWorksGraphicsTests.sln
+++ b/MoonWorksGraphicsTests.sln
@@ -61,6 +61,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowResizing", "WindowRes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoreLoad", "StoreLoad\StoreLoad.csproj", "{CD31F1B5-C76A-428A-A812-8DFD6CAB20A9}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpriteBatch", "SpriteBatch\SpriteBatch.csproj", "{40E25B99-1196-4695-99A6-C0A8EF385539}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -183,6 +185,10 @@ Global
{CD31F1B5-C76A-428A-A812-8DFD6CAB20A9}.Debug|x64.Build.0 = Debug|x64
{CD31F1B5-C76A-428A-A812-8DFD6CAB20A9}.Release|x64.ActiveCfg = Release|x64
{CD31F1B5-C76A-428A-A812-8DFD6CAB20A9}.Release|x64.Build.0 = Release|x64
+ {40E25B99-1196-4695-99A6-C0A8EF385539}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {40E25B99-1196-4695-99A6-C0A8EF385539}.Debug|x64.Build.0 = Debug|Any CPU
+ {40E25B99-1196-4695-99A6-C0A8EF385539}.Release|x64.ActiveCfg = Release|Any CPU
+ {40E25B99-1196-4695-99A6-C0A8EF385539}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/SpriteBatch/SpriteBatch.csproj b/SpriteBatch/SpriteBatch.csproj
new file mode 100644
index 0000000..af5328d
--- /dev/null
+++ b/SpriteBatch/SpriteBatch.csproj
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+ Exe
+ net8.0
+ enable
+ true
+
+
+
+
+
diff --git a/SpriteBatch/SpriteBatchGame.cs b/SpriteBatch/SpriteBatchGame.cs
new file mode 100644
index 0000000..e445f25
--- /dev/null
+++ b/SpriteBatch/SpriteBatchGame.cs
@@ -0,0 +1,152 @@
+using System;
+using MoonWorks.Graphics;
+using MoonWorks.Math.Float;
+
+namespace MoonWorks.Test
+{
+ class SpriteBatchGame : Game
+ {
+ GraphicsPipeline spriteBatchPipeline;
+ Graphics.Buffer quadVertexBuffer;
+ Graphics.Buffer quadIndexBuffer;
+ SpriteBatch SpriteBatch;
+
+ public unsafe SpriteBatchGame() : base(TestUtils.GetStandardWindowCreateInfo(), TestUtils.GetStandardFrameLimiterSettings(), 60, true)
+ {
+ ShaderModule vertShaderModule = new ShaderModule(GraphicsDevice, TestUtils.GetShaderPath("InstancedSpriteBatch.vert"));
+ ShaderModule fragShaderModule = new ShaderModule(GraphicsDevice, TestUtils.GetShaderPath("InstancedSpriteBatch.frag"));
+
+ var vertexBufferDescription = VertexBindingAndAttributes.Create(0);
+ var instanceBufferDescription = VertexBindingAndAttributes.Create(1, VertexInputRate.Instance);
+
+ GraphicsPipelineCreateInfo pipelineCreateInfo = TestUtils.GetStandardGraphicsPipelineCreateInfo(
+ MainWindow.SwapchainFormat,
+ vertShaderModule,
+ fragShaderModule
+ );
+
+ pipelineCreateInfo.VertexInputState = new VertexInputState([
+ vertexBufferDescription,
+ instanceBufferDescription
+ ]);
+
+ spriteBatchPipeline = new GraphicsPipeline(GraphicsDevice, pipelineCreateInfo);
+
+ quadVertexBuffer = Graphics.Buffer.Create(GraphicsDevice, BufferUsageFlags.Vertex, 4);
+ quadIndexBuffer = Graphics.Buffer.Create(GraphicsDevice, BufferUsageFlags.Index, 6);
+
+ var vertices = stackalloc PositionVertex[4];
+ vertices[0].Position = new Math.Float.Vector3(0, 0, 0);
+ vertices[1].Position = new Math.Float.Vector3(1, 0, 0);
+ vertices[2].Position = new Math.Float.Vector3(0, 1, 0);
+ vertices[3].Position = new Math.Float.Vector3(1, 1, 0);
+
+ var indices = stackalloc ushort[6]
+ {
+ 0, 1, 2,
+ 2, 1, 3
+ };
+
+ var cmdbuf = GraphicsDevice.AcquireCommandBuffer();
+ cmdbuf.SetBufferData(quadVertexBuffer, new Span(vertices, 4));
+ cmdbuf.SetBufferData(quadIndexBuffer, new Span(indices, 6));
+ GraphicsDevice.Submit(cmdbuf);
+
+ SpriteBatch = new SpriteBatch(GraphicsDevice);
+ }
+
+ protected override void Update(TimeSpan delta)
+ {
+
+ }
+
+ protected override void Draw(double alpha)
+ {
+ CommandBuffer cmdbuf = GraphicsDevice.AcquireCommandBuffer();
+ Texture? swapchain = cmdbuf.AcquireSwapchainTexture(MainWindow);
+ if (swapchain != null)
+ {
+ for (var i = 0; i < 1024; i += 1)
+ {
+ SpriteBatch.Add()
+ }
+
+ SpriteBatch.Upload(cmdbuf);
+
+ cmdbuf.BeginRenderPass(new ColorAttachmentInfo(swapchain, Color.Black));
+ cmdbuf.BindGraphicsPipeline(spriteBatchPipeline);
+ cmdbuf.BindVertexBuffers(
+ new BufferBinding(quadVertexBuffer, 0),
+ new BufferBinding(SpriteBatch.)
+ )
+ cmdbuf.EndRenderPass();
+ }
+ GraphicsDevice.Submit(cmdbuf);
+ }
+
+ public static void Main(string[] args)
+ {
+ SpriteBatchGame game = new SpriteBatchGame();
+ game.Run();
+ }
+ }
+
+ public struct SpriteInstanceData : IVertexType
+ {
+ public Vector3 Translation;
+ public float Rotation;
+ public Vector2 Scale;
+ public Color Color;
+ public Vector2 UV0;
+ public Vector2 UV1;
+ public Vector2 UV2;
+ public Vector2 UV3;
+
+ public static VertexElementFormat[] Formats =>
+ [
+ VertexElementFormat.Vector3,
+ VertexElementFormat.Float,
+ VertexElementFormat.Vector2,
+ VertexElementFormat.Color,
+ VertexElementFormat.Vector2,
+ VertexElementFormat.Vector2,
+ VertexElementFormat.Vector2,
+ VertexElementFormat.Vector2
+ ];
+ }
+
+ class SpriteBatch
+ {
+ GraphicsDevice GraphicsDevice;
+ public Graphics.Buffer BatchBuffer;
+ SpriteInstanceData[] InstanceDatas;
+ int Index;
+
+ public SpriteBatch(GraphicsDevice graphicsDevice)
+ {
+ GraphicsDevice = graphicsDevice;
+ BatchBuffer = Graphics.Buffer.Create(GraphicsDevice, BufferUsageFlags.Vertex, 1024);
+ InstanceDatas = new SpriteInstanceData[1024];
+ Index = 0;
+ }
+
+ public void Add(Vector3 position, float rotation, Vector2 size, Color color)
+ {
+ InstanceDatas[Index].Translation = position;
+ InstanceDatas[Index].Rotation = rotation;
+ InstanceDatas[Index].Scale = size;
+ InstanceDatas[Index].Color = color;
+ InstanceDatas[Index].UV0 = new Vector2(0, 0);
+ InstanceDatas[Index].UV1 = new Vector2(0, 1);
+ InstanceDatas[Index].UV2 = new Vector2(1, 0);
+ InstanceDatas[Index].UV3 = new Vector2(1, 1);
+ Index += 1;
+ }
+
+ public void Upload(CommandBuffer commandBuffer)
+ {
+ commandBuffer.SetBufferData(BatchBuffer, InstanceDatas, 0, 0, (uint) Index);
+ Index = 0;
+ }
+ }
+}
diff --git a/StoreLoad/StoreLoad.csproj b/StoreLoad/StoreLoad.csproj
index fb73c0c..5c541ff 100644
--- a/StoreLoad/StoreLoad.csproj
+++ b/StoreLoad/StoreLoad.csproj
@@ -9,7 +9,6 @@
Exe
net8.0
enable
- x64