/// <summary> /// Gets the image row pitch (in bytes) and the slice pitch (size of the image in bytes) based /// on format, width, height and additional options. /// </summary> /// <param name="format">The texture format.</param> /// <param name="width">The image width in pixels.</param> /// <param name="height">The image height in pixels.</param> /// <param name="rowPitch">The row pitch in bytes.</param> /// <param name="slicePitch">The image size in bytes.</param> /// <param name="flags">Additional options.</param> public static void ComputePitch(DataFormat format, int width, int height, out int rowPitch, out int slicePitch, ComputePitchFlags flags) { switch (format) { case DataFormat.BC1_TYPELESS: case DataFormat.BC1_UNORM: case DataFormat.BC1_UNORM_SRGB: case DataFormat.BC4_TYPELESS: case DataFormat.BC4_UNORM: case DataFormat.BC4_SNORM: { Debug.Assert(IsBCn(format)); // Width and height in blocks. int nbw = Math.Max(1, (width + 3) / 4); int nbh = Math.Max(1, (height + 3) / 4); rowPitch = nbw * 8; slicePitch = rowPitch * nbh; } break; case DataFormat.BC2_TYPELESS: case DataFormat.BC2_UNORM: case DataFormat.BC2_UNORM_SRGB: case DataFormat.BC3_TYPELESS: case DataFormat.BC3_UNORM: case DataFormat.BC3_UNORM_SRGB: case DataFormat.BC5_TYPELESS: case DataFormat.BC5_UNORM: case DataFormat.BC5_SNORM: case DataFormat.BC6H_TYPELESS: case DataFormat.BC6H_UF16: case DataFormat.BC6H_SF16: case DataFormat.BC7_TYPELESS: case DataFormat.BC7_UNORM: case DataFormat.BC7_UNORM_SRGB: { Debug.Assert(IsBCn(format)); // Width and height in blocks. int nbw = Math.Max(1, (width + 3) / 4); int nbh = Math.Max(1, (height + 3) / 4); rowPitch = nbw * 16; slicePitch = rowPitch * nbh; } break; case DataFormat.PVRTCI_2bpp_RGB: case DataFormat.PVRTCI_2bpp_RGBA: { Debug.Assert(IsPvrtc(format)); // Bytes per block. int bpb = 8; // Additional limitations set by Apple: // (Reference https://developer.apple.com/library/ios/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/TextureTool/TextureTool.html) // // - width and height must be power of two. // - width and height must be square. // - width and height must be at least 8. // (PVRTexLib returns the same slice pitch for size = 1, 2, 4 and 8!) // - Minimum data size is 32 bytes (= 2x2 blocks). // => Minimum size for 2 bpp mode: 16x8 pixels // Minimum size for 4 bpp mode: 8x8 pixels. // 2 bpp mode: block = 8x4 pixels width = Math.Max(16, width); height = Math.Max(8, height); // Width and height in blocks. int nbw = Math.Max(1, (width + 7) / 8); int nbh = Math.Max(1, (height + 3) / 4); rowPitch = nbw * bpb; slicePitch = rowPitch * nbh; } break; case DataFormat.PVRTCI_4bpp_RGB: case DataFormat.PVRTCI_4bpp_RGBA: { Debug.Assert(IsPvrtc(format)); // Bytes per block. int bpb = 8; // Additional limitations set by Apple: // (Reference https://developer.apple.com/library/ios/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/TextureTool/TextureTool.html) // // - width and height must be power of two. // - width and height must be square. // - width and height must be at least 8. // (PVRTexLib returns the same slice pitch for size = 1, 2, 4 and 8!) // - Minimum data size is 32 bytes (= 2x2 blocks). // => Minimum size for 2 bpp mode: 16x8 pixels // Minimum size for 4 bpp mode: 8x8 pixels. // 4 bpp mode: block = 4x4 pixels width = Math.Max(8, width); height = Math.Max(8, height); // Width and height in blocks. int nbw = Math.Max(1, (width + 3) / 4); int nbh = Math.Max(1, (height + 3) / 4); rowPitch = nbw * bpb; slicePitch = rowPitch * nbh; } break; case DataFormat.ETC1: { Debug.Assert(IsEtc(format)); // Reference: https://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt // Bytes per block. int bpb = 8; // Width and height in blocks. int nbw = Math.Max(1, (width + 3) / 4); int nbh = Math.Max(1, (height + 3) / 4); rowPitch = nbw * bpb; slicePitch = rowPitch * nbh; } break; case DataFormat.ATC_RGB: { Debug.Assert(IsAtc(format)); // Reference: https://www.khronos.org/registry/gles/extensions/AMD/AMD_compressed_ATC_texture.txt // Width and height in blocks. int nbw = Math.Max(1, (width + 3) / 4); int nbh = Math.Max(1, (height + 3) / 4); rowPitch = nbw * 8; slicePitch = rowPitch * nbh; } break; case DataFormat.ATC_RGBA_EXPLICIT_ALPHA: case DataFormat.ATC_RGBA_INTERPOLATED_ALPHA: { Debug.Assert(IsAtc(format)); // Reference: https://www.khronos.org/registry/gles/extensions/AMD/AMD_compressed_ATC_texture.txt // Width and height in blocks. int nbw = Math.Max(1, (width + 3) / 4); int nbh = Math.Max(1, (height + 3) / 4); rowPitch = nbw * 16; slicePitch = rowPitch * nbh; } break; case DataFormat.R8G8_B8G8_UNORM: case DataFormat.G8R8_G8B8_UNORM: case DataFormat.YUY2: { Debug.Assert(IsPacked(format)); rowPitch = ((width + 1) >> 1) * 4; slicePitch = rowPitch * height; } break; case DataFormat.Y210: // 4:2:2 10-bit case DataFormat.Y216: // 4:2:2 16-bit { Debug.Assert(IsPacked(format)); rowPitch = ((width + 1) >> 1) * 8; slicePitch = rowPitch * height; } break; case DataFormat.NV12: case DataFormat.Y420_OPAQUE: { Debug.Assert(IsPlanar(format)); rowPitch = ((width + 1) >> 1) * 2; slicePitch = rowPitch * (height + ((height + 1) >> 1)); } break; case DataFormat.P010: case DataFormat.P016: case DataFormat.D16_UNORM_S8_UINT: case DataFormat.R16_UNORM_X8_TYPELESS: case DataFormat.X16_TYPELESS_G8_UINT: { Debug.Assert(IsPlanar(format)); rowPitch = ((width + 1) >> 1) * 4; slicePitch = rowPitch * (height + ((height + 1) >> 1)); } break; case DataFormat.NV11: { Debug.Assert(IsPlanar(format)); rowPitch = ((width + 3) >> 2) * 4; // Direct3D makes this simplifying assumption, although it is larger than the 4:1:1 data. slicePitch = rowPitch * height * 2; } break; case DataFormat.P208: { Debug.Assert(IsPlanar(format)); rowPitch = ((width + 1) >> 1) * 2; slicePitch = rowPitch * height * 2; } break; case DataFormat.V208: { Debug.Assert(IsPlanar(format)); rowPitch = width; slicePitch = rowPitch * (height + (((height + 1) >> 1) * 2)); } break; case DataFormat.V408: { Debug.Assert(IsPlanar(format)); rowPitch = width; slicePitch = rowPitch * (height + ((height >> 1) * 4)); } break; default: { Debug.Assert(IsValid(format)); Debug.Assert(!IsCompressed(format) && !IsPacked(format) && !IsPlanar(format)); int bpp; if ((flags & ComputePitchFlags.Bpp24) != 0) bpp = 24; else if ((flags & ComputePitchFlags.Bpp16) != 0) bpp = 16; else if ((flags & ComputePitchFlags.Bpp8) != 0) bpp = 8; else bpp = BitsPerPixel(format); if ((flags & (ComputePitchFlags.LegacyDword | ComputePitchFlags.Paragraph | ComputePitchFlags.Ymm | ComputePitchFlags.Zmm | ComputePitchFlags.Page4K)) != 0) { if ((flags & ComputePitchFlags.Page4K) != 0) { rowPitch = ((width * bpp + 32767) / 32768) * 4096; slicePitch = rowPitch * height; } else if ((flags & ComputePitchFlags.Zmm) != 0) { rowPitch = ((width * bpp + 511) / 512) * 64; slicePitch = rowPitch * height; } else if ((flags & ComputePitchFlags.Ymm) != 0) { rowPitch = ((width * bpp + 255) / 256) * 32; slicePitch = rowPitch * height; } else if ((flags & ComputePitchFlags.Paragraph) != 0) { rowPitch = ((width * bpp + 127) / 128) * 16; slicePitch = rowPitch * height; } else // DWORD alignment { // Special computation for some incorrectly created DDS files based on // legacy DirectDraw assumptions about pitch alignment rowPitch = ((width * bpp + 31) / 32) * Marshal.SizeOf(typeof(uint)); slicePitch = rowPitch * height; } } else { // Default byte alignment rowPitch = (width * bpp + 7) / 8; slicePitch = rowPitch * height; } } break; } }
private static Texture CopyImage(BinaryReader reader, TextureDescription description, ComputePitchFlags cpFlags, ConversionFlags convFlags, uint[] pal8) { if (reader == null) throw new ArgumentNullException("reader"); if ((convFlags & ConversionFlags.Expand) != 0) { if ((convFlags & ConversionFlags.Format888) != 0) cpFlags |= ComputePitchFlags.Bpp24; else if ((convFlags & (ConversionFlags.Format565 | ConversionFlags.Format5551 | ConversionFlags.Format4444 | ConversionFlags.Format8332 | ConversionFlags.FormatA8P8 | ConversionFlags.FormatL16 | ConversionFlags.FormatA8L8)) != 0) cpFlags |= ComputePitchFlags.Bpp16; else if ((convFlags & (ConversionFlags.Format44 | ConversionFlags.Format332 | ConversionFlags.Pal8 | ConversionFlags.FormatL8)) != 0) cpFlags |= ComputePitchFlags.Bpp8; } var texture = new Texture(description); description = texture.Description; // MipLevel may have been set. ScanlineFlags tflags = (convFlags & ConversionFlags.NoAlpha) != 0 ? ScanlineFlags.SetAlpha : 0; if ((convFlags & ConversionFlags.Swizzle) != 0) tflags |= ScanlineFlags.Legacy; switch (description.Dimension) { case TextureDimension.Texture1D: case TextureDimension.Texture2D: case TextureDimension.TextureCube: { int index = 0; for (int item = 0; item < description.ArraySize; ++item) { int width = description.Width; int height = description.Height; for (int level = 0; level < description.MipLevels; ++level, ++index) { int sRowPitch, sSlicePitch; TextureHelper.ComputePitch(description.Format, width, height, out sRowPitch, out sSlicePitch, cpFlags); var image = texture.Images[index]; if (TextureHelper.IsBCn(description.Format) || TextureHelper.IsPlanar(description.Format)) { reader.Read(image.Data, 0, image.Data.Length); } else { using (var stream = new MemoryStream(image.Data)) using (var writer = new BinaryWriter(stream)) { for (int h = 0; h < height; ++h) { if ((convFlags & ConversionFlags.Expand) != 0) { if ((convFlags & (ConversionFlags.Format565 | ConversionFlags.Format5551 | ConversionFlags.Format4444)) != 0) { if (!TextureHelper.ExpandScanline(reader, sRowPitch, (convFlags & ConversionFlags.Format565) != 0 ? DataFormat.B5G6R5_UNORM : DataFormat.B5G5R5A1_UNORM, writer, image.RowPitch, DataFormat.R8G8B8A8_UNORM, tflags)) throw new InvalidDataException("Unable to expand format."); } else { LegacyFormat lformat = FindLegacyFormat(convFlags); if (!LegacyExpandScanline(reader, sRowPitch, lformat, writer, image.RowPitch, description.Format, pal8, tflags)) throw new InvalidDataException("Unable to expand legacy format."); } } else if ((convFlags & ConversionFlags.Swizzle) != 0) { TextureHelper.SwizzleScanline(reader, sRowPitch, writer, image.RowPitch, description.Format, tflags); } else { TextureHelper.CopyScanline(reader, sRowPitch, writer, image.RowPitch, description.Format, tflags); } } } } if (width > 1) width >>= 1; if (height > 1) height >>= 1; } } } break; case TextureDimension.Texture3D: { int index = 0; int width = description.Width; int height = description.Height; int depth = description.Depth; for (int level = 0; level < description.MipLevels; ++level) { int sRowPitch, sSlicePitch; TextureHelper.ComputePitch(description.Format, width, height, out sRowPitch, out sSlicePitch, cpFlags); for (int slice = 0; slice < depth; ++slice, ++index) { // We use the same memory organization that Direct3D 11 needs for D3D11_SUBRESOURCE_DATA // with all slices of a given miplevel being continuous in memory var image = texture.Images[index]; if (TextureHelper.IsBCn(description.Format)) { reader.Read(image.Data, 0, image.Data.Length); } else if (TextureHelper.IsPlanar(description.Format)) { // Direct3D does not support any planar formats for Texture3D throw new NotSupportedException("Planar texture formats are not support for volume textures."); } else { using (var stream = new MemoryStream(image.Data)) using (var writer = new BinaryWriter(stream)) { for (int h = 0; h < height; ++h) { if ((convFlags & ConversionFlags.Expand) != 0) { if ((convFlags & (ConversionFlags.Format565 | ConversionFlags.Format5551 | ConversionFlags.Format4444)) != 0) { if (!TextureHelper.ExpandScanline(reader, sRowPitch, (convFlags & ConversionFlags.Format565) != 0 ? DataFormat.B5G6R5_UNORM : DataFormat.B5G5R5A1_UNORM, writer, image.RowPitch, DataFormat.R8G8B8A8_UNORM, tflags)) throw new InvalidDataException("Unable to expand format."); } else { LegacyFormat lformat = FindLegacyFormat(convFlags); if (!LegacyExpandScanline(reader, sRowPitch, lformat, writer, image.RowPitch, description.Format, pal8, tflags)) throw new InvalidDataException("Unable to expand legacy format."); } } else if ((convFlags & ConversionFlags.Swizzle) != 0) { TextureHelper.SwizzleScanline(reader, sRowPitch, writer, image.RowPitch, description.Format, tflags); } else { TextureHelper.CopyScanline(reader, sRowPitch, writer, image.RowPitch, description.Format, tflags); } } } } } if (width > 1) width >>= 1; if (height > 1) height >>= 1; if (depth > 1) depth >>= 1; } } break; default: throw new NotSupportedException("The specified texture dimension is not supported."); } return texture; }