HiresTextures: Support loading uncompressed .DDS files

Will load files with formats RGBA8, RGBX8, BGRA8, BGRX8, RGB8.
This commit is contained in:
Stenzek 2017-04-21 20:50:17 +10:00
parent 20cbef8e9f
commit de348fc952
1 changed files with 154 additions and 62 deletions

View File

@ -7,8 +7,10 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <functional>
#include "Common/Align.h"
#include "Common/FileUtil.h"
#include "Common/Swap.h"
#include "VideoCommon/VideoConfig.h"
namespace
@ -70,28 +72,6 @@ struct DDS_PIXELFORMAT
#define DDS_HEADER_FLAGS_PITCH 0x00000008 // DDSD_PITCH
#define DDS_HEADER_FLAGS_LINEARSIZE 0x00080000 // DDSD_LINEARSIZE
#define DDS_HEIGHT 0x00000002 // DDSD_HEIGHT
#define DDS_WIDTH 0x00000004 // DDSD_WIDTH
#define DDS_SURFACE_FLAGS_TEXTURE 0x00001000 // DDSCAPS_TEXTURE
#define DDS_SURFACE_FLAGS_MIPMAP 0x00400008 // DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
#define DDS_SURFACE_FLAGS_CUBEMAP 0x00000008 // DDSCAPS_COMPLEX
#define DDS_CUBEMAP_POSITIVEX 0x00000600 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
#define DDS_CUBEMAP_NEGATIVEX 0x00000a00 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
#define DDS_CUBEMAP_POSITIVEY 0x00001200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
#define DDS_CUBEMAP_NEGATIVEY 0x00002200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
#define DDS_CUBEMAP_POSITIVEZ 0x00004200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
#define DDS_CUBEMAP_NEGATIVEZ 0x00008200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ
#define DDS_CUBEMAP_ALLFACES \
(DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_NEGATIVEX | DDS_CUBEMAP_POSITIVEY | DDS_CUBEMAP_NEGATIVEY | \
DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ)
#define DDS_CUBEMAP 0x00000200 // DDSCAPS2_CUBEMAP
#define DDS_FLAGS_VOLUME 0x00200000 // DDSCAPS2_VOLUME
// Subset here matches D3D10_RESOURCE_DIMENSION and D3D11_RESOURCE_DIMENSION
enum DDS_RESOURCE_DIMENSION
{
@ -100,26 +80,6 @@ enum DDS_RESOURCE_DIMENSION
DDS_DIMENSION_TEXTURE3D = 4,
};
// Subset here matches D3D10_RESOURCE_MISC_FLAG and D3D11_RESOURCE_MISC_FLAG
enum DDS_RESOURCE_MISC_FLAG
{
DDS_RESOURCE_MISC_TEXTURECUBE = 0x4L,
};
enum DDS_MISC_FLAGS2
{
DDS_MISC_FLAGS2_ALPHA_MODE_MASK = 0x7L,
};
enum DDS_ALPHA_MODE
{
DDS_ALPHA_MODE_UNKNOWN = 0,
DDS_ALPHA_MODE_STRAIGHT = 1,
DDS_ALPHA_MODE_PREMULTIPLIED = 2,
DDS_ALPHA_MODE_OPAQUE = 3,
DDS_ALPHA_MODE_CUSTOM = 4,
};
struct DDS_HEADER
{
uint32_t dwSize;
@ -152,7 +112,26 @@ struct DDS_HEADER_DXT10
static_assert(sizeof(DDS_HEADER) == 124, "DDS Header size mismatch");
static_assert(sizeof(DDS_HEADER_DXT10) == 20, "DDS DX10 Extended Header size mismatch");
} // namespace
constexpr DDS_PIXELFORMAT DDSPF_A8R8G8B8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000};
constexpr DDS_PIXELFORMAT DDSPF_X8R8G8B8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000};
constexpr DDS_PIXELFORMAT DDSPF_A8B8G8R8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000};
constexpr DDS_PIXELFORMAT DDSPF_X8B8G8R8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000};
constexpr DDS_PIXELFORMAT DDSPF_R8G8B8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000};
// End of Microsoft code from DDS.h.
bool DDSPixelFormatMatches(const DDS_PIXELFORMAT& pf1, const DDS_PIXELFORMAT& pf2)
{
return std::tie(pf1.dwSize, pf1.dwFlags, pf1.dwFourCC, pf1.dwRGBBitCount, pf1.dwRBitMask,
pf1.dwGBitMask, pf1.dwGBitMask, pf1.dwBBitMask, pf1.dwABitMask) ==
std::tie(pf2.dwSize, pf2.dwFlags, pf2.dwFourCC, pf2.dwRGBBitCount, pf2.dwRBitMask,
pf2.dwGBitMask, pf2.dwGBitMask, pf2.dwBBitMask, pf2.dwABitMask);
}
struct DDSLoadInfo
{
@ -165,14 +144,95 @@ struct DDSLoadInfo
size_t first_mip_offset = 0;
size_t first_mip_size = 0;
u32 first_mip_row_length = 0;
std::function<void(HiresTexture::Level*)> conversion_function;
};
static u32 GetBlockCount(u32 extent, u32 block_size)
u32 GetBlockCount(u32 extent, u32 block_size)
{
return std::max(Common::AlignUp(extent, block_size) / block_size, 1u);
}
static bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info)
HiresTexture::ImageDataPointer AllocateLevelData(size_t size)
{
return HiresTexture::ImageDataPointer(new u8[size], [](u8* data) { delete[] data; });
}
void ConvertTexture_X8B8G8R8(HiresTexture::Level* level)
{
u8* data_ptr = level->data.get();
for (u32 row = 0; row < level->height; row++)
{
for (u32 x = 0; x < level->row_length; x++)
{
// Set alpha channel to full intensity.
data_ptr[3] = 0xFF;
data_ptr += sizeof(u32);
}
}
}
void ConvertTexture_A8R8G8B8(HiresTexture::Level* level)
{
u8* data_ptr = level->data.get();
for (u32 row = 0; row < level->height; row++)
{
for (u32 x = 0; x < level->row_length; x++)
{
// Byte swap ABGR -> RGBA
u32 val;
std::memcpy(&val, data_ptr, sizeof(val));
val = ((val & 0xFF00FF00) | ((val >> 16) & 0xFF) | ((val << 16) & 0xFF0000));
std::memcpy(data_ptr, &val, sizeof(u32));
data_ptr += sizeof(u32);
}
}
}
void ConvertTexture_X8R8G8B8(HiresTexture::Level* level)
{
u8* data_ptr = level->data.get();
for (u32 row = 0; row < level->height; row++)
{
for (u32 x = 0; x < level->row_length; x++)
{
// Byte swap XBGR -> RGBX, and set alpha to full intensity.
u32 val;
std::memcpy(&val, data_ptr, sizeof(val));
val = ((val & 0x0000FF00) | ((val >> 16) & 0xFF) | ((val << 16) & 0xFF0000)) | 0xFF000000;
std::memcpy(data_ptr, &val, sizeof(u32));
data_ptr += sizeof(u32);
}
}
}
void ConvertTexture_R8G8B8(HiresTexture::Level* level)
{
// Have to reallocate the buffer for this one, since the data in the file
// does not have an alpha byte.
level->data_size = level->row_length * level->height * sizeof(u32);
HiresTexture::ImageDataPointer rgb_data = AllocateLevelData(level->data_size);
std::swap(level->data, rgb_data);
const u8* rgb_data_ptr = rgb_data.get();
u8* data_ptr = level->data.get();
for (u32 row = 0; row < level->height; row++)
{
for (u32 x = 0; x < level->row_length; x++)
{
// This is BGR in memory.
u32 val;
std::memcpy(&val, rgb_data_ptr, sizeof(val));
val = ((val & 0x0000FF00) | ((val >> 16) & 0xFF) | ((val << 16) & 0xFF0000)) | 0xFF000000;
std::memcpy(data_ptr, &val, sizeof(u32));
data_ptr += sizeof(u32);
rgb_data_ptr += 3;
}
}
}
bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info)
{
// Exit as early as possible for non-DDS textures, since all extensions are currently
// passed through this function.
@ -265,8 +325,35 @@ static bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info)
}
else
{
// TODO: Support RGBA8 and friends.
return false;
if (DDSPixelFormatMatches(header.ddspf, DDSPF_A8R8G8B8))
{
info->conversion_function = ConvertTexture_A8R8G8B8;
}
else if (DDSPixelFormatMatches(header.ddspf, DDSPF_X8R8G8B8))
{
info->conversion_function = ConvertTexture_X8R8G8B8;
}
else if (DDSPixelFormatMatches(header.ddspf, DDSPF_X8B8G8R8))
{
info->conversion_function = ConvertTexture_X8B8G8R8;
}
else if (DDSPixelFormatMatches(header.ddspf, DDSPF_R8G8B8))
{
info->conversion_function = ConvertTexture_R8G8B8;
}
else if (DDSPixelFormatMatches(header.ddspf, DDSPF_A8B8G8R8))
{
// This format is already in RGBA order, so no conversion necessary.
}
else
{
return false;
}
// All these formats are RGBA, just with byte swapping.
info->format = HostTextureFormat::RGBA8;
info->block_size = 1;
info->bytes_per_block = header.ddspf.dwRGBBitCount / 8;
}
// We also need to ensure the backend supports these formats natively before loading them,
@ -310,27 +397,32 @@ static bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info)
return true;
}
static bool ReadMipLevel(HiresTexture::Level& level, File::IOFile& file, u32 width, u32 height,
HostTextureFormat format, u32 row_length, size_t size)
bool ReadMipLevel(HiresTexture::Level* level, File::IOFile& file, const DDSLoadInfo& info,
u32 width, u32 height, u32 row_length, size_t size)
{
// Copy to the final storage location. The deallocator here is simple, nothing extra is
// needed, compared to the SOIL-based loader.
level.width = width;
level.height = height;
level.format = format;
level.row_length = row_length;
level.data_size = size;
level.data =
HiresTexture::ImageDataPointer(new u8[level.data_size], [](u8* data) { delete[] data; });
if (!file.ReadBytes(level.data.get(), level.data_size))
level->width = width;
level->height = height;
level->format = info.format;
level->row_length = row_length;
level->data_size = size;
level->data = AllocateLevelData(level->data_size);
if (!file.ReadBytes(level->data.get(), level->data_size))
{
level.data.reset();
level->data.reset();
return false;
}
// Apply conversion function for uncompressed textures.
if (info.conversion_function)
info.conversion_function(level);
return true;
}
} // namespace
bool HiresTexture::LoadDDSTexture(HiresTexture* tex, const std::string& filename)
{
File::IOFile file;
@ -345,8 +437,8 @@ bool HiresTexture::LoadDDSTexture(HiresTexture* tex, const std::string& filename
// Read first mip level, as it may have a custom pitch.
Level first_level;
if (!file.Seek(info.first_mip_offset, SEEK_SET) ||
!ReadMipLevel(first_level, file, info.width, info.height, info.format,
info.first_mip_row_length, info.first_mip_size))
!ReadMipLevel(&first_level, file, info, info.width, info.height, info.first_mip_row_length,
info.first_mip_size))
{
return false;
}
@ -368,7 +460,7 @@ bool HiresTexture::LoadDDSTexture(HiresTexture* tex, const std::string& filename
u32 mip_row_length = blocks_wide * info.block_size;
size_t mip_size = blocks_wide * static_cast<size_t>(info.bytes_per_block) * blocks_high;
Level level;
if (!ReadMipLevel(level, file, mip_width, mip_height, info.format, mip_row_length, mip_size))
if (!ReadMipLevel(&level, file, info, mip_width, mip_height, mip_row_length, mip_size))
break;
tex->m_levels.push_back(std::move(level));
@ -389,6 +481,6 @@ bool HiresTexture::LoadDDSTexture(Level& level, const std::string& filename)
if (!ParseDDSHeader(file, &info))
return false;
return ReadMipLevel(level, file, info.width, info.height, info.format, info.first_mip_row_length,
return ReadMipLevel(&level, file, info, info.width, info.height, info.first_mip_row_length,
info.first_mip_size);
}