532 lines
20 KiB
C#
532 lines
20 KiB
C#
// MonoGame - Copyright (C) The MonoGame Team
|
|
// This file is subject to the terms and conditions defined in
|
|
// file 'LICENSE.txt', which is part of this source code package.
|
|
|
|
using System;
|
|
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using Microsoft.Xna.Framework.Graphics.PackedVector;
|
|
using System.IO;
|
|
|
|
namespace Microsoft.Xna.Framework.Content.Pipeline
|
|
{
|
|
/// <summary>
|
|
/// Loader class for DDS format image files.
|
|
/// </summary>
|
|
class DdsLoader
|
|
{
|
|
[Flags]
|
|
enum Ddsd : uint
|
|
{
|
|
Caps = 0x1, // Required in every DDS file
|
|
Height = 0x2, // Required in every DDS file
|
|
Width = 0x4, // Required in every DDS file
|
|
Pitch = 0x8, // Required when pitch is provided for an uncompressed texture
|
|
PixelFormat = 0x1000, // Required in every DDS file
|
|
MipMapCount = 0x2000, // Required in a mipmapped texture
|
|
LinearSize = 0x80000, // Required when pitch is provided for a compressed texture
|
|
Depth = 0x800000, // Required in a depth texture
|
|
}
|
|
|
|
[Flags]
|
|
enum DdsCaps : uint
|
|
{
|
|
Complex = 0x8, // Optional; must be used on any file that contains more than one surface (a mipmap, a cubic environment map, or mipmapped volume texture)
|
|
MipMap = 0x400000, // Optional; should be used for a mipmap
|
|
Texture = 0x1000, // Required
|
|
}
|
|
|
|
[Flags]
|
|
enum DdsCaps2 : uint
|
|
{
|
|
Cubemap = 0x200,
|
|
CubemapPositiveX = 0x400,
|
|
CubemapNegativeX = 0x800,
|
|
CubemapPositiveY = 0x1000,
|
|
CubemapNegativeY = 0x2000,
|
|
CubemapPositiveZ = 0x4000,
|
|
CubemapNegativeZ = 0x8000,
|
|
Volume = 0x200000,
|
|
|
|
CubemapAllFaces = Cubemap | CubemapPositiveX | CubemapNegativeX | CubemapPositiveY | CubemapNegativeY | CubemapPositiveZ | CubemapNegativeZ,
|
|
}
|
|
|
|
[Flags]
|
|
enum Ddpf : uint
|
|
{
|
|
AlphaPixels = 0x1,
|
|
Alpha = 0x2,
|
|
FourCC = 0x4,
|
|
Rgb = 0x40,
|
|
Yuv = 0x200,
|
|
Luminance = 0x20000,
|
|
}
|
|
|
|
static uint MakeFourCC(char c1, char c2, char c3, char c4)
|
|
{
|
|
return ((uint)c1 << 24) | ((uint)c2 << 16) | ((uint)c3 << 8) | (uint)c4;
|
|
}
|
|
|
|
static uint MakeFourCC(string cc)
|
|
{
|
|
return ((uint)cc[0] << 24) | ((uint)cc[1] << 16) | ((uint)cc[2] << 8) | (uint)cc[3];
|
|
}
|
|
|
|
enum FourCC : uint
|
|
{
|
|
A32B32G32R32F = 116,
|
|
Dxt1 = 0x31545844,
|
|
Dxt2 = 0x32545844,
|
|
Dxt3 = 0x33545844,
|
|
Dxt4 = 0x34545844,
|
|
Dxt5 = 0x35545844,
|
|
Dx10 = 0x30315844,
|
|
}
|
|
|
|
struct DdsPixelFormat
|
|
{
|
|
public uint dwSize;
|
|
public Ddpf dwFlags;
|
|
public FourCC dwFourCC;
|
|
public uint dwRgbBitCount;
|
|
public uint dwRBitMask;
|
|
public uint dwGBitMask;
|
|
public uint dwBBitMask;
|
|
public uint dwABitMask;
|
|
}
|
|
|
|
struct DdsHeader
|
|
{
|
|
public uint dwSize;
|
|
public Ddsd dwFlags;
|
|
public uint dwHeight;
|
|
public uint dwWidth;
|
|
public uint dwPitchOrLinearSize;
|
|
public uint dwDepth;
|
|
public uint dwMipMapCount;
|
|
public DdsPixelFormat ddspf;
|
|
public DdsCaps dwCaps;
|
|
public DdsCaps2 dwCaps2;
|
|
}
|
|
|
|
static SurfaceFormat GetSurfaceFormat(ref DdsPixelFormat pixelFormat, out bool rbSwap)
|
|
{
|
|
rbSwap = false;
|
|
if (pixelFormat.dwFlags.HasFlag(Ddpf.FourCC))
|
|
{
|
|
switch (pixelFormat.dwFourCC)
|
|
{
|
|
case FourCC.A32B32G32R32F:
|
|
return SurfaceFormat.Vector4;
|
|
case FourCC.Dxt1:
|
|
return SurfaceFormat.Dxt1;
|
|
case FourCC.Dxt2:
|
|
throw new ContentLoadException("Unsupported compression format DXT2");
|
|
case FourCC.Dxt3:
|
|
return SurfaceFormat.Dxt3;
|
|
case FourCC.Dxt4:
|
|
throw new ContentLoadException("Unsupported compression format DXT4");
|
|
case FourCC.Dxt5:
|
|
return SurfaceFormat.Dxt5;
|
|
}
|
|
}
|
|
else if (pixelFormat.dwFlags.HasFlag(Ddpf.Rgb))
|
|
{
|
|
// Uncompressed format
|
|
if (pixelFormat.dwFlags.HasFlag(Ddpf.AlphaPixels))
|
|
{
|
|
// Format contains RGB and A
|
|
if (pixelFormat.dwRgbBitCount == 16)
|
|
{
|
|
if (pixelFormat.dwABitMask == 0xF)
|
|
{
|
|
rbSwap = pixelFormat.dwBBitMask == 0xF0;
|
|
return SurfaceFormat.Bgra4444;
|
|
}
|
|
rbSwap = pixelFormat.dwBBitMask == 0x3E;
|
|
return SurfaceFormat.Bgra5551;
|
|
}
|
|
else if (pixelFormat.dwRgbBitCount == 32)
|
|
{
|
|
rbSwap = pixelFormat.dwBBitMask == 0xFF;
|
|
return SurfaceFormat.Color;
|
|
}
|
|
throw new ContentLoadException("Unsupported RGBA pixel format");
|
|
}
|
|
else
|
|
{
|
|
// Format contains RGB only
|
|
if (pixelFormat.dwRgbBitCount == 16)
|
|
{
|
|
rbSwap = pixelFormat.dwBBitMask == 0x1F;
|
|
return SurfaceFormat.Bgr565;
|
|
}
|
|
else if (pixelFormat.dwRgbBitCount == 24)
|
|
{
|
|
rbSwap = pixelFormat.dwBBitMask == 0xFF;
|
|
return SurfaceFormat.Color;
|
|
}
|
|
else if (pixelFormat.dwRgbBitCount == 32)
|
|
{
|
|
rbSwap = pixelFormat.dwBBitMask == 0xFF;
|
|
return SurfaceFormat.Color;
|
|
}
|
|
throw new ContentLoadException("Unsupported RGB pixel format");
|
|
}
|
|
}
|
|
//else if (pixelFormat.dwFlags.HasFlag(Ddpf.Luminance))
|
|
//{
|
|
// return SurfaceFormat.Alpha8;
|
|
//}
|
|
throw new ContentLoadException("Unsupported pixel format");
|
|
}
|
|
|
|
static BitmapContent CreateBitmapContent(SurfaceFormat format, int width, int height)
|
|
{
|
|
switch (format)
|
|
{
|
|
case SurfaceFormat.Color:
|
|
return new PixelBitmapContent<Color>(width, height);
|
|
|
|
case SurfaceFormat.Bgra4444:
|
|
return new PixelBitmapContent<Bgra4444>(width, height);
|
|
|
|
case SurfaceFormat.Bgra5551:
|
|
return new PixelBitmapContent<Bgra5551>(width, height);
|
|
|
|
case SurfaceFormat.Bgr565:
|
|
return new PixelBitmapContent<Bgr565>(width, height);
|
|
|
|
case SurfaceFormat.Dxt1:
|
|
return new Dxt1BitmapContent(width, height);
|
|
|
|
case SurfaceFormat.Dxt3:
|
|
return new Dxt3BitmapContent(width, height);
|
|
|
|
case SurfaceFormat.Dxt5:
|
|
return new Dxt5BitmapContent(width, height);
|
|
|
|
case SurfaceFormat.Vector4:
|
|
return new PixelBitmapContent<Vector4>(width, height);
|
|
}
|
|
throw new ContentLoadException("Unsupported SurfaceFormat " + format);
|
|
}
|
|
|
|
static int GetBitmapSize(SurfaceFormat format, int width, int height)
|
|
{
|
|
// It is recommended that the dwPitchOrLinearSize field is ignored and we calculate it ourselves
|
|
// https://msdn.microsoft.com/en-us/library/bb943991.aspx
|
|
int pitch = 0;
|
|
int rows = 0;
|
|
|
|
switch (format)
|
|
{
|
|
case SurfaceFormat.Color:
|
|
case SurfaceFormat.Bgra4444:
|
|
case SurfaceFormat.Bgra5551:
|
|
case SurfaceFormat.Bgr565:
|
|
case SurfaceFormat.Vector4:
|
|
pitch = width * format.GetSize();
|
|
rows = height;
|
|
break;
|
|
|
|
case SurfaceFormat.Dxt1:
|
|
case SurfaceFormat.Dxt3:
|
|
case SurfaceFormat.Dxt5:
|
|
pitch = ((width + 3) / 4) * format.GetSize();
|
|
rows = (height + 3) / 4;
|
|
break;
|
|
|
|
default:
|
|
throw new ContentLoadException("Unsupported SurfaceFormat " + format);
|
|
}
|
|
|
|
return pitch * rows;
|
|
}
|
|
|
|
static internal TextureContent Import(string filename, ContentImporterContext context)
|
|
{
|
|
var identity = new ContentIdentity(filename);
|
|
TextureContent output = null;
|
|
|
|
using (var reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
|
|
{
|
|
// Read signature ("DDS ")
|
|
var valid = reader.ReadByte() == 0x44;
|
|
valid = valid && reader.ReadByte() == 0x44;
|
|
valid = valid && reader.ReadByte() == 0x53;
|
|
valid = valid && reader.ReadByte() == 0x20;
|
|
if (!valid)
|
|
throw new ContentLoadException("Invalid file signature");
|
|
|
|
var header = new DdsHeader();
|
|
|
|
// Read DDS_HEADER
|
|
header.dwSize = reader.ReadUInt32();
|
|
if (header.dwSize != 124)
|
|
throw new ContentLoadException("Invalid DDS_HEADER dwSize value");
|
|
header.dwFlags = (Ddsd)reader.ReadUInt32();
|
|
header.dwHeight = reader.ReadUInt32();
|
|
header.dwWidth = reader.ReadUInt32();
|
|
header.dwPitchOrLinearSize = reader.ReadUInt32();
|
|
header.dwDepth = reader.ReadUInt32();
|
|
header.dwMipMapCount = reader.ReadUInt32();
|
|
// The next 11 DWORDs are reserved and unused
|
|
for (int i = 0; i < 11; ++i)
|
|
reader.ReadUInt32();
|
|
// Read DDS_PIXELFORMAT
|
|
header.ddspf.dwSize = reader.ReadUInt32();
|
|
if (header.ddspf.dwSize != 32)
|
|
throw new ContentLoadException("Invalid DDS_PIXELFORMAT dwSize value");
|
|
header.ddspf.dwFlags = (Ddpf)reader.ReadUInt32();
|
|
header.ddspf.dwFourCC = (FourCC)reader.ReadUInt32();
|
|
header.ddspf.dwRgbBitCount = reader.ReadUInt32();
|
|
header.ddspf.dwRBitMask = reader.ReadUInt32();
|
|
header.ddspf.dwGBitMask = reader.ReadUInt32();
|
|
header.ddspf.dwBBitMask = reader.ReadUInt32();
|
|
header.ddspf.dwABitMask = reader.ReadUInt32();
|
|
// Continue reading DDS_HEADER
|
|
header.dwCaps = (DdsCaps)reader.ReadUInt32();
|
|
header.dwCaps2 = (DdsCaps2)reader.ReadUInt32();
|
|
// dwCaps3 unused
|
|
reader.ReadUInt32();
|
|
// dwCaps4 unused
|
|
reader.ReadUInt32();
|
|
// dwReserved2 unused
|
|
reader.ReadUInt32();
|
|
|
|
// Check for the existence of the DDS_HEADER_DXT10 struct next
|
|
if (header.ddspf.dwFlags == Ddpf.FourCC && header.ddspf.dwFourCC == FourCC.Dx10)
|
|
{
|
|
throw new ContentLoadException("Unsupported DDS_HEADER_DXT10 struct found");
|
|
}
|
|
|
|
int faceCount = 1;
|
|
int mipMapCount = (int)(header.dwCaps.HasFlag(DdsCaps.MipMap) ? header.dwMipMapCount : 1);
|
|
if (header.dwCaps2.HasFlag(DdsCaps2.Cubemap))
|
|
{
|
|
if (!header.dwCaps2.HasFlag(DdsCaps2.CubemapAllFaces))
|
|
throw new ContentLoadException("Incomplete cubemap in DDS file");
|
|
faceCount = 6;
|
|
output = new TextureCubeContent() { Identity = identity };
|
|
}
|
|
else
|
|
{
|
|
output = new Texture2DContent() { Identity = identity };
|
|
}
|
|
|
|
bool rbSwap;
|
|
var format = GetSurfaceFormat(ref header.ddspf, out rbSwap);
|
|
|
|
for (int f = 0; f < faceCount; ++f)
|
|
{
|
|
var w = (int)header.dwWidth;
|
|
var h = (int)header.dwHeight;
|
|
var mipMaps = new MipmapChain();
|
|
for (int m = 0; m < mipMapCount; ++m)
|
|
{
|
|
var content = CreateBitmapContent(format, w, h);
|
|
var byteCount = GetBitmapSize(format, w, h);
|
|
// A 24-bit format is slightly different
|
|
if (header.ddspf.dwRgbBitCount == 24)
|
|
byteCount = 3 * w * h;
|
|
var bytes = reader.ReadBytes(byteCount);
|
|
if (rbSwap)
|
|
{
|
|
switch (format)
|
|
{
|
|
case SurfaceFormat.Bgr565:
|
|
ByteSwapBGR565(bytes);
|
|
break;
|
|
case SurfaceFormat.Bgra4444:
|
|
ByteSwapBGRA4444(bytes);
|
|
break;
|
|
case SurfaceFormat.Bgra5551:
|
|
ByteSwapBGRA5551(bytes);
|
|
break;
|
|
case SurfaceFormat.Color:
|
|
if (header.ddspf.dwRgbBitCount == 32)
|
|
ByteSwapRGBX(bytes);
|
|
else if (header.ddspf.dwRgbBitCount == 24)
|
|
ByteSwapRGB(bytes);
|
|
break;
|
|
}
|
|
}
|
|
if ((format == SurfaceFormat.Color) && header.ddspf.dwFlags.HasFlag(Ddpf.Rgb) && !header.ddspf.dwFlags.HasFlag(Ddpf.AlphaPixels))
|
|
{
|
|
// Fill or add alpha with opaque
|
|
if (header.ddspf.dwRgbBitCount == 32)
|
|
ByteFillAlpha(bytes);
|
|
else if (header.ddspf.dwRgbBitCount == 24)
|
|
ByteExpandAlpha(ref bytes);
|
|
}
|
|
content.SetPixelData(bytes);
|
|
mipMaps.Add(content);
|
|
w = MathHelper.Max(1, w / 2);
|
|
h = MathHelper.Max(1, h / 2);
|
|
}
|
|
output.Faces[f] = mipMaps;
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
static void ByteFillAlpha(byte[] bytes)
|
|
{
|
|
for (int i = 0; i < bytes.Length; i += 4)
|
|
{
|
|
bytes[i + 3] = 255;
|
|
}
|
|
}
|
|
|
|
static void ByteExpandAlpha(ref byte[] bytes)
|
|
{
|
|
var rgba = new byte[bytes.Length + (bytes.Length / 3)];
|
|
int j = 0;
|
|
for (int i = 0; i < bytes.Length; i += 3)
|
|
{
|
|
rgba[j] = bytes[i];
|
|
rgba[j + 1] = bytes[i + 1];
|
|
rgba[j + 2] = bytes[i + 2];
|
|
rgba[j + 3] = 255;
|
|
j += 4;
|
|
}
|
|
bytes = rgba;
|
|
}
|
|
|
|
static void ByteSwapRGB(byte[] bytes)
|
|
{
|
|
for (int i = 0; i < bytes.Length; i += 3)
|
|
{
|
|
byte r = bytes[i];
|
|
bytes[i] = bytes[i + 2];
|
|
bytes[i + 2] = r;
|
|
}
|
|
}
|
|
|
|
static void ByteSwapRGBX(byte[] bytes)
|
|
{
|
|
for (int i = 0; i < bytes.Length; i += 4)
|
|
{
|
|
byte r = bytes[i];
|
|
bytes[i] = bytes[i + 2];
|
|
bytes[i + 2] = r;
|
|
}
|
|
}
|
|
|
|
static void ByteSwapBGRA4444(byte[] bytes)
|
|
{
|
|
for (int i = 0; i < bytes.Length; i += 2)
|
|
{
|
|
var r = bytes[i] & 0xF0;
|
|
var b = bytes[i + 1] & 0xF0;
|
|
bytes[i] = (byte)((bytes[i] & 0x0F) | b);
|
|
bytes[i + 1] = (byte)((bytes[i + 1] & 0x0F) | r);
|
|
}
|
|
}
|
|
|
|
static void ByteSwapBGRA5551(byte[] bytes)
|
|
{
|
|
for (int i = 0; i < bytes.Length; i += 2)
|
|
{
|
|
var r = (bytes[i] & 0xF8) >> 3;
|
|
var b = (bytes[i + 1] & 0x3E) >> 1;
|
|
bytes[i] = (byte)((bytes[i] & 0x07) | (b << 3));
|
|
bytes[i + 1] = (byte)((bytes[i + 1] & 0xC1) | (r << 1));
|
|
}
|
|
}
|
|
|
|
static void ByteSwapBGR565(byte[] bytes)
|
|
{
|
|
for (int i = 0; i < bytes.Length; i += 2)
|
|
{
|
|
var r = (bytes[i] & 0xF8) >> 3;
|
|
var b = bytes[i + 1] & 0x1F;
|
|
bytes[i] = (byte)((bytes[i] & 0x07) | (b << 3));
|
|
bytes[i + 1] = (byte)((bytes[i + 1] & 0xE0) | r);
|
|
}
|
|
}
|
|
|
|
internal static void WriteUncompressed(string filename, BitmapContent bitmapContent)
|
|
{
|
|
using (var writer = new BinaryWriter(new FileStream(filename, FileMode.Create, FileAccess.Write)))
|
|
{
|
|
// Write signature ("DDS ")
|
|
writer.Write((byte)0x44);
|
|
writer.Write((byte)0x44);
|
|
writer.Write((byte)0x53);
|
|
writer.Write((byte)0x20);
|
|
|
|
var header = new DdsHeader();
|
|
header.dwSize = 124;
|
|
header.dwFlags = Ddsd.Caps | Ddsd.Width | Ddsd.Height | Ddsd.Pitch | Ddsd.PixelFormat;
|
|
header.dwWidth = (uint)bitmapContent.Width;
|
|
header.dwHeight = (uint)bitmapContent.Height;
|
|
header.dwPitchOrLinearSize = (uint)(bitmapContent.Width * 4);
|
|
header.dwDepth = (uint)0;
|
|
header.dwMipMapCount = (uint)0;
|
|
|
|
writer.Write((uint)header.dwSize);
|
|
writer.Write((uint)header.dwFlags);
|
|
writer.Write((uint)header.dwHeight);
|
|
writer.Write((uint)header.dwWidth);
|
|
writer.Write((uint)header.dwPitchOrLinearSize);
|
|
writer.Write((uint)header.dwDepth);
|
|
writer.Write((uint)header.dwMipMapCount);
|
|
|
|
// 11 unsed and reserved DWORDS.
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
|
|
SurfaceFormat format;
|
|
if (!bitmapContent.TryGetFormat(out format) || format != SurfaceFormat.Color)
|
|
throw new NotSupportedException("Unsupported bitmap content!");
|
|
|
|
header.ddspf.dwSize = 32;
|
|
header.ddspf.dwFlags = Ddpf.AlphaPixels | Ddpf.Rgb;
|
|
header.ddspf.dwFourCC = 0;
|
|
header.ddspf.dwRgbBitCount = 32;
|
|
header.ddspf.dwRBitMask = 0x000000ff;
|
|
header.ddspf.dwGBitMask = 0x0000ff00;
|
|
header.ddspf.dwBBitMask = 0x00ff0000;
|
|
header.ddspf.dwABitMask = 0xff000000;
|
|
|
|
// Write the DDS_PIXELFORMAT
|
|
writer.Write((uint)header.ddspf.dwSize);
|
|
writer.Write((uint)header.ddspf.dwFlags);
|
|
writer.Write((uint)header.ddspf.dwFourCC);
|
|
writer.Write((uint)header.ddspf.dwRgbBitCount);
|
|
writer.Write((uint)header.ddspf.dwRBitMask);
|
|
writer.Write((uint)header.ddspf.dwGBitMask);
|
|
writer.Write((uint)header.ddspf.dwBBitMask);
|
|
writer.Write((uint)header.ddspf.dwABitMask);
|
|
|
|
header.dwCaps = DdsCaps.Texture;
|
|
header.dwCaps2 = 0;
|
|
|
|
// Continue reading DDS_HEADER
|
|
writer.Write((uint)header.dwCaps);
|
|
writer.Write((uint)header.dwCaps2);
|
|
|
|
// More reserved unused DWORDs.
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
writer.Write((uint)0);
|
|
|
|
// Write out the face data.
|
|
writer.Write(bitmapContent.GetPixelData());
|
|
}
|
|
}
|
|
}
|
|
}
|