diff --git a/lib/RefreshCS b/lib/RefreshCS index 610698fc..98c590ae 160000 --- a/lib/RefreshCS +++ b/lib/RefreshCS @@ -1 +1 @@ -Subproject commit 610698fcd63f4c135fc0f60f38ccc8707ce568eb +Subproject commit 98c590ae77c3b6a64a370bac439f20728959a8b6 diff --git a/src/Graphics/RefreshEnums.cs b/src/Graphics/RefreshEnums.cs index 03fd4bc7..12d70c8a 100644 --- a/src/Graphics/RefreshEnums.cs +++ b/src/Graphics/RefreshEnums.cs @@ -67,6 +67,7 @@ namespace MoonWorks.Graphics BC1, BC2, BC3, + BC7, R8G8_SNORM, R8G8B8A8_SNORM, A2R10G10B10, diff --git a/src/Graphics/Resources/Texture.cs b/src/Graphics/Resources/Texture.cs index e604a787..86d5f07d 100644 --- a/src/Graphics/Resources/Texture.cs +++ b/src/Graphics/Resources/Texture.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using RefreshCS; namespace MoonWorks.Graphics @@ -56,6 +57,9 @@ namespace MoonWorks.Graphics return texture; } + /// + /// Saves RGBA or BGRA pixel data to a file in PNG format. + /// public unsafe static void SavePNG(string path, int width, int height, TextureFormat format, byte[] pixels) { if (format != TextureFormat.R8G8B8A8 && format != TextureFormat.B8G8R8A8) @@ -69,6 +73,34 @@ namespace MoonWorks.Graphics } } + public static Texture LoadDDS(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, System.IO.Stream stream) + { + using (var reader = new BinaryReader(stream)) + { + ParseDDS(reader, out var format, out var width, out var height, out var levels); + Texture texture = CreateTexture2D(graphicsDevice, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, SampleCount.One, (uint) levels); + + for (int i = 0; i < levels; i += 1) + { + var levelWidth = width >> i; + var levelHeight = height >> i; + + var pixels = reader.ReadBytes( + Texture.CalculateDDSLevelSize( + levelWidth, + levelHeight, + format + ) + ); + + var textureSlice = new TextureSlice(texture, new Rect(0, 0, levelWidth, levelHeight), 0, 0, (uint) i); + commandBuffer.SetTextureData(textureSlice, pixels); + } + + return texture; + } + } + /// /// Creates a 2D texture. /// @@ -222,5 +254,202 @@ namespace MoonWorks.Graphics LevelCount = 1; UsageFlags = TextureUsageFlags.ColorTarget; } + + // 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 + ) { + // 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_CAPS = 0x1; + const uint DDSD_HEIGHT = 0x2; + const uint DDSD_WIDTH = 0x4; + const uint DDSD_PITCH = 0x8; + const uint DDSD_FMT = 0x1000; + const uint DDSD_LINEARSIZE = 0x80000; + const uint DDSD_REQ = ( + DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_FMT + ); + 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_BPTC = 0x30315844; + // 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!"); + } + uint caps2 = reader.ReadUInt32(); + if ( caps2 != 0 && + (caps2 & DDSCAPS2_CUBEMAP) != DDSCAPS2_CUBEMAP ) + { + 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: + format = TextureFormat.R16G16B16A16_SFLOAT; + break; + case 0x74: + 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_BPTC: + format = TextureFormat.BC7; + // These next 5 uints are part of the DX10 DDS header. + // They contain a little extra information but aren't that important. + uint dxgiFormat = reader.ReadUInt32(); + uint resourceDimension = reader.ReadUInt32(); + uint miscFlag = reader.ReadUInt32(); + uint arraySize = reader.ReadUInt32(); + 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 + ); + } + } } }