|
|
#region License
|
|
|
/* FNA - XNA4 Reimplementation for Desktop Platforms
|
|
|
* Copyright 2009-2022 Ethan Lee and the MonoGame Team
|
|
|
*
|
|
|
* Released under the Microsoft Public License.
|
|
|
* See LICENSE for details.
|
|
|
*/
|
|
|
#endregion
|
|
|
|
|
|
#region Using Statements
|
|
|
using System;
|
|
|
using System.IO;
|
|
|
#endregion
|
|
|
|
|
|
namespace Microsoft.Xna.Framework.Graphics
|
|
|
{
|
|
|
public abstract class Texture : GraphicsResource
|
|
|
{
|
|
|
#region Public Properties
|
|
|
|
|
|
public SurfaceFormat Format
|
|
|
{
|
|
|
get;
|
|
|
protected set;
|
|
|
}
|
|
|
|
|
|
public int LevelCount
|
|
|
{
|
|
|
get;
|
|
|
protected set;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Internal FNA3D Variables
|
|
|
|
|
|
internal IntPtr texture;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Protected Dispose Method
|
|
|
|
|
|
protected override void Dispose(bool disposing)
|
|
|
{
|
|
|
if (!IsDisposed)
|
|
|
{
|
|
|
GraphicsDevice.Textures.RemoveDisposedTexture(this);
|
|
|
GraphicsDevice.VertexTextures.RemoveDisposedTexture(this);
|
|
|
FNA3D.FNA3D_AddDisposeTexture(
|
|
|
GraphicsDevice.GLDevice,
|
|
|
texture
|
|
|
);
|
|
|
}
|
|
|
base.Dispose(disposing);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Internal Context Reset Method
|
|
|
|
|
|
internal protected override void GraphicsDeviceResetting()
|
|
|
{
|
|
|
// FIXME: Do we even want to bother with DeviceResetting for GL? -flibit
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Static SurfaceFormat Size Methods
|
|
|
|
|
|
protected static int GetBlockSizeSquared(SurfaceFormat format)
|
|
|
{
|
|
|
switch (format)
|
|
|
{
|
|
|
case SurfaceFormat.Dxt1:
|
|
|
case SurfaceFormat.Dxt3:
|
|
|
case SurfaceFormat.Dxt5:
|
|
|
case SurfaceFormat.Dxt5SrgbEXT:
|
|
|
case SurfaceFormat.Bc7EXT:
|
|
|
case SurfaceFormat.Bc7SrgbEXT:
|
|
|
return 16;
|
|
|
case SurfaceFormat.Alpha8:
|
|
|
case SurfaceFormat.Bgr565:
|
|
|
case SurfaceFormat.Bgra4444:
|
|
|
case SurfaceFormat.Bgra5551:
|
|
|
case SurfaceFormat.HalfSingle:
|
|
|
case SurfaceFormat.NormalizedByte2:
|
|
|
case SurfaceFormat.Color:
|
|
|
case SurfaceFormat.Single:
|
|
|
case SurfaceFormat.Rg32:
|
|
|
case SurfaceFormat.HalfVector2:
|
|
|
case SurfaceFormat.NormalizedByte4:
|
|
|
case SurfaceFormat.Rgba1010102:
|
|
|
case SurfaceFormat.ColorBgraEXT:
|
|
|
case SurfaceFormat.ColorSrgbEXT:
|
|
|
case SurfaceFormat.HalfVector4:
|
|
|
case SurfaceFormat.Rgba64:
|
|
|
case SurfaceFormat.Vector2:
|
|
|
case SurfaceFormat.HdrBlendable:
|
|
|
case SurfaceFormat.Vector4:
|
|
|
return 1;
|
|
|
default:
|
|
|
throw new ArgumentException("Should be a value defined in SurfaceFormat", "Format");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
internal static int GetFormatSize(SurfaceFormat format)
|
|
|
{
|
|
|
switch (format)
|
|
|
{
|
|
|
case SurfaceFormat.Dxt1:
|
|
|
return 8;
|
|
|
case SurfaceFormat.Dxt3:
|
|
|
case SurfaceFormat.Dxt5:
|
|
|
case SurfaceFormat.Dxt5SrgbEXT:
|
|
|
case SurfaceFormat.Bc7EXT:
|
|
|
case SurfaceFormat.Bc7SrgbEXT:
|
|
|
return 16;
|
|
|
case SurfaceFormat.Alpha8:
|
|
|
return 1;
|
|
|
case SurfaceFormat.Bgr565:
|
|
|
case SurfaceFormat.Bgra4444:
|
|
|
case SurfaceFormat.Bgra5551:
|
|
|
case SurfaceFormat.HalfSingle:
|
|
|
case SurfaceFormat.NormalizedByte2:
|
|
|
return 2;
|
|
|
case SurfaceFormat.Color:
|
|
|
case SurfaceFormat.Single:
|
|
|
case SurfaceFormat.Rg32:
|
|
|
case SurfaceFormat.HalfVector2:
|
|
|
case SurfaceFormat.NormalizedByte4:
|
|
|
case SurfaceFormat.Rgba1010102:
|
|
|
case SurfaceFormat.ColorBgraEXT:
|
|
|
case SurfaceFormat.ColorSrgbEXT:
|
|
|
return 4;
|
|
|
case SurfaceFormat.HalfVector4:
|
|
|
case SurfaceFormat.Rgba64:
|
|
|
case SurfaceFormat.Vector2:
|
|
|
case SurfaceFormat.HdrBlendable:
|
|
|
return 8;
|
|
|
case SurfaceFormat.Vector4:
|
|
|
return 16;
|
|
|
default:
|
|
|
throw new ArgumentException("Should be a value defined in SurfaceFormat", "Format");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
internal static int GetPixelStoreAlignment(SurfaceFormat format)
|
|
|
{
|
|
|
/*
|
|
|
* https://github.com/FNA-XNA/FNA/pull/238
|
|
|
* https://www.khronos.org/registry/OpenGL/specs/gl/glspec21.pdf
|
|
|
* OpenGL 2.1 Specification, section 3.6.1, table 3.1 specifies that the pixelstorei alignment cannot exceed 8
|
|
|
*/
|
|
|
return Math.Min(8, GetFormatSize(format));
|
|
|
}
|
|
|
|
|
|
internal static void ValidateGetDataFormat(
|
|
|
SurfaceFormat format,
|
|
|
int elementSizeInBytes
|
|
|
) {
|
|
|
if (GetFormatSize(format) % elementSizeInBytes != 0)
|
|
|
{
|
|
|
throw new ArgumentException(
|
|
|
"The type you are using for T in this" +
|
|
|
" method is an invalid size for this" +
|
|
|
" resource"
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Static Mipmap Level Calculator
|
|
|
|
|
|
internal static int CalculateMipLevels(
|
|
|
int width,
|
|
|
int height = 0,
|
|
|
int depth = 0
|
|
|
) {
|
|
|
int levels = 1;
|
|
|
for (
|
|
|
int size = Math.Max(Math.Max(width, height), depth);
|
|
|
size > 1;
|
|
|
levels += 1
|
|
|
) {
|
|
|
size /= 2;
|
|
|
}
|
|
|
return levels;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Static DDS Parser
|
|
|
|
|
|
internal static int CalculateDDSLevelSize(
|
|
|
int width,
|
|
|
int height,
|
|
|
SurfaceFormat format
|
|
|
) {
|
|
|
if (format == SurfaceFormat.ColorBgraEXT)
|
|
|
{
|
|
|
return (((width * 32) + 7) / 8) * height;
|
|
|
}
|
|
|
else if (format == SurfaceFormat.HalfVector4)
|
|
|
{
|
|
|
return (((width * 64) + 7) / 8) * height;
|
|
|
}
|
|
|
else if (format == SurfaceFormat.Vector4)
|
|
|
{
|
|
|
return (((width * 128) + 7) / 8) * height;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
int blockSize = 16;
|
|
|
if (format == SurfaceFormat.Dxt1)
|
|
|
{
|
|
|
blockSize = 8;
|
|
|
}
|
|
|
width = Math.Max(width, 1);
|
|
|
height = Math.Max(height, 1);
|
|
|
return (
|
|
|
((width + 3) / 4) *
|
|
|
((height + 3) / 4) *
|
|
|
blockSize
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// DDS loading extension, based on MojoDDS
|
|
|
internal static void ParseDDS(
|
|
|
BinaryReader reader,
|
|
|
out SurfaceFormat 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_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_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 = SurfaceFormat.HalfVector4;
|
|
|
break;
|
|
|
case 0x74: // D3DFMT_A32B32G32R32F
|
|
|
format = SurfaceFormat.Vector4;
|
|
|
break;
|
|
|
case FOURCC_DXT1:
|
|
|
format = SurfaceFormat.Dxt1;
|
|
|
break;
|
|
|
case FOURCC_DXT3:
|
|
|
format = SurfaceFormat.Dxt3;
|
|
|
break;
|
|
|
case FOURCC_DXT5:
|
|
|
format = SurfaceFormat.Dxt5;
|
|
|
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 = SurfaceFormat.Vector4;
|
|
|
break;
|
|
|
|
|
|
case 10:
|
|
|
format = SurfaceFormat.HalfVector4;
|
|
|
break;
|
|
|
|
|
|
case 71:
|
|
|
format = SurfaceFormat.Dxt1;
|
|
|
break;
|
|
|
|
|
|
case 74:
|
|
|
format = SurfaceFormat.Dxt3;
|
|
|
break;
|
|
|
|
|
|
case 77:
|
|
|
format = SurfaceFormat.Dxt5;
|
|
|
break;
|
|
|
|
|
|
case 98:
|
|
|
format = SurfaceFormat.Bc7EXT;
|
|
|
break;
|
|
|
|
|
|
case 99:
|
|
|
format = SurfaceFormat.Bc7SrgbEXT;
|
|
|
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!
|
|
|
*/
|
|
|
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 = SurfaceFormat.ColorBgraEXT;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
throw new NotSupportedException(
|
|
|
"Unsupported DDS texture format"
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
}
|
|
|
|