private static bool WriteHeader(Stream output, StreamTransferBuffer buffer, TextureDimension texDim, DXGIFormat format, int width, int height, int depth, int arrayCount, int mipCount, DDSFlags flags) { //Force the DX10 header... bool writeDX10Header = (flags & DDSFlags.ForceExtendedHeader) == DDSFlags.ForceExtendedHeader; //Or do DX10 if the following is true...1D textures or 2D texture arrays that aren't cubemaps... if (!writeDX10Header) { switch (texDim) { case TextureDimension.One: writeDX10Header = true; break; case TextureDimension.Two: writeDX10Header = arrayCount > 1; break; } } //Figure out pixel format, if not writing DX10 header... PixelFormat pixelFormat; if (!writeDX10Header) { switch (format) { case DXGIFormat.R8G8B8A8_UNorm: pixelFormat = PixelFormat.A8B8G8R8; break; case DXGIFormat.R16G16_UNorm: pixelFormat = PixelFormat.G16R16; break; case DXGIFormat.R8G8_UNorm: pixelFormat = PixelFormat.A8L8; break; case DXGIFormat.R16_UNorm: pixelFormat = PixelFormat.L16; break; case DXGIFormat.R8_UNorm: pixelFormat = PixelFormat.L8; break; case DXGIFormat.A8_UNorm: pixelFormat = PixelFormat.A8; break; case DXGIFormat.R8G8_B8G8_UNorm: pixelFormat = PixelFormat.R8G8_B8G8; break; case DXGIFormat.G8R8_G8B8_UNorm: pixelFormat = PixelFormat.G8R8_G8B8; break; case DXGIFormat.BC1_UNorm: pixelFormat = PixelFormat.DXT1; break; case DXGIFormat.BC2_UNorm: pixelFormat = PixelFormat.DXT3; break; case DXGIFormat.BC3_UNorm: pixelFormat = PixelFormat.DXT5; break; case DXGIFormat.BC4_UNorm: pixelFormat = PixelFormat.BC4_UNorm; break; case DXGIFormat.BC4_SNorm: pixelFormat = PixelFormat.BC4_SNorm; break; case DXGIFormat.BC5_UNorm: pixelFormat = PixelFormat.BC5_UNorm; break; case DXGIFormat.BC5_SNorm: pixelFormat = PixelFormat.BC5_SNorm; break; case DXGIFormat.B5G6R5_UNorm: pixelFormat = PixelFormat.R5G6B5; break; case DXGIFormat.B5G5R5A1_UNorm: pixelFormat = PixelFormat.A1R5G5B5; break; case DXGIFormat.B8G8R8A8_UNorm: pixelFormat = PixelFormat.A8R8G8B8; break; case DXGIFormat.B8G8R8X8_UNorm: pixelFormat = PixelFormat.X8R8G8B8; break; case DXGIFormat.B4G4R4A4_UNorm: pixelFormat = PixelFormat.A4R4G4B4; break; case DXGIFormat.R32G32B32A32_Float: pixelFormat = PixelFormat.R32G32B32A32_Float; break; case DXGIFormat.R16G16B16A16_Float: pixelFormat = PixelFormat.R16G16B16A16_Float; break; case DXGIFormat.R16G16B16A16_UNorm: pixelFormat = PixelFormat.R16G16B16A16_UNorm; break; case DXGIFormat.R16G16B16A16_SNorm: pixelFormat = PixelFormat.R16G16B16A16_SNorm; break; case DXGIFormat.R32G32_Float: pixelFormat = PixelFormat.R32G32_Float; break; case DXGIFormat.R16G16_Float: pixelFormat = PixelFormat.R16G16_Float; break; case DXGIFormat.R32_Float: pixelFormat = PixelFormat.R32_Float; break; case DXGIFormat.R16_Float: pixelFormat = PixelFormat.R16_Float; break; default: pixelFormat = PixelFormat.DX10Extended; writeDX10Header = true; break; } } else { pixelFormat = PixelFormat.DX10Extended; } Header header = new Header(); header.Size = (uint)MemoryHelper.SizeOf <Header>(); header.PixelFormat = pixelFormat; header.Flags = HeaderFlags.Caps | HeaderFlags.Width | HeaderFlags.Height | HeaderFlags.PixelFormat; header.Caps = HeaderCaps.Texture; Header10?header10 = null; if (mipCount > 0) { header.Flags |= HeaderFlags.MipMapCount; header.MipMapCount = (uint)mipCount; header.Caps |= HeaderCaps.MipMap; } switch (texDim) { case TextureDimension.One: header.Width = (uint)width; header.Height = 1; header.Depth = 1; //Should always be writing out extended header for 1D textures System.Diagnostics.Debug.Assert(writeDX10Header); header10 = new Header10(format, D3D10ResourceDimension.Texture1D, Header10Flags.None, (uint)arrayCount, Header10Flags2.None); break; case TextureDimension.Two: header.Width = (uint)width; header.Height = (uint)height; header.Depth = 1; if (writeDX10Header) { header10 = new Header10(format, D3D10ResourceDimension.Texture2D, Header10Flags.None, (uint)arrayCount, Header10Flags2.None); } break; case TextureDimension.Cube: header.Width = (uint)width; header.Height = (uint)height; header.Depth = 1; header.Caps |= HeaderCaps.Complex; header.Caps2 |= HeaderCaps2.Cubemap_AllFaces; //can support array tex cubes, so must be multiples of 6 if (arrayCount % 6 != 0) { return(false); } if (writeDX10Header) { header10 = new Header10(format, D3D10ResourceDimension.Texture2D, Header10Flags.TextureCube, (uint)arrayCount / 6, Header10Flags2.None); } break; case TextureDimension.Three: header.Width = (uint)width; header.Height = (uint)height; header.Depth = (uint)depth; header.Flags |= HeaderFlags.Depth; header.Caps2 |= HeaderCaps2.Volume; if (arrayCount != 1) { return(false); } if (writeDX10Header) { header10 = new Header10(format, D3D10ResourceDimension.Texture3D, Header10Flags.None, 1, Header10Flags2.None); } break; } int realWidth, realHeight, rowPitch, slicePitch; ImageHelper.ComputePitch(format, width, height, out rowPitch, out slicePitch, out realWidth, out realHeight); if (FormatConverter.IsCompressed(format)) { header.Flags |= HeaderFlags.LinearSize; header.PitchOrLinearSize = (uint)slicePitch; } else { header.Flags |= HeaderFlags.Pitch; header.PitchOrLinearSize = (uint)rowPitch; } //Write out magic word, DDS header, and optionally extended header buffer.Write <FourCC>(output, DDS_MAGIC); buffer.Write <Header>(output, header); if (header10.HasValue) { System.Diagnostics.Debug.Assert(header.PixelFormat.IsDX10Extended); buffer.Write <Header10>(output, header10.Value); } return(true); }
/// <summary> /// Reads DDS formatted data from a stream. Image data is always returned as DXGI-Compliant /// format, therefore some old legacy formats will automatically be converted. /// </summary> /// <param name="input">Input stream.</param> /// <param name="flags">Flags to control how the DDS data is loaded.</param> /// <returns>Loaded image data, or null if the data failed to load.</returns> public static DDSContainer Read(Stream input, DDSFlags flags = DDSFlags.None) { StreamTransferBuffer buffer = new StreamTransferBuffer(); Header header; Header10?headerExt; //Reads + validates header(s) if (!ReadHeader(input, buffer, out header, out headerExt)) { return(null); } //Gather up metadata List <MipChain> mipChains = null; DXGIFormat format = DXGIFormat.Unknown; TextureDimension texDim = TextureDimension.Two; ConversionFlags convFlags = ConversionFlags.None; bool legacyDword = (flags & DDSFlags.LegacyDword) == DDSFlags.LegacyDword ? true : false; int width = Math.Max((int)header.Width, 1); int height = Math.Max((int)header.Height, 1); int depth = Math.Max((int)header.Depth, 1); int mipCount = (int)header.MipMapCount; int arrayCount = 1; //Has extended header, a modern DDS if (headerExt.HasValue) { Header10 extendedHeader = headerExt.Value; arrayCount = (int)extendedHeader.ArraySize; format = extendedHeader.Format; switch (extendedHeader.ResourceDimension) { case D3D10ResourceDimension.Texture1D: { texDim = TextureDimension.One; if (height > 1 || depth > 1) { return(null); } } break; case D3D10ResourceDimension.Texture2D: { if ((extendedHeader.MiscFlags & Header10Flags.TextureCube) == Header10Flags.TextureCube) { //Specifies # of cubemaps, so to get total # of faces must multiple by 6 arrayCount *= 6; texDim = TextureDimension.Cube; } else { texDim = TextureDimension.Two; } if (depth > 1) { return(null); } } break; case D3D10ResourceDimension.Texture3D: { texDim = TextureDimension.Three; if (arrayCount > 1 || (header.Caps2 & HeaderCaps2.Volume) != HeaderCaps2.Volume) { return(null); } } break; } } else { //Otherwise, read legacy DDS and possibly convert data //Check volume flag if ((header.Caps2 & HeaderCaps2.Volume) == HeaderCaps2.Volume) { texDim = TextureDimension.Three; } else { //legacy DDS could not express 1D textures, so either a cubemap or a 2D non-array texture if ((header.Caps2 & HeaderCaps2.Cubemap) == HeaderCaps2.Cubemap) { //Must have all six faces. DirectX 8 and above always would write out all 6 faces if ((header.Caps2 & HeaderCaps2.Cubemap_AllFaces) != HeaderCaps2.Cubemap_AllFaces) { return(null); } arrayCount = 6; texDim = TextureDimension.Cube; } else { texDim = TextureDimension.Two; } } format = FormatConverter.DetermineDXGIFormat(header.PixelFormat, flags, out convFlags); } //Modify conversion flags, if necessary FormatConverter.ModifyConversionFormat(ref format, ref convFlags, flags); //If palette image, the palette will be the first thing int[] palette = null; if (FormatConverter.HasConversionFlag(convFlags, ConversionFlags.Pal8)) { palette = new int[256]; int palSize = palette.Length * sizeof(int); buffer.ReadBytes(input, palSize); if (buffer.LastReadByteCount != palSize) { return(null); } MemoryHelper.CopyBytes <int>(buffer.ByteArray, 0, palette, 0, palette.Length); } //Now read data based on available mip/arrays mipChains = new List <MipChain>(arrayCount); byte[] scanline = buffer.ByteArray; IntPtr scanlinePtr = buffer.Pointer; bool noPadding = (flags & DDSFlags.NoPadding) == DDSFlags.NoPadding ? true : false; bool isCompressed = FormatConverter.IsCompressed(format); bool errored = false; try { //Iterate over each array face... for (int i = 0; i < arrayCount; i++) { MipChain mipChain = new MipChain(mipCount); mipChains.Add(mipChain); //Iterate over each mip face... for (int mipLevel = 0; mipLevel < mipCount; mipLevel++) { //Calculate mip dimensions int mipWidth = width; int mipHeight = height; int mipDepth = depth; ImageHelper.CalculateMipmapLevelDimensions(mipLevel, ref mipWidth, ref mipHeight, ref mipDepth); //Compute pitch, based on MSDN programming guide which says PitchOrLinearSize is unreliable and to calculate based on format. //"real" mip width/height is the given mip width/height for all non-compressed, compressed images it will be smaller since each block //is a 4x4 region of pixels. int realMipWidth, realMipHeight, dstRowPitch, dstSlicePitch, bytesPerPixel; ImageHelper.ComputePitch(format, mipWidth, mipHeight, out dstRowPitch, out dstSlicePitch, out realMipWidth, out realMipHeight, out bytesPerPixel, legacyDword); int srcRowPitch = dstRowPitch; int srcSlicePitch = dstSlicePitch; //Are we converting from a legacy format, possibly? if (!headerExt.HasValue) { int legacySize = FormatConverter.LegacyFormatBitsPerPixelFromConversionFlag(convFlags); if (legacySize != 0) { srcRowPitch = (realMipWidth * legacySize + 7) / 8; srcSlicePitch = srcRowPitch * realMipHeight; } } //If output data is requested not to have padding, recompute destination pitches if (noPadding) { dstRowPitch = bytesPerPixel * realMipWidth; dstSlicePitch = dstRowPitch * realMipHeight; } //Setup memory to hold the loaded image MipData mipSurface = new MipData(mipWidth, mipHeight, mipDepth, dstRowPitch, dstSlicePitch); mipChain.Add(mipSurface); //Ensure read buffer is sufficiently sized for a single scanline if (buffer.Length < srcRowPitch) { buffer.Resize(srcRowPitch, false); } IntPtr dstPtr = mipSurface.Data; //Advance stream one slice at a time... for (int slice = 0; slice < mipDepth; slice++) { long slicePos = input.Position; IntPtr dPtr = dstPtr; //Copy scanline into temp buffer, do any conversions, copy to output for (int row = 0; row < realMipHeight; row++) { int numBytesRead = input.Read(scanline, 0, srcRowPitch); if (numBytesRead != srcRowPitch) { errored = true; System.Diagnostics.Debug.Assert(false); return(null); } //Copy scanline, optionally convert data FormatConverter.CopyScanline(dPtr, dstRowPitch, scanlinePtr, srcRowPitch, format, convFlags, palette); //Increment dest pointer to next row dPtr = MemoryHelper.AddIntPtr(dPtr, dstRowPitch); } //Advance stream and destination pointer to the next slice input.Position = slicePos + srcSlicePitch; dstPtr = MemoryHelper.AddIntPtr(dstPtr, dstSlicePitch); } } } } finally { //If errored, clean up any mip surfaces we allocated...no null entries should have been made either if (errored) { DisposeMipChains(mipChains); } } if (!ValidateInternal(mipChains, format, texDim)) { System.Diagnostics.Debug.Assert(false); return(null); } return(new DDSContainer(mipChains, format, texDim)); }