From df3f38a67b667a3c7aaff1c88d9166b5149625d7 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 15 Jan 2024 22:19:59 -0800 Subject: [PATCH] Debug mode bounds checks for buffer and texture upload --- src/Graphics/CommandBuffer.cs | 31 ++++++++-- src/Graphics/GraphicsDevice.cs | 2 +- src/Graphics/Resources/Texture.cs | 99 ++++++++++++++++++++++++++++++- src/Graphics/TextureSlice.cs | 2 + 4 files changed, 127 insertions(+), 7 deletions(-) diff --git a/src/Graphics/CommandBuffer.cs b/src/Graphics/CommandBuffer.cs index 431770d..7debb1e 100644 --- a/src/Graphics/CommandBuffer.cs +++ b/src/Graphics/CommandBuffer.cs @@ -1767,6 +1767,7 @@ namespace MoonWorks.Graphics #if DEBUG AssertRenderPassInactive("Cannot copy during render pass!"); AssertNonEmptyCopy(dataLengthInBytes); + AssertBufferBoundsCheck(buffer.Size, bufferOffsetInBytes, dataLengthInBytes); #endif Refresh.Refresh_SetBufferData( @@ -1803,6 +1804,7 @@ namespace MoonWorks.Graphics #if DEBUG AssertRenderPassInactive("Cannot copy during render pass!"); AssertNonEmptyCopy(dataLengthInBytes); + AssertBufferBoundsCheck(buffer.Size, bufferOffsetInBytes, dataLengthInBytes); #endif fixed (T* ptr = data) @@ -1847,17 +1849,19 @@ namespace MoonWorks.Graphics { var elementSize = Marshal.SizeOf(); var dataLengthInBytes = (uint) (elementSize * numElements); + var offsetLengthInBytes = (uint) elementSize * bufferOffsetInElements; #if DEBUG AssertRenderPassInactive("Cannot copy during render pass!"); AssertNonEmptyCopy((uint) (elementSize * numElements)); + AssertBufferBoundsCheck(buffer.Size, offsetLengthInBytes, dataLengthInBytes); #endif Refresh.Refresh_SetBufferData( Device.Handle, Handle, buffer.Handle, - (uint) elementSize * bufferOffsetInElements, + offsetLengthInBytes, dataPtr, dataLengthInBytes ); @@ -1888,12 +1892,13 @@ namespace MoonWorks.Graphics /// A span of data to copy into the texture. public unsafe void SetTextureData(in TextureSlice textureSlice, Span data) where T : unmanaged { + var dataLengthInBytes = (uint) (data.Length * Marshal.SizeOf()); + #if DEBUG AssertRenderPassInactive("Cannot copy during render pass!"); + AssertTextureBoundsCheck(textureSlice.Size, dataLengthInBytes); #endif - var size = sizeof(T); - fixed (T* ptr = data) { Refresh.Refresh_SetTextureData( @@ -1901,7 +1906,7 @@ namespace MoonWorks.Graphics Handle, textureSlice.ToRefreshTextureSlice(), (IntPtr) ptr, - (uint) (data.Length * size) + dataLengthInBytes ); } } @@ -1926,6 +1931,7 @@ namespace MoonWorks.Graphics { #if DEBUG AssertRenderPassInactive("Cannot copy during render pass!"); + AssertTextureBoundsCheck(textureSlice.Size, dataLengthInBytes); #endif Refresh.Refresh_SetTextureData( @@ -2027,6 +2033,7 @@ namespace MoonWorks.Graphics { #if DEBUG AssertRenderPassInactive("Cannot copy during render pass!"); + AssertBufferBoundsCheck(buffer.Size, 0, textureSlice.Size); #endif var refreshTextureSlice = textureSlice.ToRefreshTextureSlice(); @@ -2209,6 +2216,22 @@ namespace MoonWorks.Graphics throw new System.InvalidOperationException("SetBufferData must have a length greater than 0 bytes!"); } } + + private void AssertBufferBoundsCheck(uint bufferLengthInBytes, uint offsetInBytes, uint copyLengthInBytes) + { + if (copyLengthInBytes > bufferLengthInBytes + offsetInBytes) + { + throw new System.InvalidOperationException($"SetBufferData overflow! buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}"); + } + } + + private void AssertTextureBoundsCheck(uint textureSizeInBytes, uint dataLengthInBytes) + { + if (dataLengthInBytes > textureSizeInBytes) + { + throw new System.InvalidOperationException($"SetTextureData overflow! texture size {textureSizeInBytes}, data size {dataLengthInBytes}"); + } + } #endif } } diff --git a/src/Graphics/GraphicsDevice.cs b/src/Graphics/GraphicsDevice.cs index 7fc0737..71ed6a4 100644 --- a/src/Graphics/GraphicsDevice.cs +++ b/src/Graphics/GraphicsDevice.cs @@ -167,7 +167,7 @@ namespace MoonWorks.Graphics window.SwapchainFormat = GetSwapchainFormat(window); if (window.SwapchainTexture == null) { - window.SwapchainTexture = new Texture(this); + window.SwapchainTexture = new Texture(this, window.SwapchainFormat); } } diff --git a/src/Graphics/Resources/Texture.cs b/src/Graphics/Resources/Texture.cs index 7a8fadb..ecf9298 100644 --- a/src/Graphics/Resources/Texture.cs +++ b/src/Graphics/Resources/Texture.cs @@ -18,6 +18,7 @@ namespace MoonWorks.Graphics public uint LevelCount { get; } public SampleCount SampleCount { get; } public TextureUsageFlags UsageFlags { get; } + public uint Size { get; } // FIXME: this allocates a delegate instance protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture; @@ -296,6 +297,7 @@ namespace MoonWorks.Graphics LevelCount = textureCreateInfo.LevelCount; SampleCount = textureCreateInfo.SampleCount; UsageFlags = textureCreateInfo.UsageFlags; + Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format); } public static implicit operator TextureSlice(Texture t) => new TextureSlice(t); @@ -303,12 +305,13 @@ namespace MoonWorks.Graphics // Used by AcquireSwapchainTexture. // Should not be tracked, because swapchain textures are managed by Vulkan. internal Texture( - GraphicsDevice device + GraphicsDevice device, + TextureFormat format ) : base(device) { Handle = IntPtr.Zero; - Format = TextureFormat.R8G8B8A8; + Format = format; Width = 0; Height = 0; Depth = 1; @@ -316,6 +319,7 @@ namespace MoonWorks.Graphics LevelCount = 1; SampleCount = SampleCount.One; UsageFlags = TextureUsageFlags.ColorTarget; + Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format); } // DDS loading extension, based on MojoDDS @@ -644,5 +648,96 @@ namespace MoonWorks.Graphics NativeMemory.Free(pixelsPtr); } + + public static uint BytesPerPixel(TextureFormat format) + { + switch (format) + { + case TextureFormat.R8: + case TextureFormat.R8_UINT: + return 1; + case TextureFormat.R5G6B5: + case TextureFormat.B4G4R4A4: + case TextureFormat.A1R5G5B5: + case TextureFormat.R16_SFLOAT: + case TextureFormat.R8G8_SNORM: + case TextureFormat.R8G8_UINT: + case TextureFormat.R16_UINT: + case TextureFormat.D16: + return 2; + case TextureFormat.D16S8: + return 3; + case TextureFormat.R8G8B8A8: + case TextureFormat.B8G8R8A8: + case TextureFormat.R32_SFLOAT: + case TextureFormat.R16G16: + case TextureFormat.R16G16_SFLOAT: + case TextureFormat.R8G8B8A8_SNORM: + case TextureFormat.A2R10G10B10: + case TextureFormat.R8G8B8A8_UINT: + case TextureFormat.R16G16_UINT: + case TextureFormat.D32: + return 4; + case TextureFormat.D32S8: + return 5; + case TextureFormat.R16G16B16A16_SFLOAT: + case TextureFormat.R16G16B16A16: + case TextureFormat.R32G32_SFLOAT: + case TextureFormat.R16G16B16A16_UINT: + case TextureFormat.BC1: + return 8; + case TextureFormat.R32G32B32A32_SFLOAT: + case TextureFormat.BC2: + case TextureFormat.BC3: + case TextureFormat.BC7: + return 16; + default: + Logger.LogError("Texture format not recognized!"); + return 0; + } + } + + public static uint BlockSizeSquared(TextureFormat format) + { + switch (format) + { + case TextureFormat.BC1: + case TextureFormat.BC2: + case TextureFormat.BC3: + case TextureFormat.BC7: + return 16; + case TextureFormat.R8G8B8A8: + case TextureFormat.B8G8R8A8: + case TextureFormat.R5G6B5: + case TextureFormat.A1R5G5B5: + case TextureFormat.B4G4R4A4: + case TextureFormat.A2R10G10B10: + case TextureFormat.R16G16: + case TextureFormat.R16G16B16A16: + case TextureFormat.R8: + case TextureFormat.R8G8_SNORM: + case TextureFormat.R8G8B8A8_SNORM: + case TextureFormat.R16_SFLOAT: + case TextureFormat.R16G16_SFLOAT: + case TextureFormat.R16G16B16A16_SFLOAT: + case TextureFormat.R32_SFLOAT: + case TextureFormat.R32G32_SFLOAT: + case TextureFormat.R32G32B32A32_SFLOAT: + case TextureFormat.R8_UINT: + case TextureFormat.R8G8_UINT: + case TextureFormat.R8G8B8A8_UINT: + case TextureFormat.R16_UINT: + case TextureFormat.R16G16_UINT: + case TextureFormat.R16G16B16A16_UINT: + case TextureFormat.D16: + case TextureFormat.D32: + case TextureFormat.D16S8: + case TextureFormat.D32S8: + return 1; + default: + Logger.LogError("Texture format not recognized!"); + return 0; + } + } } } diff --git a/src/Graphics/TextureSlice.cs b/src/Graphics/TextureSlice.cs index e640083..f0f8e30 100644 --- a/src/Graphics/TextureSlice.cs +++ b/src/Graphics/TextureSlice.cs @@ -14,6 +14,8 @@ namespace MoonWorks.Graphics public uint Layer { get; } public uint Level { get; } + public uint Size => (uint) (Rectangle.W * Rectangle.H * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format)); + public TextureSlice(Texture texture) { Texture = texture;