Debug mode bounds checks for buffer and texture upload

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 #if DEBUG
AssertRenderPassInactive("Cannot copy during render pass!"); AssertRenderPassInactive("Cannot copy during render pass!");
AssertNonEmptyCopy(dataLengthInBytes); AssertNonEmptyCopy(dataLengthInBytes);
AssertBufferBoundsCheck(buffer.Size, bufferOffsetInBytes, dataLengthInBytes);
#endif #endif
Refresh.Refresh_SetBufferData( Refresh.Refresh_SetBufferData(
@ -1803,6 +1804,7 @@ namespace MoonWorks.Graphics
#if DEBUG #if DEBUG
AssertRenderPassInactive("Cannot copy during render pass!"); AssertRenderPassInactive("Cannot copy during render pass!");
AssertNonEmptyCopy(dataLengthInBytes); AssertNonEmptyCopy(dataLengthInBytes);
AssertBufferBoundsCheck(buffer.Size, bufferOffsetInBytes, dataLengthInBytes);
#endif #endif
fixed (T* ptr = data) fixed (T* ptr = data)
@ -1847,17 +1849,19 @@ namespace MoonWorks.Graphics
{ {
var elementSize = Marshal.SizeOf<T>(); var elementSize = Marshal.SizeOf<T>();
var dataLengthInBytes = (uint) (elementSize * numElements); var dataLengthInBytes = (uint) (elementSize * numElements);
var offsetLengthInBytes = (uint) elementSize * bufferOffsetInElements;
#if DEBUG #if DEBUG
AssertRenderPassInactive("Cannot copy during render pass!"); AssertRenderPassInactive("Cannot copy during render pass!");
AssertNonEmptyCopy((uint) (elementSize * numElements)); AssertNonEmptyCopy((uint) (elementSize * numElements));
AssertBufferBoundsCheck(buffer.Size, offsetLengthInBytes, dataLengthInBytes);
#endif #endif
Refresh.Refresh_SetBufferData( Refresh.Refresh_SetBufferData(
Device.Handle, Device.Handle,
Handle, Handle,
buffer.Handle, buffer.Handle,
(uint) elementSize * bufferOffsetInElements, offsetLengthInBytes,
dataPtr, dataPtr,
dataLengthInBytes dataLengthInBytes
); );
@ -1888,12 +1892,13 @@ namespace MoonWorks.Graphics
/// <param name="data">A span of data to copy into the texture.</param> /// <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 public unsafe void SetTextureData<T>(in TextureSlice textureSlice, Span<T> data) where T : unmanaged
{ {
var dataLengthInBytes = (uint) (data.Length * Marshal.SizeOf<T>());
#if DEBUG #if DEBUG
AssertRenderPassInactive("Cannot copy during render pass!"); AssertRenderPassInactive("Cannot copy during render pass!");
AssertTextureBoundsCheck(textureSlice.Size, dataLengthInBytes);
#endif #endif
var size = sizeof(T);
fixed (T* ptr = data) fixed (T* ptr = data)
{ {
Refresh.Refresh_SetTextureData( Refresh.Refresh_SetTextureData(
@ -1901,7 +1906,7 @@ namespace MoonWorks.Graphics
Handle, Handle,
textureSlice.ToRefreshTextureSlice(), textureSlice.ToRefreshTextureSlice(),
(IntPtr) ptr, (IntPtr) ptr,
(uint) (data.Length * size) dataLengthInBytes
); );
} }
} }
@ -1926,6 +1931,7 @@ namespace MoonWorks.Graphics
{ {
#if DEBUG #if DEBUG
AssertRenderPassInactive("Cannot copy during render pass!"); AssertRenderPassInactive("Cannot copy during render pass!");
AssertTextureBoundsCheck(textureSlice.Size, dataLengthInBytes);
#endif #endif
Refresh.Refresh_SetTextureData( Refresh.Refresh_SetTextureData(
@ -2027,6 +2033,7 @@ namespace MoonWorks.Graphics
{ {
#if DEBUG #if DEBUG
AssertRenderPassInactive("Cannot copy during render pass!"); AssertRenderPassInactive("Cannot copy during render pass!");
AssertBufferBoundsCheck(buffer.Size, 0, textureSlice.Size);
#endif #endif
var refreshTextureSlice = textureSlice.ToRefreshTextureSlice(); var refreshTextureSlice = textureSlice.ToRefreshTextureSlice();
@ -2209,6 +2216,22 @@ namespace MoonWorks.Graphics
throw new System.InvalidOperationException("SetBufferData must have a length greater than 0 bytes!"); 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 #endif
} }
} }

View File

@ -167,7 +167,7 @@ namespace MoonWorks.Graphics
window.SwapchainFormat = GetSwapchainFormat(window); window.SwapchainFormat = GetSwapchainFormat(window);
if (window.SwapchainTexture == null) 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 uint LevelCount { get; }
public SampleCount SampleCount { get; } public SampleCount SampleCount { get; }
public TextureUsageFlags UsageFlags { get; } public TextureUsageFlags UsageFlags { get; }
public uint Size { get; }
// FIXME: this allocates a delegate instance // FIXME: this allocates a delegate instance
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture; protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
@ -296,6 +297,7 @@ namespace MoonWorks.Graphics
LevelCount = textureCreateInfo.LevelCount; LevelCount = textureCreateInfo.LevelCount;
SampleCount = textureCreateInfo.SampleCount; SampleCount = textureCreateInfo.SampleCount;
UsageFlags = textureCreateInfo.UsageFlags; UsageFlags = textureCreateInfo.UsageFlags;
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
} }
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t); public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
@ -303,12 +305,13 @@ namespace MoonWorks.Graphics
// Used by AcquireSwapchainTexture. // Used by AcquireSwapchainTexture.
// Should not be tracked, because swapchain textures are managed by Vulkan. // Should not be tracked, because swapchain textures are managed by Vulkan.
internal Texture( internal Texture(
GraphicsDevice device GraphicsDevice device,
TextureFormat format
) : base(device) ) : base(device)
{ {
Handle = IntPtr.Zero; Handle = IntPtr.Zero;
Format = TextureFormat.R8G8B8A8; Format = format;
Width = 0; Width = 0;
Height = 0; Height = 0;
Depth = 1; Depth = 1;
@ -316,6 +319,7 @@ namespace MoonWorks.Graphics
LevelCount = 1; LevelCount = 1;
SampleCount = SampleCount.One; SampleCount = SampleCount.One;
UsageFlags = TextureUsageFlags.ColorTarget; UsageFlags = TextureUsageFlags.ColorTarget;
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
} }
// DDS loading extension, based on MojoDDS // DDS loading extension, based on MojoDDS
@ -644,5 +648,96 @@ namespace MoonWorks.Graphics
NativeMemory.Free(pixelsPtr); 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 Layer { get; }
public uint Level { 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) public TextureSlice(Texture texture)
{ {
Texture = texture; Texture = texture;