Skip to content

Commit

Permalink
Implement BC1 and BC2 compressed texture formats
Browse files Browse the repository at this point in the history
and fix bugs in BC3 implementation when copying non-origin texture regions
  • Loading branch information
tgjones authored and mellinoe committed Feb 3, 2018
1 parent 56322d3 commit 380a620
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 48 deletions.
36 changes: 26 additions & 10 deletions src/Veldrid.Tests/TextureTests.cs
Expand Up @@ -199,15 +199,26 @@ public unsafe void Update_ThenCopySingleMip_Succeeds_R16UNorm()
}
}

[Fact]
public unsafe void Copy_BC3_Unorm()
[Theory]
[InlineData(PixelFormat.BC1_Rgb_UNorm, 8, 0, 0, 64, 64)]
[InlineData(PixelFormat.BC1_Rgb_UNorm, 8, 8, 4, 16, 16)]
[InlineData(PixelFormat.BC1_Rgba_UNorm, 8, 0, 0, 64, 64)]
[InlineData(PixelFormat.BC1_Rgba_UNorm, 8, 8, 4, 16, 16)]
[InlineData(PixelFormat.BC2_UNorm, 16, 0, 0, 64, 64)]
[InlineData(PixelFormat.BC2_UNorm, 16, 8, 4, 16, 16)]
[InlineData(PixelFormat.BC3_UNorm, 16, 0, 0, 64, 64)]
[InlineData(PixelFormat.BC3_UNorm, 16, 8, 4, 16, 16)]
public unsafe void Copy_Compressed_Texture(PixelFormat format, uint blockSizeInBytes, uint srcX, uint srcY, uint copyWidth, uint copyHeight)
{
Texture copySrc = RF.CreateTexture(TextureDescription.Texture2D(
64, 64, 1, 1, PixelFormat.BC3_UNorm, TextureUsage.Staging));
64, 64, 1, 1, format, TextureUsage.Staging));
Texture copyDst = RF.CreateTexture(TextureDescription.Texture2D(
64, 64, 1, 1, PixelFormat.BC3_UNorm, TextureUsage.Staging));
copyWidth, copyHeight, 1, 1, format, TextureUsage.Staging));

const int numPixelsInBlockSide = 4;
const int numPixelsInBlock = 16;

uint totalDataSize = copySrc.Width * copySrc.Height;
uint totalDataSize = copyWidth * copyHeight / numPixelsInBlock * blockSizeInBytes;
byte[] data = new byte[totalDataSize];

for (int i = 0; i < data.Length; i++)
Expand All @@ -216,23 +227,28 @@ public unsafe void Copy_BC3_Unorm()
}
fixed (byte* dataPtr = data)
{
GD.UpdateTexture(copySrc, (IntPtr)dataPtr, totalDataSize, 0, 0, 0, copySrc.Width, copySrc.Height, 1, 0, 0);
GD.UpdateTexture(copySrc, (IntPtr)dataPtr, totalDataSize, srcX, srcY, 0, copyWidth, copyHeight, 1, 0, 0);
}

CommandList cl = RF.CreateCommandList();
cl.Begin();
cl.CopyTexture(
copySrc, 0, 0, 0, 0, 0,
copySrc, srcX, srcY, 0, 0, 0,
copyDst, 0, 0, 0, 0, 0,
copySrc.Width, copySrc.Height, 1, 1);
copyWidth, copyHeight, 1, 1);
cl.End();
GD.SubmitCommands(cl);
GD.WaitForIdle();

uint numBytesPerRow = copyWidth / numPixelsInBlockSide * blockSizeInBytes;
MappedResourceView<byte> view = GD.Map<byte>(copyDst, MapMode.Read);
for (int i = 0; i < data.Length; i++)
for (uint i = 0; i < data.Length; i++)
{
Assert.Equal(view[i], data[i]);
uint viewRow = i / numBytesPerRow;
uint viewIndex = (view.MappedResource.RowPitch * viewRow) + (i % numBytesPerRow);
Assert.Equal(data[i], view[viewIndex]);
}
GD.Unmap(copyDst);
}

