You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1368 lines
42 KiB
C++
1368 lines
42 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DDSFile.h"
|
|
#include "Logging/LogMacros.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogDDSFile, Log, All);
|
|
|
|
namespace UE { namespace DDS
|
|
{
|
|
|
|
constexpr uint32 MakeFOURCC(uint32 a, uint32 b, uint32 c, uint32 d) { return ((a) | ((b) << 8) | ((c) << 16) | ((d) << 24)); }
|
|
|
|
constexpr uint32 DDSD_CAPS = 0x00000001;
|
|
constexpr uint32 DDSD_HEIGHT = 0x00000002;
|
|
constexpr uint32 DDSD_WIDTH = 0x00000004;
|
|
constexpr uint32 DDSD_PITCH = 0x00000008;
|
|
constexpr uint32 DDSD_PIXELFORMAT = 0x00001000;
|
|
constexpr uint32 DDSD_MIPMAPCOUNT = 0x00020000;
|
|
constexpr uint32 DDSD_DEPTH = 0x00800000;
|
|
|
|
constexpr uint32 DDPF_ALPHA = 0x00000002;
|
|
constexpr uint32 DDPF_FOURCC = 0x00000004;
|
|
constexpr uint32 DDPF_RGB = 0x00000040;
|
|
constexpr uint32 DDPF_LUMINANCE = 0x00020000;
|
|
constexpr uint32 DDPF_BUMPDUDV = 0x00080000;
|
|
|
|
constexpr uint32 DDSCAPS_COMPLEX = 0x00000008;
|
|
constexpr uint32 DDSCAPS_TEXTURE = 0x00001000;
|
|
constexpr uint32 DDSCAPS_MIPMAP = 0x00400000;
|
|
|
|
constexpr uint32 DDSCAPS2_CUBEMAP = 0x00000200;
|
|
constexpr uint32 DDSCAPS2_VOLUME = 0x00200000;
|
|
|
|
constexpr uint32 RESOURCE_DIMENSION_UNKNOWN = 0;
|
|
constexpr uint32 RESOURCE_DIMENSION_BUFFER = 1;
|
|
constexpr uint32 RESOURCE_DIMENSION_TEXTURE1D = 2;
|
|
constexpr uint32 RESOURCE_DIMENSION_TEXTURE2D = 3;
|
|
constexpr uint32 RESOURCE_DIMENSION_TEXTURE3D = 4;
|
|
|
|
constexpr uint32 RESOURCE_MISC_TEXTURECUBE = 0x00000004;
|
|
|
|
constexpr uint32 DDS_MAGIC = MakeFOURCC('D', 'D', 'S', ' ');
|
|
constexpr uint32 DX10_MAGIC = MakeFOURCC('D', 'X', '1', '0');
|
|
|
|
struct FDDSPixelFormat
|
|
{
|
|
uint32 size;
|
|
uint32 flags;
|
|
uint32 fourCC;
|
|
uint32 RGBBitCount;
|
|
uint32 RBitMask;
|
|
uint32 GBitMask;
|
|
uint32 BBitMask;
|
|
uint32 ABitMask;
|
|
};
|
|
|
|
struct FDDSHeaderWithMagic
|
|
{
|
|
uint32 Magic; // Must be DDS_MAGIC
|
|
uint32 size;
|
|
uint32 flags;
|
|
uint32 height;
|
|
uint32 width;
|
|
uint32 pitchOrLinearSize;
|
|
uint32 depth;
|
|
uint32 num_mips;
|
|
uint32 reserved1[11];
|
|
FDDSPixelFormat ddspf;
|
|
uint32 caps;
|
|
uint32 caps2;
|
|
uint32 caps3;
|
|
uint32 caps4;
|
|
uint32 reserved2;
|
|
};
|
|
|
|
struct FDDSHeaderDX10
|
|
{
|
|
uint32 dxgi_format;
|
|
uint32 resource_dimension;
|
|
uint32 misc_flag; // see D3D11_RESOURCE_MISC_FLAG
|
|
uint32 array_size;
|
|
uint32 misc_flag2;
|
|
};
|
|
|
|
struct FDXGIFormatName
|
|
{
|
|
EDXGIFormat Format;
|
|
const TCHAR* Name;
|
|
};
|
|
|
|
const TCHAR * DXGIFormatGetName(EDXGIFormat fmt)
|
|
{
|
|
static const FDXGIFormatName FormatList[] =
|
|
{
|
|
#define RGBFMT(name,id,bypu) { EDXGIFormat::name, TEXT(#name) },
|
|
#define BCNFMT(name,id,bypu) { EDXGIFormat::name, TEXT(#name) },
|
|
#define ODDFMT(name,id) { EDXGIFormat::name, TEXT(#name) },
|
|
UE_DXGI_FORMAT_LIST
|
|
#undef RGBFMT
|
|
#undef BCNFMT
|
|
#undef ODDFMT
|
|
};
|
|
|
|
for(size_t i = 0; i < sizeof(FormatList) / sizeof(*FormatList); ++i)
|
|
{
|
|
if (FormatList[i].Format == fmt)
|
|
{
|
|
return FormatList[i].Name;
|
|
}
|
|
}
|
|
return FormatList[0].Name; // first entry is "unknown format"
|
|
}
|
|
|
|
// list of non-sRGB / sRGB pixel format pairs: even=UNORM, odd=UNORM_SRGB
|
|
// (sorted by DXGI_FORMAT code)
|
|
static const EDXGIFormat DXGIFormatSRGBTable[] =
|
|
{
|
|
EDXGIFormat::R8G8B8A8_UNORM, EDXGIFormat::R8G8B8A8_UNORM_SRGB,
|
|
EDXGIFormat::BC1_UNORM, EDXGIFormat::BC1_UNORM_SRGB,
|
|
EDXGIFormat::BC2_UNORM, EDXGIFormat::BC2_UNORM_SRGB,
|
|
EDXGIFormat::BC3_UNORM, EDXGIFormat::BC3_UNORM_SRGB,
|
|
EDXGIFormat::B8G8R8A8_UNORM, EDXGIFormat::B8G8R8A8_UNORM_SRGB,
|
|
EDXGIFormat::B8G8R8X8_UNORM, EDXGIFormat::B8G8R8X8_UNORM_SRGB,
|
|
EDXGIFormat::BC7_UNORM, EDXGIFormat::BC7_UNORM_SRGB,
|
|
};
|
|
|
|
// List of corresponding RGBA/BGRA format pairs: even=RGBA, odd=BGRA
|
|
static const EDXGIFormat DXGIFormatRGBATable[] =
|
|
{
|
|
EDXGIFormat::R8G8B8A8_TYPELESS, EDXGIFormat::B8G8R8A8_TYPELESS,
|
|
EDXGIFormat::R8G8B8A8_UNORM, EDXGIFormat::B8G8R8A8_UNORM,
|
|
EDXGIFormat::R8G8B8A8_UNORM_SRGB, EDXGIFormat::B8G8R8A8_UNORM_SRGB,
|
|
EDXGIFormat::R8G8B8X8, EDXGIFormat::B8G8R8X8_TYPELESS,
|
|
// not mapped :
|
|
// RGBFMT(R8G8B8A8_UINT, 30, 4)
|
|
// RGBFMT(R8G8B8A8_SNORM, 31, 4)
|
|
// RGBFMT(R8G8B8A8_SINT, 32, 4)
|
|
};
|
|
|
|
static int DXGIFormatFindInTableImpl(const EDXGIFormat* InTable, int InCount, EDXGIFormat InFormat)
|
|
{
|
|
for (int i = 0; i < InCount; ++i)
|
|
{
|
|
if (InTable[i] == InFormat)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
template<int N>
|
|
static int DXGIFormatFindInTable(const EDXGIFormat (&InTable)[N], EDXGIFormat InFormat)
|
|
{
|
|
return DXGIFormatFindInTableImpl(InTable, N, InFormat);
|
|
}
|
|
|
|
bool DXGIFormatIsSRGB(EDXGIFormat Format)
|
|
{
|
|
int idx = DXGIFormatFindInTable(DXGIFormatSRGBTable, Format);
|
|
return idx >= 0 && ((idx & 1) == 1);
|
|
}
|
|
|
|
bool DXGIFormatHasLinearAndSRGBForm(EDXGIFormat Format)
|
|
{
|
|
int idx = DXGIFormatFindInTable(DXGIFormatSRGBTable, Format);
|
|
return idx >= 0;
|
|
}
|
|
|
|
EDXGIFormat DXGIFormatRemoveSRGB(EDXGIFormat fmt)
|
|
{
|
|
int idx = DXGIFormatFindInTable(DXGIFormatSRGBTable, fmt);
|
|
if(idx >= 0)
|
|
{
|
|
return DXGIFormatSRGBTable[idx & ~1];
|
|
}
|
|
else
|
|
{
|
|
return fmt;
|
|
}
|
|
}
|
|
|
|
EDXGIFormat DXGIFormatAddSRGB(EDXGIFormat fmt)
|
|
{
|
|
int idx = DXGIFormatFindInTable(DXGIFormatSRGBTable, fmt);
|
|
if(idx >= 0)
|
|
{
|
|
return DXGIFormatSRGBTable[idx | 1];
|
|
}
|
|
else
|
|
{
|
|
return fmt;
|
|
}
|
|
}
|
|
|
|
// this is used for trying to map old format specifications to DXGI.
|
|
struct FBitmaskToDXGI
|
|
{
|
|
uint32 Flags;
|
|
uint32 Bits;
|
|
uint32 RMask, GMask, BMask, AMask;
|
|
EDXGIFormat Format;
|
|
int32 AutoD3d9;
|
|
};
|
|
|
|
// used for mapping fourcc format specifications to DXGI
|
|
struct FFOURCCToDXGI
|
|
{
|
|
uint32 fourcc;
|
|
EDXGIFormat Format;
|
|
int32 AutoD3d9;
|
|
};
|
|
|
|
struct FDXGIFormatInfo
|
|
{
|
|
EDXGIFormat Format;
|
|
uint32 UnitWidth; // width of a coding unit
|
|
uint32 UnitHeight; // height of a coding unit
|
|
uint32 UnitBytes;
|
|
};
|
|
|
|
static const FDXGIFormatInfo SupportedFormatList[] =
|
|
{
|
|
#define RGBFMT(name,id,bypu) { EDXGIFormat::name, 1,1, bypu },
|
|
#define BCNFMT(name,id,bypu) { EDXGIFormat::name, 4,4, bypu },
|
|
#define ODDFMT(name,id) // these are not supported for reading so they're intentionally not on the list
|
|
UE_DXGI_FORMAT_LIST
|
|
#undef RGBFMT
|
|
#undef BCNFMT
|
|
#undef ODDFMT
|
|
};
|
|
|
|
// This is following MS DDSTextureLoader11.
|
|
//
|
|
// Formats with AutoD3d9 == 1 will get emitted as D3D9 DDS when FormatVersion is "Auto";
|
|
// the other ones we read from D3D9 .DDS files but will only write when FormatVersion is D3D9,
|
|
// and otherwise prefer D3D10 mode, because they're not widely supported in apps that consume
|
|
// D3D9 .DDS files.
|
|
//
|
|
static const FBitmaskToDXGI BitmaskToDXGITable[] =
|
|
{
|
|
//flags bits r g b a dxgi AutoD3d9
|
|
{ DDPF_RGB, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000, EDXGIFormat::R8G8B8A8_UNORM, 1 },
|
|
{ DDPF_RGB, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000, EDXGIFormat::B8G8R8A8_UNORM, 1 },
|
|
{ DDPF_RGB, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000, EDXGIFormat::B8G8R8X8_UNORM, 1 },
|
|
{ DDPF_RGB, 32, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000, EDXGIFormat::R10G10B10A2_UNORM, 0 }, // This mask is backwards, but that's the standard value to write for R10G10B10A2_UNORM! (see comments in DDSTextureLoader11)
|
|
{ DDPF_RGB, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000, EDXGIFormat::R16G16_UNORM, 1 },
|
|
{ DDPF_RGB, 32, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, EDXGIFormat::R32_FLOAT, 0 }, // only 32-bit color channel format in D3D9 was R32F
|
|
{ DDPF_RGB, 16, 0x7c00, 0x03e0, 0x001f, 0x8000, EDXGIFormat::B5G5R5A1_UNORM, 1 },
|
|
{ DDPF_RGB, 16, 0xf800, 0x07e0, 0x001f, 0x0000, EDXGIFormat::B5G6R5_UNORM, 1 },
|
|
{ DDPF_RGB, 16, 0x0f00, 0x00f0, 0x000f, 0xf000, EDXGIFormat::B4G4R4A4_UNORM, 1 },
|
|
{ DDPF_LUMINANCE, 8, 0xff, 0x00, 0x00, 0x00, EDXGIFormat::R8_UNORM, 1 },
|
|
{ DDPF_LUMINANCE, 16, 0xffff, 0x0000, 0x0000, 0x0000, EDXGIFormat::R16_UNORM, 1 },
|
|
{ DDPF_LUMINANCE, 16, 0x00ff, 0x0000, 0x0000, 0xff00, EDXGIFormat::R8G8_UNORM, 1 }, // official way to do it - this must go first!
|
|
{ DDPF_LUMINANCE, 8, 0xff, 0x00, 0x00, 0xff00, EDXGIFormat::R8G8_UNORM, 0 }, // some writers write this instead, ugh.
|
|
{ DDPF_ALPHA, 8, 0x00, 0x00, 0x00, 0xff, EDXGIFormat::A8_UNORM, 1 },
|
|
{ DDPF_BUMPDUDV, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000, EDXGIFormat::R8G8B8A8_SNORM, 0 },
|
|
{ DDPF_BUMPDUDV, 2, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000, EDXGIFormat::R16G16_SNORM, 0 },
|
|
{ DDPF_BUMPDUDV, 16, 0x00ff, 0xff00, 0x0000, 0x0000, EDXGIFormat::R8G8_SNORM, 0 },
|
|
{ DDPF_RGB, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000, EDXGIFormat::R8G8B8X8, 1 },
|
|
{ DDPF_RGB, 24, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000, EDXGIFormat::R8G8B8, 1 },
|
|
{ DDPF_RGB, 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000, EDXGIFormat::B8G8R8, 1 },
|
|
};
|
|
|
|
|
|
|
|
// This is following MS DDSTextureLoader11.
|
|
// When multiple FOURCCs map to the same DXGI format, put the preferred FOURCC first.
|
|
// AutoD3d9 works as above.
|
|
static const FFOURCCToDXGI FOURCCToDXGITable[] =
|
|
{
|
|
//fourcc dxgi AutoD3d9
|
|
{ MakeFOURCC('D','X','T','1'), EDXGIFormat::BC1_UNORM, 1 },
|
|
{ MakeFOURCC('D','X','T','3'), EDXGIFormat::BC2_UNORM, 1 }, // Note: comes before DXT2 because it's our preferred choice for BC2 export
|
|
{ MakeFOURCC('D','X','T','2'), EDXGIFormat::BC2_UNORM, 1 },
|
|
{ MakeFOURCC('D','X','T','5'), EDXGIFormat::BC3_UNORM, 1 }, // Note: comes before DXT4 because it's our preferred choice for BC3 export
|
|
{ MakeFOURCC('D','X','T','4'), EDXGIFormat::BC3_UNORM, 1 },
|
|
{ MakeFOURCC('B','C','4','U'), EDXGIFormat::BC4_UNORM, 0 },
|
|
{ MakeFOURCC('B','C','4','S'), EDXGIFormat::BC4_SNORM, 0 },
|
|
{ MakeFOURCC('A','T','I','1'), EDXGIFormat::BC4_UNORM, 0 }, // Note: prefer the more explicit BC4U to ATI1 for export
|
|
{ MakeFOURCC('B','C','5','U'), EDXGIFormat::BC5_UNORM, 0 },
|
|
{ MakeFOURCC('B','C','5','S'), EDXGIFormat::BC5_SNORM, 0 },
|
|
{ MakeFOURCC('A','T','I','2'), EDXGIFormat::BC5_UNORM, 0 }, // NOTE: ATI2 is kind of odd (technically swapped block order), so put it below BC5U
|
|
{ MakeFOURCC('B','C','6','H'), EDXGIFormat::BC6H_UF16, 0 },
|
|
{ MakeFOURCC('B','C','7','L'), EDXGIFormat::BC7_UNORM, 0 },
|
|
{ MakeFOURCC('B','C','7', 0 ), EDXGIFormat::BC7_UNORM, 0 },
|
|
{ 36, EDXGIFormat::R16G16B16A16_UNORM, 1 }, // D3DFMT_A16B16G16R16
|
|
{ 110, EDXGIFormat::R16G16B16A16_SNORM, 0 }, // D3DFMT_Q16W16V16U16
|
|
{ 111, EDXGIFormat::R16_FLOAT, 1 }, // D3DFMT_R16F
|
|
{ 112, EDXGIFormat::R16G16_FLOAT, 1 }, // D3DFMT_G16R16F
|
|
{ 113, EDXGIFormat::R16G16B16A16_FLOAT, 1 }, // D3DFMT_A16B16G16R16F
|
|
{ 114, EDXGIFormat::R32_FLOAT, 1 }, // D3DFMT_R32F
|
|
{ 115, EDXGIFormat::R32G32_FLOAT, 1 }, // D3DFMT_G32R32F
|
|
{ 116, EDXGIFormat::R32G32B32A32_FLOAT, 1 }, // D3DFMT_A32B32G32R32F
|
|
};
|
|
|
|
static const FDXGIFormatInfo* DXGIFormatGetInfo(EDXGIFormat InFormat)
|
|
{
|
|
// need to handle this special because UNKNOWN _does_ appear in the SupportedFormatList
|
|
// but we don't want to treat it as legal
|
|
if (InFormat == EDXGIFormat::UNKNOWN)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
for (size_t i = 0; i < sizeof(SupportedFormatList)/sizeof(*SupportedFormatList); ++i)
|
|
{
|
|
if (InFormat == SupportedFormatList[i].Format)
|
|
{
|
|
return &SupportedFormatList[i];
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static EDXGIFormat DXGIFormatFromDDS9Header(const FDDSHeaderWithMagic* InDDSHeader)
|
|
{
|
|
// The old format can be specified either with FOURCC or with some bit masks, so we use
|
|
// this to determine the corresponding dxgi format.
|
|
const FDDSPixelFormat &ddpf = InDDSHeader->ddspf;
|
|
if (ddpf.flags & DDPF_FOURCC)
|
|
{
|
|
for (size_t i = 0; i < sizeof(FOURCCToDXGITable)/sizeof(*FOURCCToDXGITable); ++i)
|
|
{
|
|
if (ddpf.fourCC == FOURCCToDXGITable[i].fourcc)
|
|
{
|
|
return FOURCCToDXGITable[i].Format;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32 type_flags = ddpf.flags & (DDPF_RGB | DDPF_LUMINANCE | DDPF_ALPHA | DDPF_BUMPDUDV);
|
|
for (size_t i = 0; i < sizeof(BitmaskToDXGITable)/sizeof(*BitmaskToDXGITable); ++i)
|
|
{
|
|
const FBitmaskToDXGI *fmt = &BitmaskToDXGITable[i];
|
|
if (type_flags == fmt->Flags && ddpf.RGBBitCount == fmt->Bits &&
|
|
ddpf.RBitMask == fmt->RMask && ddpf.GBitMask == fmt->GMask &&
|
|
ddpf.BBitMask == fmt->BMask && ddpf.ABitMask == fmt->AMask)
|
|
{
|
|
return fmt->Format;
|
|
}
|
|
}
|
|
}
|
|
|
|
return EDXGIFormat::UNKNOWN;
|
|
}
|
|
|
|
static uint32 MipDimension(uint32 dim, uint32 level)
|
|
{
|
|
check( level <= FDDSFile::MAX_MIPS_SUPPORTED ); // guaranteed less than 32
|
|
|
|
// mip dimensions truncate at every level and bottom out at 1
|
|
uint32 x = dim >> level;
|
|
return x ? x : 1;
|
|
}
|
|
|
|
static void InitMip(FDDSMip* InMip, uint32 InWidth, uint32 InHeight, uint32 InDepth, const FDXGIFormatInfo* InFormatInfo)
|
|
{
|
|
uint32 width_u = (InWidth + InFormatInfo->UnitWidth-1) / InFormatInfo->UnitWidth;
|
|
uint32 height_u = (InHeight + InFormatInfo->UnitHeight-1) / InFormatInfo->UnitHeight;
|
|
|
|
InMip->Width = InWidth;
|
|
InMip->Height = InHeight;
|
|
InMip->Depth = InDepth;
|
|
InMip->RowStride = (int64)width_u * InFormatInfo->UnitBytes;
|
|
InMip->SliceStride = (int64)height_u * InMip->RowStride;
|
|
InMip->DataSize = (int64)InDepth * InMip->SliceStride;
|
|
InMip->Data = 0;
|
|
}
|
|
|
|
EDDSError FDDSFile::Validate() const
|
|
{
|
|
// Supported pixel format?
|
|
const FDXGIFormatInfo* FormatInfo = DXGIFormatGetInfo(DXGIFormat);
|
|
if (!FormatInfo)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("Unsupported format %d (%s)"), int(DXGIFormat), DXGIFormatGetName(DXGIFormat));
|
|
return EDDSError::BadPixelFormat;
|
|
}
|
|
|
|
// Resource and image dimensions agree?
|
|
switch (Dimension)
|
|
{
|
|
case 1:
|
|
if (Height != 1 || Depth != 1)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("1D textures must have height and depth of 1."));
|
|
return EDDSError::BadImageDimension;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
if (Depth != 1)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("2D textures must have depth of 1."));
|
|
return EDDSError::BadImageDimension;
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
if (ArraySize != 1)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("3D textures must have array size of 1."));
|
|
return EDDSError::BadImageDimension;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
UE_LOG(LogDDSFile, Warning, TEXT("DDS textures must be 1D, 2D or 3D."));
|
|
return EDDSError::BadResourceDimension;
|
|
}
|
|
|
|
// All dimensions must be non-zero
|
|
if (Width == 0 || Height == 0 || Depth == 0 || MipCount == 0 || ArraySize == 0)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("One or more image dimensions are zero."));
|
|
return EDDSError::BadImageDimension;
|
|
}
|
|
|
|
// Images must not be larger than we support
|
|
check( MAX_MIPS_SUPPORTED < 32 );
|
|
const uint32 MaxDimension = (1 << MAX_MIPS_SUPPORTED) - 1; // 1<<k is when we tip over into needing k+1 mip levels, (1<<k)-1 needs just k.
|
|
if (Width > MaxDimension || Height > MaxDimension || Depth > MaxDimension)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("Image dimensions %ux%ux%u of DDS exceed maximum of %u."), Width, Height, Depth, MaxDimension);
|
|
return EDDSError::BadImageDimension;
|
|
}
|
|
|
|
// Check that mipmap count is supported and makes sense for the image dimensions.
|
|
if (MipCount > MAX_MIPS_SUPPORTED)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("Invalid mipmap count of %u."), MipCount);
|
|
return EDDSError::BadMipmapCount;
|
|
}
|
|
|
|
// ArraySize * MipCount must fit in 32 bits
|
|
// checking against max dim will ensure that :
|
|
if ( ArraySize > MaxDimension )
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("ArraySize %ux of DDS exceed maximum of %u."), ArraySize, MaxDimension);
|
|
return EDDSError::BadImageDimension;
|
|
}
|
|
|
|
// all dimensions are limited to 20 bits, so you could get to 1<<60 pixel count
|
|
// with 16-byte pixels the bytes per surface could then go to negative in int64
|
|
uint64 MaxPixelCount = 1ULL<<40; // could be higher and not overflow int64 , but would run out of memory anyway
|
|
// note the limit at MaxPixelCount is not strict due to use of floats, but doesn't need to be
|
|
if ( float(Width) * float(Height) * float(Depth) * float(ArraySize) > float(MaxPixelCount) )
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("Image dimensions %ux%ux%ux%u of DDS exceed maximum pixel count."), Width, Height, Depth, ArraySize);
|
|
return EDDSError::BadImageDimension;
|
|
}
|
|
|
|
// Mipmaps halve each dimension (rounding down) every step; dimensions that end up at 0
|
|
// turn into 1. For the mip count to be valid, in the final mip level, at least one
|
|
// dimension should have been 0 before this adjustment.
|
|
const uint32 FinalMip = MipCount - 1;
|
|
if ((Width >> FinalMip) == 0 &&
|
|
(Height >> FinalMip) == 0 &&
|
|
(Depth >> FinalMip) == 0)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("Invalid mipmap count of %u for %ux%ux%u image."), MipCount, Width, Height, Depth);
|
|
return EDDSError::BadMipmapCount;
|
|
}
|
|
|
|
// Cubemaps need to be square and have a valid array count
|
|
if (CreateFlags & CREATE_FLAG_CUBEMAP)
|
|
{
|
|
if (Dimension != 2 )
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("Cubemap must be dimension 2!"));
|
|
return EDDSError::BadCubemap;
|
|
}
|
|
|
|
if (Width != Height || Depth != 1)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("Cubemap has non-square faces or non-1 depth!"));
|
|
return EDDSError::BadCubemap;
|
|
}
|
|
|
|
if (ArraySize < 6 || (ArraySize % 6) != 0)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("Cubemap or cubemap array doesn't have a multiple of 6 faces."));
|
|
return EDDSError::BadCubemap;
|
|
}
|
|
}
|
|
|
|
return EDDSError::OK;
|
|
}
|
|
|
|
bool FDDSFile::IsValidTexture2D() const
|
|
{
|
|
if ( Validate() != EDDSError::OK )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( Dimension == 1 || Dimension == 2 )
|
|
{
|
|
if ( ArraySize == 1 )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FDDSFile::IsValidTextureCube() const
|
|
{
|
|
if ( Validate() != EDDSError::OK )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (CreateFlags & CREATE_FLAG_CUBEMAP)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FDDSFile::IsValidTextureArray() const
|
|
{
|
|
if ( Validate() != EDDSError::OK )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (CreateFlags & CREATE_FLAG_CUBEMAP)
|
|
{
|
|
// don't identify cubes as arrays
|
|
return false;
|
|
}
|
|
|
|
if ( Dimension == 1 || Dimension == 2 )
|
|
{
|
|
if ( ArraySize > 1 )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FDDSFile::IsValidTextureVolume() const
|
|
{
|
|
if ( Validate() != EDDSError::OK )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( Dimension == 3 )
|
|
{
|
|
// checked in Validate :
|
|
check( ArraySize == 1 );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static EDDSError AllocateMips(FDDSFile* InDDS, const FDXGIFormatInfo* InFormatInfo, uint32 InCreateFlags)
|
|
{
|
|
InDDS->Mips.Empty();
|
|
InDDS->Mips.SetNumZeroed(InDDS->ArraySize * InDDS->MipCount);
|
|
|
|
if (!(InCreateFlags & FDDSFile::CREATE_FLAG_NO_MIP_STORAGE_ALLOC))
|
|
{
|
|
// Allocate storage for all the mip levels
|
|
|
|
// first pass, add up all sizes
|
|
// then alloc it, second pass hand out all the pointers
|
|
|
|
int64 AllMipsSize = 0;
|
|
for (uint32 ArrayIndex = 0; ArrayIndex < InDDS->ArraySize; ++ArrayIndex)
|
|
{
|
|
for (uint32 MipIndex = 0; MipIndex < InDDS->MipCount; ++MipIndex)
|
|
{
|
|
FDDSMip* Mip = &InDDS->Mips[ArrayIndex * InDDS->MipCount + MipIndex];
|
|
uint32 MipWidth = MipDimension(InDDS->Width, MipIndex);
|
|
uint32 MipHeight = MipDimension(InDDS->Height, MipIndex);
|
|
uint32 MipDepth = MipDimension(InDDS->Depth, MipIndex);
|
|
InitMip(Mip, MipWidth, MipHeight, MipDepth, InFormatInfo);
|
|
AllMipsSize += Mip->DataSize;
|
|
}
|
|
}
|
|
|
|
InDDS->MipRawData.SetNumUninitialized(AllMipsSize);
|
|
|
|
int64 CurrentOffset = 0;
|
|
for (auto& Mip : InDDS->Mips)
|
|
{
|
|
Mip.Data = &InDDS->MipRawData[CurrentOffset];
|
|
CurrentOffset += Mip.DataSize;
|
|
}
|
|
|
|
check(CurrentOffset == AllMipsSize);
|
|
}
|
|
|
|
return EDDSError::OK;
|
|
}
|
|
|
|
/* static */ FDDSFile* FDDSFile::CreateEmpty(int InDimension, uint32 InWidth, uint32 InHeight, uint32 InDepth, uint32 InMipCount, uint32 InArraySize, EDXGIFormat InFormat, uint32 InCreateFlags, EDDSError* OutError)
|
|
{
|
|
// If null OutError passed, point to somewhere safe
|
|
EDDSError DummyError;
|
|
if (!OutError)
|
|
OutError = &DummyError;
|
|
|
|
// Allocate the new DDS
|
|
FDDSFile* DDS = new FDDSFile();
|
|
if (!DDS)
|
|
{
|
|
*OutError = EDDSError::OutOfMemory;
|
|
return nullptr;
|
|
}
|
|
|
|
// Set up the parameters
|
|
DDS->Dimension = InDimension;
|
|
DDS->Width = InWidth;
|
|
DDS->Height = InHeight;
|
|
DDS->Depth = InDepth;
|
|
DDS->MipCount = InMipCount;
|
|
DDS->ArraySize = InArraySize;
|
|
DDS->DXGIFormat = InFormat;
|
|
DDS->CreateFlags = InCreateFlags & ~CREATE_FLAG_NO_MIP_STORAGE_ALLOC;
|
|
|
|
// Check all the parameters
|
|
*OutError = DDS->Validate();
|
|
if (*OutError != EDDSError::OK)
|
|
{
|
|
delete DDS;
|
|
return nullptr;
|
|
}
|
|
|
|
// Try to allocate mip storage
|
|
// Validate checks the pixel format is OK, so we don't need to re-check here
|
|
const FDXGIFormatInfo* FormatInfo = DXGIFormatGetInfo(InFormat);
|
|
*OutError = AllocateMips(DDS, FormatInfo, InCreateFlags);
|
|
if (*OutError != EDDSError::OK) //-V547
|
|
{
|
|
delete DDS;
|
|
return nullptr;
|
|
}
|
|
|
|
return DDS;
|
|
}
|
|
|
|
/* static */ FDDSFile* FDDSFile::CreateEmpty2D(uint32 InWidth, uint32 InHeight, uint32 InMipCount, EDXGIFormat InFormat, uint32 InCreateFlags, EDDSError* OutError)
|
|
{
|
|
return FDDSFile::CreateEmpty(2, InWidth, InHeight, 1, InMipCount, 1, InFormat, InCreateFlags, OutError);
|
|
}
|
|
|
|
static EDDSError ParseHeader(FDDSFile* InDDS, FDDSHeaderWithMagic const* InHeader, FDDSHeaderDX10 const* InDX10Header)
|
|
{
|
|
// If the fourCC is "DX10" then we have a secondary header that follows the first header.
|
|
// This header specifies an dxgi_format explicitly, so we don't have to derive one.
|
|
bool bDX10 = false;
|
|
const FDDSPixelFormat& ddpf = InHeader->ddspf;
|
|
if ((ddpf.flags & DDPF_FOURCC) && ddpf.fourCC == DX10_MAGIC)
|
|
{
|
|
if (InDX10Header->resource_dimension >= RESOURCE_DIMENSION_TEXTURE1D && InDX10Header->resource_dimension <= RESOURCE_DIMENSION_TEXTURE3D)
|
|
{
|
|
InDDS->Dimension = (InDX10Header->resource_dimension - RESOURCE_DIMENSION_TEXTURE1D) + 1;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("D3D10 resource dimension in DDS is neither 1D, 2D, nor 3D."));
|
|
return EDDSError::BadResourceDimension;
|
|
}
|
|
InDDS->DXGIFormat = (EDXGIFormat)InDX10Header->dxgi_format;
|
|
bDX10 = true;
|
|
}
|
|
else
|
|
{
|
|
// For D3D9-style files, we guess dimension from the caps bits.
|
|
// If the volume cap is set, assume 3D, otherwise 2D.
|
|
InDDS->Dimension = (InHeader->caps2 & DDSCAPS2_VOLUME) ? 3 : 2;
|
|
InDDS->DXGIFormat = DXGIFormatFromDDS9Header(InHeader);
|
|
}
|
|
|
|
// More header parsing
|
|
bool bIsCubemap = bDX10 ? (InDX10Header->misc_flag & RESOURCE_MISC_TEXTURECUBE) != 0 : (InHeader->caps2 & DDSCAPS2_CUBEMAP) != 0;
|
|
bool bIsVolume = InDDS->Dimension == 3;
|
|
|
|
InDDS->Width = InHeader->width;
|
|
InDDS->Height = InHeader->height;
|
|
InDDS->Depth = bIsVolume ? InHeader->depth : 1;
|
|
InDDS->MipCount = (InHeader->caps & DDSCAPS_MIPMAP) ? InHeader->num_mips : 1;
|
|
InDDS->ArraySize = bDX10 ? InDX10Header->array_size : 1;
|
|
InDDS->CreateFlags = 0;
|
|
if (bIsCubemap)
|
|
{
|
|
InDDS->CreateFlags |= FDDSFile::CREATE_FLAG_CUBEMAP;
|
|
InDDS->ArraySize *= 6;
|
|
}
|
|
|
|
if (!bDX10)
|
|
{
|
|
InDDS->CreateFlags |= FDDSFile::CREATE_FLAG_WAS_D3D9;
|
|
}
|
|
|
|
// Sanity-check header values and return
|
|
EDDSError Error = InDDS->Validate();
|
|
|
|
return Error;
|
|
}
|
|
|
|
static EDDSError ReadPayload(FDDSFile* InDDS, const uint8* InReadCursor, const uint8* InReadEnd)
|
|
{
|
|
const FDXGIFormatInfo* FormatInfo = DXGIFormatGetInfo(InDDS->DXGIFormat);
|
|
EDDSError Error = AllocateMips(InDDS, FormatInfo, InDDS->CreateFlags);
|
|
if (Error != EDDSError::OK)
|
|
{
|
|
return Error;
|
|
}
|
|
|
|
for (auto& Mip : InDDS->Mips)
|
|
{
|
|
if (InReadEnd - InReadCursor < Mip.DataSize)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("Error reading texture data."));
|
|
return EDDSError::IoError;
|
|
}
|
|
|
|
memcpy(Mip.Data, InReadCursor, Mip.DataSize);
|
|
InReadCursor += Mip.DataSize;
|
|
}
|
|
|
|
return EDDSError::OK;
|
|
}
|
|
|
|
|
|
int64 GetDDSHeaderMaximalSize()
|
|
{
|
|
return sizeof(FDDSHeaderWithMagic) + sizeof(FDDSHeaderDX10);
|
|
}
|
|
|
|
int64 GetDDSHeaderMinimalSize()
|
|
{
|
|
return sizeof(FDDSHeaderWithMagic);
|
|
}
|
|
|
|
|
|
|
|
/* static */ FDDSFile* FDDSFile::CreateFromDDSInMemory(const uint8* InDDS, int64 InDDSSize, EDDSError *OutError, bool bHeaderOnly)
|
|
{
|
|
// If no OutError passed in, redirect it to dummy storage on stack.
|
|
EDDSError DummyError;
|
|
if (!OutError)
|
|
{
|
|
OutError = &DummyError;
|
|
}
|
|
|
|
FDDSHeaderWithMagic DDSHeader = {};
|
|
FDDSHeaderDX10 DDS10Header = {};
|
|
|
|
// If we don't even have enough bytes for this to contain a valid header,
|
|
// definitely a bad file.
|
|
if (InDDSSize < sizeof(DDSHeader))
|
|
{
|
|
//UE_LOG(LogDDSFile, Display, TEXT("Too few bytes to read DDS header"));
|
|
*OutError = EDDSError::IoError;
|
|
return nullptr;
|
|
}
|
|
|
|
// We've now established that InDDSSize >= sizeof(DDSHeader),
|
|
// so we can read that much for sure.
|
|
// It's slightly more convenient to work with an end pointer here.
|
|
const uint8* ReadEnd = InDDS + InDDSSize;
|
|
const uint8* ReadCursor = InDDS;
|
|
|
|
memcpy(&DDSHeader, ReadCursor, sizeof(DDSHeader));
|
|
ReadCursor += sizeof(DDSHeader);
|
|
|
|
if (DDSHeader.Magic != DDS_MAGIC)
|
|
{
|
|
//UE_LOG(LogDDSFile, Display, TEXT("Not a DDS file."));
|
|
*OutError = EDDSError::NotADds;
|
|
return nullptr;
|
|
}
|
|
|
|
// Do we need to read a dx10 header?
|
|
const FDDSPixelFormat& ddpf = DDSHeader.ddspf;
|
|
if ((ddpf.flags & DDPF_FOURCC) && ddpf.fourCC == DX10_MAGIC)
|
|
{
|
|
if (ReadEnd - ReadCursor < sizeof(DDS10Header))
|
|
{
|
|
UE_LOG(LogDDSFile, Display, TEXT("Failed to read DX10 DDS header"));
|
|
*OutError = EDDSError::IoError;
|
|
return nullptr;
|
|
}
|
|
|
|
memcpy(&DDS10Header, ReadCursor, sizeof(DDS10Header));
|
|
ReadCursor += sizeof(DDS10Header);
|
|
}
|
|
|
|
FDDSFile* DDS = new FDDSFile();
|
|
|
|
*OutError = ParseHeader(DDS, &DDSHeader, &DDS10Header);
|
|
if (*OutError == EDDSError::OK && !bHeaderOnly)
|
|
{
|
|
*OutError = ReadPayload(DDS, ReadCursor, ReadEnd);
|
|
}
|
|
|
|
if (*OutError != EDDSError::OK)
|
|
{
|
|
delete DDS;
|
|
return nullptr;
|
|
}
|
|
|
|
return DDS;
|
|
}
|
|
|
|
bool FDDSFile::IsADDS(const uint8* InDDS, int64 InDDSSize)
|
|
{
|
|
FDDSHeaderWithMagic DDSHeader = {};
|
|
FDDSHeaderDX10 DDS10Header = {};
|
|
|
|
// If we don't even have enough bytes for this to contain a valid header,
|
|
// definitely a bad file.
|
|
if (InDDSSize < sizeof(DDSHeader))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// We've now established that InDDSSize >= sizeof(DDSHeader),
|
|
// so we can read that much for sure.
|
|
// It's slightly more convenient to work with an end pointer here.
|
|
const uint8* ReadEnd = InDDS + InDDSSize;
|
|
const uint8* ReadCursor = InDDS;
|
|
|
|
memcpy(&DDSHeader, ReadCursor, sizeof(DDSHeader));
|
|
ReadCursor += sizeof(DDSHeader);
|
|
|
|
if (DDSHeader.Magic != DDS_MAGIC)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Do we need to read a dx10 header?
|
|
const FDDSPixelFormat& ddpf = DDSHeader.ddspf;
|
|
if ((ddpf.flags & DDPF_FOURCC) && ddpf.fourCC == DX10_MAGIC)
|
|
{
|
|
if (ReadEnd - ReadCursor < sizeof(DDS10Header))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
memcpy(&DDS10Header, ReadCursor, sizeof(DDS10Header));
|
|
ReadCursor += sizeof(DDS10Header);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Write to an archive (i.e. file)
|
|
//
|
|
EDDSError FDDSFile::WriteDDS(TArray64<uint8>& OutDDS, EDDSFormatVersion InFormatVersion)
|
|
{
|
|
// Validate before we save
|
|
EDDSError ValidateErr = Validate();
|
|
if (ValidateErr != EDDSError::OK)
|
|
{
|
|
return ValidateErr;
|
|
}
|
|
|
|
// Preallocate enough memory for all mips and the largest header option
|
|
int64 PreallocSize = sizeof(FDDSHeaderWithMagic) + sizeof(FDDSHeaderDX10);
|
|
for (auto& Mip : Mips)
|
|
{
|
|
PreallocSize += Mip.DataSize;
|
|
}
|
|
OutDDS.Empty();
|
|
OutDDS.Reserve(PreallocSize);
|
|
|
|
// We can change format and dimension when writing in D3D9 mode, so keep track of
|
|
// what we're going to write to the file.
|
|
int32 EffectiveDimension = Dimension;
|
|
EDXGIFormat EffectiveFormat = DXGIFormat;
|
|
|
|
bool bIsCubemap = (this->CreateFlags & CREATE_FLAG_CUBEMAP) != 0;
|
|
uint32 DepthFlag = (this->Dimension == 3) ? 0x800000 : 0; // DDSD_DEPTH
|
|
uint32 WriteArraySize = bIsCubemap ? this->ArraySize / 6 : this->ArraySize;
|
|
|
|
uint32 Caps2 = 0;
|
|
if (this->Dimension == 3)
|
|
{
|
|
Caps2 |= DDSCAPS2_VOLUME;
|
|
}
|
|
if (bIsCubemap)
|
|
{
|
|
Caps2 |= 0xFE00; // DDSCAPS2_CUBEMAP*
|
|
}
|
|
|
|
// We always try to find a D3D9 pixel format matching our DXGI Format for export.
|
|
// In D3D10 mode, nothing is done with this information.
|
|
// In Auto mode, we consider D3D9 pixel formats when we consider them widely supported,
|
|
// which are those with AutoD3D9 >= 1.
|
|
// In D3D9 mode, we'll take any D3D9 pixel format that works, even if they get fairly
|
|
// obscure.
|
|
int MinAutoD3d9 = 1; // Only consider D3d9 for pixel formats that have an AutoD3d9 value >= this
|
|
if (InFormatVersion == EDDSFormatVersion::D3D9)
|
|
{
|
|
// In force-D3D9 mode, don't be picky about which formats we consider viable.
|
|
MinAutoD3d9 = 0;
|
|
|
|
// D3D9 format DDS can't represent sRGB-ness, so strip sRGB flag from format.
|
|
EffectiveFormat = DXGIFormatRemoveSRGB(EffectiveFormat);
|
|
}
|
|
|
|
// Look up how to represent effective format as D3D9 bitmasks format
|
|
// (if multiple choices, pick the first we find)
|
|
const FBitmaskToDXGI* D3d9BitmaskFormat = nullptr;
|
|
for (size_t i = 0; i < sizeof(BitmaskToDXGITable) / sizeof(*BitmaskToDXGITable); ++i)
|
|
{
|
|
if (BitmaskToDXGITable[i].Format == EffectiveFormat && BitmaskToDXGITable[i].AutoD3d9 >= MinAutoD3d9)
|
|
{
|
|
D3d9BitmaskFormat = &BitmaskToDXGITable[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Look up how to represent effective format as D3D9 FOURCC format
|
|
// (if multiple choices, pick the first we find)
|
|
uint32 D3d9FourCC = 0;
|
|
for (size_t i = 0; i < sizeof(FOURCCToDXGITable) / sizeof(*FOURCCToDXGITable); i++)
|
|
{
|
|
if (FOURCCToDXGITable[i].Format == EffectiveFormat && FOURCCToDXGITable[i].AutoD3d9 >= MinAutoD3d9)
|
|
{
|
|
D3d9FourCC = FOURCCToDXGITable[i].fourcc;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// In D3D9 mode, there's some extra checks because a few things just aren't representable as D3D9.
|
|
if (InFormatVersion == EDDSFormatVersion::D3D9)
|
|
{
|
|
// If we found neither a bitmask format nor FourCC, we don't know how to save this pixel format for D3D9.
|
|
if (!D3d9BitmaskFormat && !D3d9FourCC)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("Unsupported pixel format %s for D3D9 DDS."), DXGIFormatGetName(EffectiveFormat));
|
|
return EDDSError::BadPixelFormat;
|
|
}
|
|
|
|
if (ArraySize != 1)
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("D3D9 .DDS does not support arrays."));
|
|
return EDDSError::BadImageDimension;
|
|
}
|
|
|
|
if (EffectiveDimension == 1)
|
|
{
|
|
// D3D9 DDS can't do real 1D, it just implicitly turns it into 2D.
|
|
// Height is already 1 (validate checks that), so in D3D9 mode we just
|
|
// bump the effective dimension to 2D and write a Nx1 pixel image instead.
|
|
EffectiveDimension = 2;
|
|
}
|
|
}
|
|
|
|
// Set up the DDS header
|
|
FDDSHeaderWithMagic DDSHeader =
|
|
{
|
|
DDS_MAGIC,
|
|
124, // size value. Required to be 124
|
|
DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DepthFlag,
|
|
this->Height,
|
|
this->Width,
|
|
0, // pitch or linear size
|
|
this->Depth,
|
|
this->MipCount,
|
|
{}, // reserved U32's
|
|
// DDSPF (DDS PixelFormat)
|
|
{
|
|
32, // size, must be 32
|
|
DDPF_FOURCC, // DDPF_FOURCC
|
|
DX10_MAGIC, // Set up for writing as D3D10-format file
|
|
0,0,0,0,0 // Mask and bit counts, if used, are set up below.
|
|
},
|
|
DDSCAPS_COMPLEX | DDSCAPS_TEXTURE | DDSCAPS_MIPMAP,
|
|
Caps2,
|
|
0,
|
|
0,
|
|
0
|
|
};
|
|
|
|
uint32 ResourceDimension = RESOURCE_DIMENSION_TEXTURE1D + (EffectiveDimension - 1);
|
|
uint32 MiscFlags = bIsCubemap ? RESOURCE_MISC_TEXTURECUBE : 0;
|
|
FDDSHeaderDX10 DX10Header =
|
|
{
|
|
(uint32)EffectiveFormat, // DXGI_FORMAT
|
|
ResourceDimension,
|
|
MiscFlags,
|
|
WriteArraySize,
|
|
0, // DDS_ALPHA_MODE_UNKNOWN
|
|
};
|
|
|
|
// So far, we set up the header for a D3D10-format DDS. Check if we should write
|
|
// as D3D9-format instead.
|
|
//
|
|
// In format=D3D9 mode, we did some extra validation earlier to ensure that we can always
|
|
// write D3D9-format files once we get here, else we would've returned an error earlier.
|
|
// In Auto mode, we check if writing a D3D9-format file is viable, and if so, change our
|
|
// pixel format description to a D3D9 one (which then makes us write a D3D9 DDS).
|
|
if (InFormatVersion != EDDSFormatVersion::D3D10 && EffectiveDimension >= 2 && ArraySize == 1)
|
|
{
|
|
// Sizes are OK.
|
|
// If we have a suitable D3D9 pixel format, writing as D3D9-format DDS is an option.
|
|
FDDSPixelFormat* PixelFmt = &DDSHeader.ddspf;
|
|
|
|
if (D3d9BitmaskFormat)
|
|
{
|
|
// Can write as D3D9 bitmask format, so do it!
|
|
PixelFmt->flags = D3d9BitmaskFormat->Flags;
|
|
PixelFmt->fourCC = 0;
|
|
PixelFmt->RGBBitCount = D3d9BitmaskFormat->Bits;
|
|
PixelFmt->RBitMask = D3d9BitmaskFormat->RMask;
|
|
PixelFmt->GBitMask = D3d9BitmaskFormat->GMask;
|
|
PixelFmt->BBitMask = D3d9BitmaskFormat->BMask;
|
|
PixelFmt->ABitMask = D3d9BitmaskFormat->AMask;
|
|
}
|
|
else if (D3d9FourCC != 0)
|
|
{
|
|
PixelFmt->fourCC = D3d9FourCC;
|
|
}
|
|
|
|
// If neither of the above two cases were true, continue on with the header we already have,
|
|
// which corresponds to a D3D10 DDS.
|
|
//
|
|
// In Auto mode, this happens when we didn't find a suitable D3D9-esque pixel format to write.
|
|
// In D3D9 mode, this should never happen.
|
|
}
|
|
|
|
// Write the headers
|
|
OutDDS.Append((const uint8*)&DDSHeader, sizeof(DDSHeader));
|
|
if (DDSHeader.ddspf.fourCC == DX10_MAGIC)
|
|
{
|
|
check(InFormatVersion != EDDSFormatVersion::D3D9); // If we try to write a D3D10 header despite being in D3D9 mode, something went wrong.
|
|
OutDDS.Append((const uint8*)&DX10Header, sizeof(DX10Header));
|
|
}
|
|
|
|
// Now go through all subresources in standard order and write them out
|
|
// Need to write them one by one even though we allocate them as a
|
|
// contiguous block if we do it ourselves, because of
|
|
// CREATE_FLAG_NO_MIP_STORAGE_ALLOC.
|
|
for (auto &Mip : this->Mips)
|
|
{
|
|
OutDDS.Append(Mip.Data, Mip.DataSize);
|
|
}
|
|
|
|
return EDDSError::OK;
|
|
}
|
|
|
|
void FDDSFile::ConvertRGBXtoRGBA()
|
|
{
|
|
// change X8 formats to A8 :
|
|
if ( DXGIFormat == EDXGIFormat::R8G8B8X8 )
|
|
{
|
|
DXGIFormat = EDXGIFormat::R8G8B8A8_UNORM_SRGB;
|
|
}
|
|
else if ( DXGIFormat == EDXGIFormat::B8G8R8X8_UNORM )
|
|
{
|
|
DXGIFormat = EDXGIFormat::B8G8R8A8_UNORM;
|
|
}
|
|
else if ( DXGIFormat == EDXGIFormat::B8G8R8X8_UNORM_SRGB )
|
|
{
|
|
DXGIFormat = EDXGIFormat::B8G8R8A8_UNORM_SRGB;
|
|
}
|
|
else if ( DXGIFormat == EDXGIFormat::B8G8R8X8_TYPELESS )
|
|
{
|
|
DXGIFormat = EDXGIFormat::B8G8R8A8_TYPELESS;
|
|
}
|
|
else
|
|
{
|
|
// not an X8 format
|
|
return;
|
|
}
|
|
|
|
// stuff 255 in A :
|
|
for (auto& Mip : Mips)
|
|
{
|
|
// Loop over pixels. Data in DDS mips is densely packed!
|
|
uint8* PixelData = Mip.Data;
|
|
const int64 DataSize = Mip.DataSize;
|
|
for (int64 i = 0; i < DataSize; i += 4)
|
|
{
|
|
PixelData[3] = 0xFF;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDDSFile::ConvertChannelOrder(EChannelOrder InTargetOrder)
|
|
{
|
|
// Is this one of the few RGBA/BGRA formats we can convert?
|
|
int FormatIndex = DXGIFormatFindInTable(DXGIFormatRGBATable, DXGIFormat);
|
|
if (FormatIndex < 0)
|
|
{
|
|
// Nope, not one of those formats, leave it alone.
|
|
return;
|
|
}
|
|
|
|
// Figure out which channel order we currently are based on whether the index
|
|
// is even or odd.
|
|
EChannelOrder CurrentOrder = (FormatIndex & 1) ? EChannelOrder::BGRA : EChannelOrder::RGBA;
|
|
if (CurrentOrder == InTargetOrder)
|
|
{
|
|
// Channel order is already target order, nothing to do!
|
|
return;
|
|
}
|
|
|
|
// Change format to the opposite in the pair, then fix the pixel data
|
|
// by swapping R and B.
|
|
DXGIFormat = DXGIFormatRGBATable[FormatIndex ^ 1];
|
|
|
|
for (auto& Mip : Mips)
|
|
{
|
|
// Loop over pixels. Data in DDS mips is densely packed!
|
|
uint8* PixelData = Mip.Data;
|
|
const int64 DataSize = Mip.DataSize;
|
|
for (int64 i = 0; i < DataSize; i += 4)
|
|
{
|
|
// RGBA <-> BGRA swaps B and R and leaves G and A alone.
|
|
// This can be done more efficiently but keep it simple here.
|
|
uint8 t = PixelData[i];
|
|
PixelData[i] = PixelData[i + 2];
|
|
PixelData[i + 2] = t;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDDSFile::FillMip(const FImageView & FromImage, int MipIndex)
|
|
{
|
|
check( MipIndex < Mips.Num() );
|
|
const FDDSMip & ToMip = Mips[MipIndex];
|
|
|
|
check( FromImage.GetNumPixels() == ToMip.Width * ToMip.Height * (int64) ToMip.Depth );
|
|
check( FromImage.GetImageSizeBytes() == ToMip.DataSize );
|
|
|
|
memcpy(ToMip.Data,FromImage.RawData,ToMip.DataSize);
|
|
}
|
|
|
|
// Blit pixels from DDS mip to Image
|
|
// return false if no pixel format conversion is supported
|
|
bool FDDSFile::GetMipImage(const FImageView & ToImage, int MipIndex) const
|
|
{
|
|
check( MipIndex < Mips.Num() );
|
|
const FDDSMip & FromMip = Mips[MipIndex];
|
|
|
|
check( ToImage.GetNumPixels() == FromMip.Width * FromMip.Height * (int64) FromMip.Depth );
|
|
|
|
// before BlitMip you should have done ConvertChannelOrder to BGRA and ConvertRGBXtoRGBA
|
|
// so we should only see BGRA (no RGBA or BGRX) here
|
|
|
|
if ( ToImage.Format == ERawImageFormat::BGRA8 && DXGIFormat == EDXGIFormat::B8G8R8 )
|
|
{
|
|
const uint8 * FmPtr = FromMip.Data;
|
|
uint8 * ToPtr = (uint8 *)ToImage.RawData;
|
|
int64 Count = ToImage.GetNumPixels();
|
|
check( ToImage.GetImageSizeBytes() == Count*4 );
|
|
check( FromMip.DataSize == Count*3 );
|
|
while(Count--)
|
|
{
|
|
*ToPtr++ = *FmPtr++;
|
|
*ToPtr++ = *FmPtr++;
|
|
*ToPtr++ = *FmPtr++;
|
|
*ToPtr++ = 0xFF;
|
|
}
|
|
return true;
|
|
}
|
|
else if ( ToImage.Format == ERawImageFormat::BGRA8 && DXGIFormat == EDXGIFormat::R8G8B8 )
|
|
{
|
|
const uint8 * FmPtr = FromMip.Data;
|
|
uint8 * ToPtr = (uint8 *)ToImage.RawData;
|
|
int64 Count = ToImage.GetNumPixels();
|
|
check( ToImage.GetImageSizeBytes() == Count*4 );
|
|
check( FromMip.DataSize == Count*3 );
|
|
while(Count--)
|
|
{
|
|
*ToPtr++ = FmPtr[2];
|
|
*ToPtr++ = FmPtr[1];
|
|
*ToPtr++ = FmPtr[0];
|
|
*ToPtr++ = 0xFF;
|
|
FmPtr += 3;
|
|
}
|
|
return true;
|
|
}
|
|
else if ( ToImage.Format == ERawImageFormat::BGRA8 && (
|
|
DXGIFormat == EDXGIFormat::R8G8_TYPELESS ||
|
|
DXGIFormat == EDXGIFormat::R8G8_UNORM ||
|
|
DXGIFormat == EDXGIFormat::R8G8_UINT ||
|
|
DXGIFormat == EDXGIFormat::R8G8_SNORM ||
|
|
DXGIFormat == EDXGIFormat::R8G8_SINT ) )
|
|
{
|
|
const uint8 * FmPtr = FromMip.Data;
|
|
uint8 * ToPtr = (uint8 *)ToImage.RawData;
|
|
int64 Count = ToImage.GetNumPixels();
|
|
check( ToImage.GetImageSizeBytes() == Count*4 );
|
|
check( FromMip.DataSize == Count*2 );
|
|
while(Count--)
|
|
{
|
|
*ToPtr++ = 0;
|
|
*ToPtr++ = FmPtr[1];
|
|
*ToPtr++ = FmPtr[0];
|
|
*ToPtr++ = 0xFF;
|
|
FmPtr += 2;
|
|
}
|
|
check( ToPtr == (uint8 *)ToImage.RawData + ToImage.GetImageSizeBytes() );
|
|
check( FmPtr == FromMip.Data + FromMip.DataSize );
|
|
return true;
|
|
}
|
|
else if ( ToImage.GetImageSizeBytes() == FromMip.DataSize )
|
|
{
|
|
bool bIsExactMatch;
|
|
ERawImageFormat::Type DXGIToRawFormat = DXGIFormatGetClosestRawFormat(DXGIFormat,&bIsExactMatch);
|
|
|
|
if ( DXGIToRawFormat == ToImage.Format )
|
|
{
|
|
if ( ! bIsExactMatch )
|
|
{
|
|
UE_LOG(LogDDSFile, Warning, TEXT("DDS BlitMip DXGIFormat isn't exact match fmt %d=(%s) to (%s)"), \
|
|
int(DXGIFormat), DXGIFormatGetName(DXGIFormat),ERawImageFormat::GetName(ToImage.Format));
|
|
// but do it anyway
|
|
}
|
|
|
|
memcpy(ToImage.RawData,FromMip.Data,FromMip.DataSize);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// formats mismatch but are same size
|
|
// very speculative, try it anyway, but log :
|
|
|
|
UE_LOG(LogDDSFile, Warning, TEXT("DDS BlitMip DXGIFormat incompatible but same size! may be junk! fmt %d=(%s) to (%s)"), \
|
|
int(DXGIFormat), DXGIFormatGetName(DXGIFormat),ERawImageFormat::GetName(ToImage.Format));
|
|
|
|
memcpy(ToImage.RawData,FromMip.Data,FromMip.DataSize);
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no supported conversion
|
|
|
|
UE_LOG(LogDDSFile, Warning, TEXT("DDS BlitMip DXGIFormat cannot blit! %d (%s) to (%s)"), \
|
|
int(DXGIFormat), DXGIFormatGetName(DXGIFormat),ERawImageFormat::GetName(ToImage.Format));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
struct FDXGIFormatRawFormatMapping
|
|
{
|
|
EDXGIFormat DXGIFormat;
|
|
ERawImageFormat::Type RawFormat;
|
|
bool bIsExactMatch;
|
|
};
|
|
|
|
FDXGIFormatRawFormatMapping DXGIRawFormatMap[] =
|
|
{
|
|
{ EDXGIFormat::R32G32B32A32_FLOAT, ERawImageFormat::RGBA32F, true },
|
|
{ EDXGIFormat::R16G16B16A16_FLOAT, ERawImageFormat::RGBA16F, true },
|
|
{ EDXGIFormat::R32_FLOAT, ERawImageFormat::R32F, true },
|
|
{ EDXGIFormat::R16_FLOAT, ERawImageFormat::R16F, true },
|
|
|
|
// R32G32B32_FLOAT not supported
|
|
// R32G32_FLOAT not supported
|
|
// R16G16_FLOAT not supported
|
|
|
|
{ EDXGIFormat::R16G16B16A16_UNORM, ERawImageFormat::RGBA16, true },
|
|
{ EDXGIFormat::R16G16B16A16_UINT, ERawImageFormat::RGBA16, true },
|
|
{ EDXGIFormat::R16G16B16A16_SNORM, ERawImageFormat::RGBA16, false },
|
|
{ EDXGIFormat::R16G16B16A16_SINT, ERawImageFormat::RGBA16, false },
|
|
|
|
{ EDXGIFormat::R8G8B8A8_TYPELESS, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::R8G8B8A8_UNORM, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::R8G8B8A8_UNORM_SRGB, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::R8G8B8A8_UINT, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::R8G8B8A8_SNORM, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::R8G8B8A8_SINT, ERawImageFormat::BGRA8, false },
|
|
|
|
{ EDXGIFormat::B8G8R8A8_UNORM, ERawImageFormat::BGRA8, true },
|
|
{ EDXGIFormat::B8G8R8X8_UNORM, ERawImageFormat::BGRA8, false },
|
|
|
|
{ EDXGIFormat::B8G8R8A8_TYPELESS, ERawImageFormat::BGRA8, true },
|
|
{ EDXGIFormat::B8G8R8A8_UNORM_SRGB, ERawImageFormat::BGRA8, true },
|
|
{ EDXGIFormat::B8G8R8X8_TYPELESS, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::B8G8R8X8_UNORM_SRGB, ERawImageFormat::BGRA8, false },
|
|
|
|
{ EDXGIFormat::R32_UINT, ERawImageFormat::G16, false },
|
|
{ EDXGIFormat::R32_SINT, ERawImageFormat::G16, false },
|
|
|
|
{ EDXGIFormat::R16_TYPELESS, ERawImageFormat::G16, true },
|
|
{ EDXGIFormat::R16_UNORM, ERawImageFormat::G16, true },
|
|
{ EDXGIFormat::R16_UINT, ERawImageFormat::G16, true },
|
|
{ EDXGIFormat::R16_SNORM, ERawImageFormat::G16, false },
|
|
{ EDXGIFormat::R16_SINT, ERawImageFormat::G16, false },
|
|
|
|
{ EDXGIFormat::R8_TYPELESS, ERawImageFormat::G8, true },
|
|
{ EDXGIFormat::R8_UNORM, ERawImageFormat::G8, true },
|
|
{ EDXGIFormat::R8_UINT, ERawImageFormat::G8, true },
|
|
{ EDXGIFormat::R8_SNORM, ERawImageFormat::G8, false },
|
|
{ EDXGIFormat::R8_SINT, ERawImageFormat::G8, false },
|
|
{ EDXGIFormat::A8_UNORM, ERawImageFormat::G8, false },
|
|
|
|
{ EDXGIFormat::B8G8R8, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::R8G8B8, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::R8G8B8X8, ERawImageFormat::BGRA8, false },
|
|
|
|
{ EDXGIFormat::R8G8_TYPELESS, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::R8G8_UNORM, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::R8G8_UINT, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::R8G8_SNORM, ERawImageFormat::BGRA8, false },
|
|
{ EDXGIFormat::R8G8_SINT, ERawImageFormat::BGRA8, false },
|
|
|
|
// terminates list :
|
|
{ EDXGIFormat::UNKNOWN, ERawImageFormat::Invalid, false}
|
|
};
|
|
|
|
ERawImageFormat::Type DXGIFormatGetClosestRawFormat(EDXGIFormat fmt, bool * pIsExactMatch)
|
|
{
|
|
for(const FDXGIFormatRawFormatMapping * Mapping = DXGIRawFormatMap;Mapping->DXGIFormat != EDXGIFormat::UNKNOWN;++Mapping)
|
|
{
|
|
if ( Mapping->DXGIFormat == fmt )
|
|
{
|
|
if ( pIsExactMatch )
|
|
{
|
|
*pIsExactMatch = Mapping->bIsExactMatch;
|
|
}
|
|
return Mapping->RawFormat;
|
|
}
|
|
}
|
|
|
|
return ERawImageFormat::Invalid;
|
|
}
|
|
|
|
EDXGIFormat DXGIFormatFromRawFormat(ERawImageFormat::Type RawFormat,EGammaSpace GammaSpace)
|
|
{
|
|
switch(RawFormat)
|
|
{
|
|
case ERawImageFormat::G8: return EDXGIFormat::R8_UNORM; // would like to encode Gamma, but no way to do so
|
|
case ERawImageFormat::BGRA8:
|
|
{
|
|
if ( GammaSpace == EGammaSpace::Linear)
|
|
return EDXGIFormat::B8G8R8A8_UNORM;
|
|
else
|
|
return EDXGIFormat::B8G8R8A8_UNORM_SRGB;
|
|
}
|
|
case ERawImageFormat::BGRE8: return EDXGIFormat::B8G8R8A8_UNORM; // no way to indicate this is BGRE
|
|
case ERawImageFormat::RGBA16: return EDXGIFormat::R16G16B16A16_UNORM;
|
|
case ERawImageFormat::RGBA16F: return EDXGIFormat::R16G16B16A16_FLOAT;
|
|
case ERawImageFormat::RGBA32F: return EDXGIFormat::R32G32B32A32_FLOAT;
|
|
case ERawImageFormat::G16: return EDXGIFormat::R16_UNORM;
|
|
case ERawImageFormat::R16F: return EDXGIFormat::R16_FLOAT;
|
|
case ERawImageFormat::R32F: return EDXGIFormat::R32_FLOAT;
|
|
default:
|
|
return EDXGIFormat::UNKNOWN;
|
|
}
|
|
}
|
|
|
|
} } // end UE::DDS namespace
|