Debug mode bounds checks for buffer and texture upload

pull/53/head
cosmonaut 2024-01-15 22:19:59 -08:00
parent eaa9266521
commit df3f38a67b
4 changed files with 127 additions and 7 deletions

View File

@ -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<T>();
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
/// <param name="data">A span of data to copy into the texture.</param>
public unsafe void SetTextureData<T>(in TextureSlice textureSlice, Span<T> data) where T : unmanaged
{
var dataLengthInBytes = (uint) (data.Length * Marshal.SizeOf<T>());
#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
}
}

View File

@ -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);
}
}

View File

@ -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<IntPtr, IntPtr> 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;
}
}
}
}

View File

@ -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;