diff --git a/CopyTexture/CopyTexture.csproj b/CopyTexture/CopyTexture.csproj new file mode 100644 index 0000000..2868e63 --- /dev/null +++ b/CopyTexture/CopyTexture.csproj @@ -0,0 +1,17 @@ + + + + + + + + + Exe + net7.0 + enable + x64 + + + + + diff --git a/CopyTexture/CopyTextureGame.cs b/CopyTexture/CopyTextureGame.cs new file mode 100644 index 0000000..478f50c --- /dev/null +++ b/CopyTexture/CopyTextureGame.cs @@ -0,0 +1,180 @@ +using MoonWorks; +using MoonWorks.Graphics; +using MoonWorks.Math.Float; + +namespace MoonWorks.Test +{ + class CopyTextureGame : Game + { + private GraphicsPipeline pipeline; + private Buffer vertexBuffer; + private Buffer indexBuffer; + private Texture originalTexture; + private Texture textureCopy; + private Texture textureSmallCopy; + private Sampler sampler; + + public CopyTextureGame() : base(TestUtils.GetStandardWindowCreateInfo(), TestUtils.GetStandardFrameLimiterSettings(), 60, true) + { + // Load the shaders + ShaderModule vertShaderModule = new ShaderModule(GraphicsDevice, TestUtils.GetShaderPath("TexturedQuadVert.spv")); + ShaderModule fragShaderModule = new ShaderModule(GraphicsDevice, TestUtils.GetShaderPath("TexturedQuadFrag.spv")); + + // Create the graphics pipeline + GraphicsPipelineCreateInfo pipelineCreateInfo = TestUtils.GetStandardGraphicsPipelineCreateInfo( + MainWindow.SwapchainFormat, + vertShaderModule, + fragShaderModule + ); + pipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions[0].BlendState = ColorAttachmentBlendState.AlphaBlend; + pipelineCreateInfo.VertexInputState = VertexInputState.CreateSingleBinding(); + pipelineCreateInfo.FragmentShaderInfo.SamplerBindingCount = 1; + pipeline = new GraphicsPipeline(GraphicsDevice, pipelineCreateInfo); + + // Create sampler + sampler = new Sampler(GraphicsDevice, SamplerCreateInfo.PointClamp); + + // Create and populate the GPU resources + vertexBuffer = Buffer.Create(GraphicsDevice, BufferUsageFlags.Vertex, 12); + indexBuffer = Buffer.Create(GraphicsDevice, BufferUsageFlags.Index, 12); + + CommandBuffer cmdbuf = GraphicsDevice.AcquireCommandBuffer(); + + cmdbuf.SetBufferData( + vertexBuffer, + new PositionTextureVertex[] + { + new PositionTextureVertex(new Vector3(-1f, 0f, 0), new Vector2(0, 0)), + new PositionTextureVertex(new Vector3( 0f, 0f, 0), new Vector2(1, 0)), + new PositionTextureVertex(new Vector3( 0f, 1f, 0), new Vector2(1, 1)), + new PositionTextureVertex(new Vector3(-1f, 1f, 0), new Vector2(0, 1)), + + new PositionTextureVertex(new Vector3(0f, 0f, 0), new Vector2(0, 0)), + new PositionTextureVertex(new Vector3(1f, 0f, 0), new Vector2(1, 0)), + new PositionTextureVertex(new Vector3(1f, 1f, 0), new Vector2(1, 1)), + new PositionTextureVertex(new Vector3(0f, 1f, 0), new Vector2(0, 1)), + + new PositionTextureVertex(new Vector3(-0.5f, -1f, 0), new Vector2(0, 0)), + new PositionTextureVertex(new Vector3( 0.5f, -1f, 0), new Vector2(1, 0)), + new PositionTextureVertex(new Vector3( 0.5f, 0f, 0), new Vector2(1, 1)), + new PositionTextureVertex(new Vector3(-0.5f, 0f, 0), new Vector2(0, 1)) + } + ); + cmdbuf.SetBufferData( + indexBuffer, + new ushort[] + { + 0, 1, 2, + 0, 2, 3, + + // For testing index offsets + 8, 9, 10, + 8, 10, 11, + } + ); + + // Load the texture. Copy-pasted from Texture.LoadPNG, + // but with the texture bytes stored. + var pixels = RefreshCS.Refresh.Refresh_Image_Load( + TestUtils.GetTexturePath("ravioli.png"), + out var width, + out var height, + out var channels + ); + + var byteCount = (uint)(width * height * channels); + + TextureCreateInfo textureCreateInfo; + textureCreateInfo.Width = (uint)width; + textureCreateInfo.Height = (uint)height; + textureCreateInfo.Depth = 1; + textureCreateInfo.Format = TextureFormat.R8G8B8A8; + textureCreateInfo.IsCube = false; + textureCreateInfo.LevelCount = 1; + textureCreateInfo.UsageFlags = TextureUsageFlags.Sampler; + + originalTexture = new Texture(GraphicsDevice, textureCreateInfo); + cmdbuf.SetTextureData(originalTexture, pixels, byteCount); + + byte[] textureBytes = new byte[byteCount]; + System.Runtime.InteropServices.Marshal.Copy(pixels, textureBytes, 0, (int) byteCount); + + RefreshCS.Refresh.Refresh_Image_Free(pixels); + + // Create a 1:1 copy of the texture + textureCopy = new Texture(GraphicsDevice, textureCreateInfo); + cmdbuf.CopyTextureToTexture( + new TextureSlice(originalTexture), + new TextureSlice(textureCopy), + Filter.Linear + ); + + // Create a half-sized copy of this texture + textureCreateInfo.Width /= 2; + textureCreateInfo.Height /= 2; + textureSmallCopy = new Texture(GraphicsDevice, textureCreateInfo); + cmdbuf.CopyTextureToTexture( + new TextureSlice(originalTexture), + new TextureSlice( + textureSmallCopy, + new Rect( + (int) textureCreateInfo.Width, + (int) textureCreateInfo.Height + ) + ), + Filter.Linear + ); + + // Copy the texture to a buffer + Buffer compareBuffer = Buffer.Create(GraphicsDevice, 0, (uint) textureBytes.Length); + cmdbuf.CopyTextureToBuffer(new TextureSlice(originalTexture), compareBuffer); + + GraphicsDevice.Submit(cmdbuf); + GraphicsDevice.Wait(); + + // Compare the original bytes to the copied bytes. + // Doing a manual equality check per byte because I can't find a way to memcmp in C#. + byte[] copiedBytes = new byte[textureBytes.Length]; + compareBuffer.GetData(copiedBytes, (uint) copiedBytes.Length); + + for (int i = 0; i < copiedBytes.Length; i += 1) + { + if (textureBytes[i] != copiedBytes[i]) + { + Logger.LogError("FAIL! Original texture bytes do not match bytes from CopyTextureToBuffer!"); + return; + } + } + Logger.LogError("SUCCESS! Original texture bytes and the bytes from CopyTextureToBuffer match!"); + } + + protected override void Update(System.TimeSpan delta) { } + + protected override void Draw(double alpha) + { + CommandBuffer cmdbuf = GraphicsDevice.AcquireCommandBuffer(); + Texture? backbuffer = cmdbuf.AcquireSwapchainTexture(MainWindow); + if (backbuffer != null) + { + cmdbuf.BeginRenderPass(new ColorAttachmentInfo(backbuffer, Color.Black)); + cmdbuf.BindGraphicsPipeline(pipeline); + cmdbuf.BindVertexBuffers(vertexBuffer); + cmdbuf.BindIndexBuffer(indexBuffer, IndexElementSize.Sixteen); + cmdbuf.BindFragmentSamplers(new TextureSamplerBinding(originalTexture, sampler)); + cmdbuf.DrawIndexedPrimitives(0, 0, 2, 0, 0); + cmdbuf.BindFragmentSamplers(new TextureSamplerBinding(textureCopy, sampler)); + cmdbuf.DrawIndexedPrimitives(4, 0, 2, 0, 0); + cmdbuf.BindFragmentSamplers(new TextureSamplerBinding(textureSmallCopy, sampler)); + cmdbuf.DrawIndexedPrimitives(0, 6, 2, 0, 0); + cmdbuf.EndRenderPass(); + } + GraphicsDevice.Submit(cmdbuf); + } + + public static void Main(string[] args) + { + CopyTextureGame game = new CopyTextureGame(); + game.Run(); + } + } +} diff --git a/MoonWorksGraphicsTests.sln b/MoonWorksGraphicsTests.sln index 18d6ad5..3dac47a 100644 --- a/MoonWorksGraphicsTests.sln +++ b/MoonWorksGraphicsTests.sln @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DrawIndirect", "DrawIndirec EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompressedTextures", "CompressedTextures\CompressedTextures.csproj", "{E90D236C-BD0F-4420-ADD0-867D21F4DCA5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CopyTexture", "CopyTexture\CopyTexture.csproj", "{CF25A5A2-A0BD-4C9B-BB07-19CCD97C1C4E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -105,6 +107,10 @@ Global {E90D236C-BD0F-4420-ADD0-867D21F4DCA5}.Debug|x64.Build.0 = Debug|x64 {E90D236C-BD0F-4420-ADD0-867D21F4DCA5}.Release|x64.ActiveCfg = Release|Any CPU {E90D236C-BD0F-4420-ADD0-867D21F4DCA5}.Release|x64.Build.0 = Release|Any CPU + {CF25A5A2-A0BD-4C9B-BB07-19CCD97C1C4E}.Debug|x64.ActiveCfg = Debug|x64 + {CF25A5A2-A0BD-4C9B-BB07-19CCD97C1C4E}.Debug|x64.Build.0 = Debug|x64 + {CF25A5A2-A0BD-4C9B-BB07-19CCD97C1C4E}.Release|x64.ActiveCfg = Release|Any CPU + {CF25A5A2-A0BD-4C9B-BB07-19CCD97C1C4E}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index 80a9beb..b52b1e3 100644 --- a/README.md +++ b/README.md @@ -55,3 +55,7 @@ Draws two triangles via indirect commands. Tests DrawPrimitivesIndirect. **CompressedTextures** Loads a series of compressed textures, then displays them for viewing. Tests compressed texture loading. + +**CopyTexture** + +Loads an image, then makes three copies of the image. One is a 1:1 scale image, another is a half-sized image (to test linear/nearest blitting), and the final copy is to a buffer. The buffer bytes are then compared with the original texture bytes to verify the copy's correctness. Tests CopyTextureToTexture, CopyTextureToBuffer, vertex offsets, and index offsets.