[Fact]
Expand Down
5 changes: 5 additions & 0 deletions src/Veldrid/D3D11/D3D11Formats.cs
Expand Up @@ -28,6 +28,11 @@ internal static Format ToDxgiFormat(PixelFormat format, bool depthFormat)
return Format.R32G32B32A32_UInt;
case PixelFormat.R32_Float:
return depthFormat ? Format.R32_Typeless : Format.R32_Float;
case PixelFormat.BC1_Rgb_UNorm:
case PixelFormat.BC1_Rgba_UNorm:
return Format.BC1_UNorm;
case PixelFormat.BC2_UNorm:
return Format.BC2_UNorm;
case PixelFormat.BC3_UNorm:
return Format.BC3_UNorm;
case PixelFormat.D24_UNorm_S8_UInt:
Expand Down
33 changes: 11 additions & 22 deletions src/Veldrid/D3D11/D3D11GraphicsDevice.cs
Expand Up @@ -434,7 +434,7 @@ protected unsafe override void UpdateBufferCore(DeviceBuffer buffer, uint buffer
}
}

public unsafe override void UpdateTexture(
protected unsafe override void UpdateTextureCore(
Texture texture,
IntPtr source,
uint sizeInBytes,
Expand All @@ -449,20 +449,15 @@ protected unsafe override void UpdateBufferCore(DeviceBuffer buffer, uint buffer
{
D3D11Texture d3dTex = Util.AssertSubtype<Texture, D3D11Texture>(texture);
bool useMap = (texture.Usage & TextureUsage.Staging) == TextureUsage.Staging;
uint blockSize = 1;
if (texture.Format == PixelFormat.BC3_UNorm)
{
blockSize = 4;
}
if (useMap)
{
uint subresource = texture.CalculateSubresource(mipLevel, arrayLayer);
MappedResourceCacheKey key = new MappedResourceCacheKey(texture, subresource);
MappedResource map = MapCore(texture, MapMode.Write, subresource);

uint pixelSizeInBytes = FormatHelpers.GetSizeInBytes(texture.Format);
uint denseRowSize = width * pixelSizeInBytes * blockSize;
uint denseSliceSize = width * height * pixelSizeInBytes;
uint denseRowSize = FormatHelpers.GetRowPitch(width, texture.Format);
uint denseSliceSize = FormatHelpers.GetDepthPitch(denseRowSize, height, texture.Format);

if (x == 0 && y == 0 && z == 0
&& map.RowPitch == denseRowSize)
Expand All @@ -485,7 +480,7 @@ protected unsafe override void UpdateBufferCore(DeviceBuffer buffer, uint buffer
}
else
{
if (blockSize == 1)
if (!FormatHelpers.IsCompressedFormat(texture.Format))
{
for (uint zz = 0; zz < depth; zz++)
for (uint yy = 0; yy < height; yy += 1)
Expand All @@ -502,24 +497,18 @@ protected unsafe override void UpdateBufferCore(DeviceBuffer buffer, uint buffer
}
else
{
denseRowSize = Math.Max(denseRowSize, Math.Max(width, blockSize) * pixelSizeInBytes * blockSize);
denseSliceSize = Math.Max(denseSliceSize, Math.Max(width, blockSize) * Math.Max(height, blockSize) * pixelSizeInBytes);
if (height % 4 != 0 || width % 4 != 0)
{
Util.GetMipDimensions(texture, mipLevel, out uint mipWidth, out uint mipHeight, out uint _);
if (width != mipWidth && height != mipHeight)
{
throw new VeldridException($"Updates to block-compressed textures must use a region that is block-size aligned and sized.");
}
}
uint numRows = Math.Max(1, height / blockSize);
uint numRows = FormatHelpers.GetNumRows(height, texture.Format);
uint compressedX = x / 4;
uint compressedY = y / 4;
uint blockSizeInBytes = FormatHelpers.GetBlockSizeInBytes(texture.Format);

for (uint zz = 0; zz < depth; zz++)
for (uint row = 0; row < numRows; row++)
{
byte* dstRowStart = ((byte*)map.Data)
+ (map.DepthPitch * (zz + z))
+ (map.RowPitch * (row + y))
+ (pixelSizeInBytes * x);
+ (map.RowPitch * (row + compressedY))
+ (blockSizeInBytes * compressedX);
byte* srcRowStart = ((byte*)source.ToPointer())
+ (denseSliceSize * zz)
+ (denseRowSize * row);
Expand Down
60 changes: 59 additions & 1 deletion src/Veldrid/FormatHelpers.cs
Expand Up @@ -20,6 +20,9 @@ public static uint GetSizeInBytes(PixelFormat format)
case PixelFormat.R32_G32_B32_A32_Float:
case PixelFormat.R32_G32_B32_A32_UInt:
return 16;
case PixelFormat.BC1_Rgb_UNorm:
case PixelFormat.BC1_Rgba_UNorm:
case PixelFormat.BC2_UNorm:
case PixelFormat.BC3_UNorm:
return 1; // Not really
default: throw Illegal.Value<PixelFormat>();
Expand Down Expand Up @@ -137,7 +140,62 @@ internal static bool IsStencilFormat(PixelFormat format)

internal static bool IsCompressedFormat(PixelFormat format)
{
return format == PixelFormat.BC3_UNorm;
return format == PixelFormat.BC1_Rgb_UNorm
|| format == PixelFormat.BC1_Rgba_UNorm
|| format == PixelFormat.BC2_UNorm
|| format == PixelFormat.BC3_UNorm;
}

internal static uint GetRowPitch(uint width, PixelFormat format)
{
switch (format)
{
case PixelFormat.BC1_Rgba_UNorm:
case PixelFormat.BC1_Rgb_UNorm:
case PixelFormat.BC2_UNorm:
case PixelFormat.BC3_UNorm:
var blocksPerRow = (width + 3) / 4;
var blockSizeInBytes = GetBlockSizeInBytes(format);
return blocksPerRow * blockSizeInBytes;

default:
return width * GetSizeInBytes(format);
}
}

public static uint GetBlockSizeInBytes(PixelFormat format)
{
switch (format)
{
case PixelFormat.BC1_Rgba_UNorm:
case PixelFormat.BC1_Rgb_UNorm:
return 8;
case PixelFormat.BC2_UNorm:
case PixelFormat.BC3_UNorm:
return 16;
default:
throw Illegal.Value<PixelFormat>();
}
}

internal static uint GetNumRows(uint height, PixelFormat format)
{
switch (format)
{
case PixelFormat.BC1_Rgba_UNorm:
case PixelFormat.BC1_Rgb_UNorm:
case PixelFormat.BC2_UNorm:
case PixelFormat.BC3_UNorm:
return (height + 3) / 4;

default:
return height;
}
}

internal static uint GetDepthPitch(uint rowPitch, uint height, PixelFormat format)
{
return rowPitch * GetNumRows(height, format);
}
}
}
27 changes: 26 additions & 1 deletion src/Veldrid/GraphicsDevice.cs
Expand Up @@ -295,7 +295,32 @@ public void Unmap(MappableResource resource, uint subresource)
/// <see cref="Texture"/>.</param>
/// <param name="arrayLayer">The array layer to update. Must be less than the total array layer count contained in the
/// <see cref="Texture"/>.</param>
public abstract void UpdateTexture(
public void UpdateTexture(
Texture texture,
IntPtr source,
uint sizeInBytes,
uint x,
uint y,
uint z,
uint width,
uint height,
uint depth,
uint mipLevel,
uint arrayLayer)
{
#if VALIDATE_USAGE
if (FormatHelpers.IsCompressedFormat(texture.Format))
{
if (x % 4 != 0 || y % 4 != 0 || height % 4 != 0 || width % 4 != 0)
{
throw new VeldridException($"Updates to block-compressed textures must use a region that is block-size aligned and sized.");
}
}
#endif
UpdateTextureCore(texture, source, sizeInBytes, x, y, z, width, height, depth, mipLevel, arrayLayer);
}

protected abstract void UpdateTextureCore(
Texture texture,
IntPtr source,
uint sizeInBytes,
Expand Down
5 changes: 5 additions & 0 deletions src/Veldrid/MTL/MTLFormats.cs
Expand Up @@ -11,6 +11,11 @@ internal static MTLPixelFormat VdToMTLPixelFormat(PixelFormat format, bool depth
{
case PixelFormat.B8_G8_R8_A8_UNorm:
return MTLPixelFormat.BGRA8Unorm;
case PixelFormat.BC1_Rgb_UNorm:
case PixelFormat.BC1_Rgba_UNorm:
return MTLPixelFormat.BC1_RGBA;
case PixelFormat.BC2_UNorm:
return MTLPixelFormat.BC2_RGBA;
case PixelFormat.BC3_UNorm:
return MTLPixelFormat.BC3_RGBA;
case PixelFormat.D24_UNorm_S8_UInt:
Expand Down
2 changes: 1 addition & 1 deletion src/Veldrid/MTL/MTLGraphicsDevice.cs
Expand Up @@ -141,7 +141,7 @@ protected override void UpdateBufferCore(DeviceBuffer buffer, uint bufferOffsetI
Unsafe.CopyBlock(destOffsetPtr, source.ToPointer(), sizeInBytes);
}

public override void UpdateTexture(
protected override void UpdateTextureCore(
Texture texture,
IntPtr source,
uint sizeInBytes,
Expand Down
22 changes: 14 additions & 8 deletions src/Veldrid/OpenGL/OpenGLCommandExecutor.cs
Expand Up @@ -727,9 +727,15 @@ public void UpdateBuffer(DeviceBuffer buffer, uint bufferOffsetInBytes, IntPtr d
glBindTexture(texTarget, glTex.Texture);
CheckLastError();

bool isCompressed = texture.Format == PixelFormat.BC3_UNorm;
bool isCompressed = FormatHelpers.IsCompressedFormat(texture.Format);
uint blockSize = isCompressed ? 4u : 1u;

uint blockAlignedWidth = Math.Max(width, blockSize);
uint blockAlignedHeight = Math.Max(width, blockSize);

uint rowPitch = FormatHelpers.GetRowPitch(blockAlignedWidth, texture.Format);
uint depthPitch = FormatHelpers.GetDepthPitch(rowPitch, blockAlignedHeight, texture.Format);

uint pixelSize = FormatHelpers.GetSizeInBytes(glTex.Format);
if (pixelSize < 4)
{
Expand All @@ -747,7 +753,7 @@ public void UpdateBuffer(DeviceBuffer buffer, uint bufferOffsetInBytes, IntPtr d
(int)x,
width,
glTex.GLInternalFormat,
pixelSize * Math.Max(width, blockSize),
rowPitch,
dataPtr.ToPointer());
CheckLastError();
}
Expand Down Expand Up @@ -776,7 +782,7 @@ public void UpdateBuffer(DeviceBuffer buffer, uint bufferOffsetInBytes, IntPtr d
width,
1,
glTex.GLInternalFormat,
pixelSize * Math.Max(width, blockSize),
rowPitch,
dataPtr.ToPointer());
CheckLastError();
}
Expand Down Expand Up @@ -807,7 +813,7 @@ public void UpdateBuffer(DeviceBuffer buffer, uint bufferOffsetInBytes, IntPtr d
width,
height,
glTex.GLInternalFormat,
pixelSize * Math.Max(width, blockSize) * Math.Max(height, blockSize),
depthPitch,
dataPtr.ToPointer());
CheckLastError();
}
Expand Down Expand Up @@ -840,7 +846,7 @@ public void UpdateBuffer(DeviceBuffer buffer, uint bufferOffsetInBytes, IntPtr d
height,
1,
glTex.GLInternalFormat,
pixelSize * Math.Max(width, blockSize) * Math.Max(height, blockSize),
depthPitch,
dataPtr.ToPointer());
CheckLastError();
}
Expand Down Expand Up @@ -875,7 +881,7 @@ public void UpdateBuffer(DeviceBuffer buffer, uint bufferOffsetInBytes, IntPtr d
height,
depth,
glTex.GLInternalFormat,
pixelSize * Math.Max(width, blockSize) * Math.Max(height, blockSize) * depth,
depthPitch * depth,
dataPtr.ToPointer());
CheckLastError();
}
Expand Down Expand Up @@ -909,7 +915,7 @@ public void UpdateBuffer(DeviceBuffer buffer, uint bufferOffsetInBytes, IntPtr d
width,
height,
glTex.GLInternalFormat,
pixelSize * Math.Max(width, blockSize) * Math.Max(height, blockSize),
depthPitch,
dataPtr.ToPointer());
CheckLastError();
}
Expand Down Expand Up @@ -942,7 +948,7 @@ public void UpdateBuffer(DeviceBuffer buffer, uint bufferOffsetInBytes, IntPtr d
height,
1,
glTex.GLInternalFormat,
pixelSize * Math.Max(width, blockSize) * Math.Max(height, blockSize),
depthPitch,
dataPtr.ToPointer());
CheckLastError();
}
Expand Down

0 comments on commit 380a620

Please sign in to comment.