From 1eae01c95cd5a73b44f400f67aed1abbdb352ba1 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 23 Feb 2024 11:59:33 -0800 Subject: [PATCH] add DDS parser to ImageUtils --- src/Graphics/ImageUtils.cs | 268 ++++++++++++++++++++++++++++ src/Graphics/Resources/Texture.cs | 283 ++---------------------------- 2 files changed, 283 insertions(+), 268 deletions(-) diff --git a/src/Graphics/ImageUtils.cs b/src/Graphics/ImageUtils.cs index de6f2f4..8908e34 100644 --- a/src/Graphics/ImageUtils.cs +++ b/src/Graphics/ImageUtils.cs @@ -229,5 +229,273 @@ namespace MoonWorks.Graphics Refresh.Refresh_Image_SavePNG(path, (nint) pixelsPtr, width, height); NativeMemory.Free(pixelsPtr); } + + // DDS loading extension, based on MojoDDS + // Taken from https://github.com/FNA-XNA/FNA/blob/1e49f868f595f62bc6385db45949a03186a7cd7f/src/Graphics/Texture.cs#L194 + public static void ParseDDS( + BinaryReader reader, + out TextureFormat format, + out int width, + out int height, + out int levels, + out bool isCube + ) { + // A whole bunch of magic numbers, yay DDS! + const uint DDS_MAGIC = 0x20534444; + const uint DDS_HEADERSIZE = 124; + const uint DDS_PIXFMTSIZE = 32; + const uint DDSD_HEIGHT = 0x2; + const uint DDSD_WIDTH = 0x4; + const uint DDSD_PITCH = 0x8; + const uint DDSD_LINEARSIZE = 0x80000; + const uint DDSD_REQ = ( + DDSD_HEIGHT | DDSD_WIDTH + ); + const uint DDSCAPS_MIPMAP = 0x400000; + const uint DDSCAPS_TEXTURE = 0x1000; + const uint DDSCAPS2_CUBEMAP = 0x200; + const uint DDPF_FOURCC = 0x4; + const uint DDPF_RGB = 0x40; + const uint FOURCC_DXT1 = 0x31545844; + const uint FOURCC_DXT3 = 0x33545844; + const uint FOURCC_DXT5 = 0x35545844; + const uint FOURCC_DX10 = 0x30315844; + const uint pitchAndLinear = ( + DDSD_PITCH | DDSD_LINEARSIZE + ); + + // File should start with 'DDS ' + if (reader.ReadUInt32() != DDS_MAGIC) + { + throw new NotSupportedException("Not a DDS!"); + } + + // Texture info + uint size = reader.ReadUInt32(); + if (size != DDS_HEADERSIZE) + { + throw new NotSupportedException("Invalid DDS header!"); + } + uint flags = reader.ReadUInt32(); + if ((flags & DDSD_REQ) != DDSD_REQ) + { + throw new NotSupportedException("Invalid DDS flags!"); + } + if ((flags & pitchAndLinear) == pitchAndLinear) + { + throw new NotSupportedException("Invalid DDS flags!"); + } + height = reader.ReadInt32(); + width = reader.ReadInt32(); + reader.ReadUInt32(); // dwPitchOrLinearSize, unused + reader.ReadUInt32(); // dwDepth, unused + levels = reader.ReadInt32(); + + // "Reserved" + reader.ReadBytes(4 * 11); + + // Format info + uint formatSize = reader.ReadUInt32(); + if (formatSize != DDS_PIXFMTSIZE) + { + throw new NotSupportedException("Bogus PIXFMTSIZE!"); + } + uint formatFlags = reader.ReadUInt32(); + uint formatFourCC = reader.ReadUInt32(); + uint formatRGBBitCount = reader.ReadUInt32(); + uint formatRBitMask = reader.ReadUInt32(); + uint formatGBitMask = reader.ReadUInt32(); + uint formatBBitMask = reader.ReadUInt32(); + uint formatABitMask = reader.ReadUInt32(); + + // dwCaps "stuff" + uint caps = reader.ReadUInt32(); + if ((caps & DDSCAPS_TEXTURE) == 0) + { + throw new NotSupportedException("Not a texture!"); + } + + isCube = false; + + uint caps2 = reader.ReadUInt32(); + if (caps2 != 0) + { + if ((caps2 & DDSCAPS2_CUBEMAP) == DDSCAPS2_CUBEMAP) + { + isCube = true; + } + else + { + throw new NotSupportedException("Invalid caps2!"); + } + } + + reader.ReadUInt32(); // dwCaps3, unused + reader.ReadUInt32(); // dwCaps4, unused + + // "Reserved" + reader.ReadUInt32(); + + // Mipmap sanity check + if ((caps & DDSCAPS_MIPMAP) != DDSCAPS_MIPMAP) + { + levels = 1; + } + + // Determine texture format + if ((formatFlags & DDPF_FOURCC) == DDPF_FOURCC) + { + switch (formatFourCC) + { + case 0x71: // D3DFMT_A16B16G16R16F + format = TextureFormat.R16G16B16A16_SFLOAT; + break; + case 0x74: // D3DFMT_A32B32G32R32F + format = TextureFormat.R32G32B32A32_SFLOAT; + break; + case FOURCC_DXT1: + format = TextureFormat.BC1; + break; + case FOURCC_DXT3: + format = TextureFormat.BC2; + break; + case FOURCC_DXT5: + format = TextureFormat.BC3; + break; + case FOURCC_DX10: + // If the fourCC is DX10, there is an extra header with additional format information. + uint dxgiFormat = reader.ReadUInt32(); + + // These values are taken from the DXGI_FORMAT enum. + switch (dxgiFormat) + { + case 2: + format = TextureFormat.R32G32B32A32_SFLOAT; + break; + + case 10: + format = TextureFormat.R16G16B16A16_SFLOAT; + break; + + case 71: + format = TextureFormat.BC1; + break; + + case 74: + format = TextureFormat.BC2; + break; + + case 77: + format = TextureFormat.BC3; + break; + + case 98: + format = TextureFormat.BC7; + break; + + default: + throw new NotSupportedException( + "Unsupported DDS texture format" + ); + } + + uint resourceDimension = reader.ReadUInt32(); + + // These values are taken from the D3D10_RESOURCE_DIMENSION enum. + switch (resourceDimension) + { + case 0: // Unknown + case 1: // Buffer + throw new NotSupportedException( + "Unsupported DDS texture format" + ); + default: + break; + } + + /* + * This flag seemingly only indicates if the texture is a cube map. + * This is already determined above. Cool! + */ + uint miscFlag = reader.ReadUInt32(); + + /* + * Indicates the number of elements in the texture array. + * We don't support texture arrays so just throw if it's greater than 1. + */ + uint arraySize = reader.ReadUInt32(); + + if (arraySize > 1) + { + throw new NotSupportedException( + "Unsupported DDS texture format" + ); + } + + reader.ReadUInt32(); // reserved + + break; + default: + throw new NotSupportedException( + "Unsupported DDS texture format" + ); + } + } + else if ((formatFlags & DDPF_RGB) == DDPF_RGB) + { + if ( formatRGBBitCount != 32 || + formatRBitMask != 0x00FF0000 || + formatGBitMask != 0x0000FF00 || + formatBBitMask != 0x000000FF || + formatABitMask != 0xFF000000 ) + { + throw new NotSupportedException( + "Unsupported DDS texture format" + ); + } + + format = TextureFormat.B8G8R8A8; + } + else + { + throw new NotSupportedException( + "Unsupported DDS texture format" + ); + } + } + + public static int CalculateDDSLevelSize( + int width, + int height, + TextureFormat format + ) { + if (format == TextureFormat.R8G8B8A8) + { + return (((width * 32) + 7) / 8) * height; + } + else if (format == TextureFormat.R16G16B16A16_SFLOAT) + { + return (((width * 64) + 7) / 8) * height; + } + else if (format == TextureFormat.R32G32B32A32_SFLOAT) + { + return (((width * 128) + 7) / 8) * height; + } + else + { + int blockSize = 16; + if (format == TextureFormat.BC1) + { + blockSize = 8; + } + width = System.Math.Max(width, 1); + height = System.Math.Max(height, 1); + return ( + ((width + 3) / 4) * + ((height + 3) / 4) * + blockSize + ); + } + } } } diff --git a/src/Graphics/Resources/Texture.cs b/src/Graphics/Resources/Texture.cs index 6f5714a..7082c44 100644 --- a/src/Graphics/Resources/Texture.cs +++ b/src/Graphics/Resources/Texture.cs @@ -165,274 +165,6 @@ namespace MoonWorks.Graphics Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format); } - // DDS loading extension, based on MojoDDS - // Taken from https://github.com/FNA-XNA/FNA/blob/1e49f868f595f62bc6385db45949a03186a7cd7f/src/Graphics/Texture.cs#L194 - private static void ParseDDS( - BinaryReader reader, - out TextureFormat format, - out int width, - out int height, - out int levels, - out bool isCube - ) { - // A whole bunch of magic numbers, yay DDS! - const uint DDS_MAGIC = 0x20534444; - const uint DDS_HEADERSIZE = 124; - const uint DDS_PIXFMTSIZE = 32; - const uint DDSD_HEIGHT = 0x2; - const uint DDSD_WIDTH = 0x4; - const uint DDSD_PITCH = 0x8; - const uint DDSD_LINEARSIZE = 0x80000; - const uint DDSD_REQ = ( - DDSD_HEIGHT | DDSD_WIDTH - ); - const uint DDSCAPS_MIPMAP = 0x400000; - const uint DDSCAPS_TEXTURE = 0x1000; - const uint DDSCAPS2_CUBEMAP = 0x200; - const uint DDPF_FOURCC = 0x4; - const uint DDPF_RGB = 0x40; - const uint FOURCC_DXT1 = 0x31545844; - const uint FOURCC_DXT3 = 0x33545844; - const uint FOURCC_DXT5 = 0x35545844; - const uint FOURCC_DX10 = 0x30315844; - const uint pitchAndLinear = ( - DDSD_PITCH | DDSD_LINEARSIZE - ); - - // File should start with 'DDS ' - if (reader.ReadUInt32() != DDS_MAGIC) - { - throw new NotSupportedException("Not a DDS!"); - } - - // Texture info - uint size = reader.ReadUInt32(); - if (size != DDS_HEADERSIZE) - { - throw new NotSupportedException("Invalid DDS header!"); - } - uint flags = reader.ReadUInt32(); - if ((flags & DDSD_REQ) != DDSD_REQ) - { - throw new NotSupportedException("Invalid DDS flags!"); - } - if ((flags & pitchAndLinear) == pitchAndLinear) - { - throw new NotSupportedException("Invalid DDS flags!"); - } - height = reader.ReadInt32(); - width = reader.ReadInt32(); - reader.ReadUInt32(); // dwPitchOrLinearSize, unused - reader.ReadUInt32(); // dwDepth, unused - levels = reader.ReadInt32(); - - // "Reserved" - reader.ReadBytes(4 * 11); - - // Format info - uint formatSize = reader.ReadUInt32(); - if (formatSize != DDS_PIXFMTSIZE) - { - throw new NotSupportedException("Bogus PIXFMTSIZE!"); - } - uint formatFlags = reader.ReadUInt32(); - uint formatFourCC = reader.ReadUInt32(); - uint formatRGBBitCount = reader.ReadUInt32(); - uint formatRBitMask = reader.ReadUInt32(); - uint formatGBitMask = reader.ReadUInt32(); - uint formatBBitMask = reader.ReadUInt32(); - uint formatABitMask = reader.ReadUInt32(); - - // dwCaps "stuff" - uint caps = reader.ReadUInt32(); - if ((caps & DDSCAPS_TEXTURE) == 0) - { - throw new NotSupportedException("Not a texture!"); - } - - isCube = false; - - uint caps2 = reader.ReadUInt32(); - if (caps2 != 0) - { - if ((caps2 & DDSCAPS2_CUBEMAP) == DDSCAPS2_CUBEMAP) - { - isCube = true; - } - else - { - throw new NotSupportedException("Invalid caps2!"); - } - } - - reader.ReadUInt32(); // dwCaps3, unused - reader.ReadUInt32(); // dwCaps4, unused - - // "Reserved" - reader.ReadUInt32(); - - // Mipmap sanity check - if ((caps & DDSCAPS_MIPMAP) != DDSCAPS_MIPMAP) - { - levels = 1; - } - - // Determine texture format - if ((formatFlags & DDPF_FOURCC) == DDPF_FOURCC) - { - switch (formatFourCC) - { - case 0x71: // D3DFMT_A16B16G16R16F - format = TextureFormat.R16G16B16A16_SFLOAT; - break; - case 0x74: // D3DFMT_A32B32G32R32F - format = TextureFormat.R32G32B32A32_SFLOAT; - break; - case FOURCC_DXT1: - format = TextureFormat.BC1; - break; - case FOURCC_DXT3: - format = TextureFormat.BC2; - break; - case FOURCC_DXT5: - format = TextureFormat.BC3; - break; - case FOURCC_DX10: - // If the fourCC is DX10, there is an extra header with additional format information. - uint dxgiFormat = reader.ReadUInt32(); - - // These values are taken from the DXGI_FORMAT enum. - switch (dxgiFormat) - { - case 2: - format = TextureFormat.R32G32B32A32_SFLOAT; - break; - - case 10: - format = TextureFormat.R16G16B16A16_SFLOAT; - break; - - case 71: - format = TextureFormat.BC1; - break; - - case 74: - format = TextureFormat.BC2; - break; - - case 77: - format = TextureFormat.BC3; - break; - - case 98: - format = TextureFormat.BC7; - break; - - default: - throw new NotSupportedException( - "Unsupported DDS texture format" - ); - } - - uint resourceDimension = reader.ReadUInt32(); - - // These values are taken from the D3D10_RESOURCE_DIMENSION enum. - switch (resourceDimension) - { - case 0: // Unknown - case 1: // Buffer - throw new NotSupportedException( - "Unsupported DDS texture format" - ); - default: - break; - } - - /* - * This flag seemingly only indicates if the texture is a cube map. - * This is already determined above. Cool! - */ - uint miscFlag = reader.ReadUInt32(); - - /* - * Indicates the number of elements in the texture array. - * We don't support texture arrays so just throw if it's greater than 1. - */ - uint arraySize = reader.ReadUInt32(); - - if (arraySize > 1) - { - throw new NotSupportedException( - "Unsupported DDS texture format" - ); - } - - reader.ReadUInt32(); // reserved - - break; - default: - throw new NotSupportedException( - "Unsupported DDS texture format" - ); - } - } - else if ((formatFlags & DDPF_RGB) == DDPF_RGB) - { - if ( formatRGBBitCount != 32 || - formatRBitMask != 0x00FF0000 || - formatGBitMask != 0x0000FF00 || - formatBBitMask != 0x000000FF || - formatABitMask != 0xFF000000 ) - { - throw new NotSupportedException( - "Unsupported DDS texture format" - ); - } - - format = TextureFormat.B8G8R8A8; - } - else - { - throw new NotSupportedException( - "Unsupported DDS texture format" - ); - } - } - - private static int CalculateDDSLevelSize( - int width, - int height, - TextureFormat format - ) { - if (format == TextureFormat.R8G8B8A8) - { - return (((width * 32) + 7) / 8) * height; - } - else if (format == TextureFormat.R16G16B16A16_SFLOAT) - { - return (((width * 64) + 7) / 8) * height; - } - else if (format == TextureFormat.R32G32B32A32_SFLOAT) - { - return (((width * 128) + 7) / 8) * height; - } - else - { - int blockSize = 16; - if (format == TextureFormat.BC1) - { - blockSize = 8; - } - width = System.Math.Max(width, 1); - height = System.Math.Max(height, 1); - return ( - ((width + 3) / 4) * - ((height + 3) / 4) * - blockSize - ); - } - } - public static uint BytesPerPixel(TextureFormat format) { switch (format) @@ -481,6 +213,21 @@ namespace MoonWorks.Graphics } } + public static uint TexelSize(TextureFormat format) + { + switch (format) + { + case TextureFormat.BC2: + case TextureFormat.BC3: + case TextureFormat.BC7: + return 16; + case TextureFormat.BC1: + return 8; + default: + return 1; + } + } + public static uint BlockSizeSquared(TextureFormat format) { switch (format)