Created
March 7, 2025 01:45
-
-
Save rygorous/46839aa005461ae34419c7ebefadb869 to your computer and use it in GitHub Desktop.
rr_dds loader/writer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//=================================================== | |
// Oodle2 DDS Tool | |
// (C) Copyright 1994-2022 Epic Games Tools LLC | |
//=================================================== | |
#ifndef RR_DDS_INCLUDED | |
#define RR_DDS_INCLUDED | |
#include <stdint.h> | |
#include <stddef.h> | |
// | |
// Interface | |
// | |
enum { | |
RR_DDS_MAX_MIPS = 16, | |
RR_DDS_FLAGS_NONE = 0, | |
RR_DDS_FLAGS_CUBEMAP = 1, // texture is a cubemap | |
RR_DDS_FLAGS_NO_MIP_STORAGE_ALLOC = 2, // mip storage wasn't allocated by, and isn't owned by, rr_dds (just pointing into data elsewhere) | |
RR_DDS_FLAGS_WAS_D3D9 = 4, // loaded from a D3D9-format .DDS file, unknown whether 8-bit pixels are sRGB or not | |
}; | |
// List of supported formats | |
// we support nope out on some of the more exotic formats entirely where they're unlikely | |
// to be useful for any assets | |
// fields: name, ID, bytes per unit (unit=1 texel for RGB, 1 block for BCN) | |
#define RR_DDS_FORMAT_LIST \ | |
RGBFMT(RR_DXGI_FORMAT_UNKNOWN, 0, 0) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32B32A32_TYPELESS, 1, 16) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32B32A32_FLOAT, 2, 16) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32B32A32_UINT, 3, 16) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32B32A32_SINT, 4, 16) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32B32_TYPELESS, 5, 12) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32B32_FLOAT, 6, 12) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32B32_UINT, 7, 12) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32B32_SINT, 8, 12) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16B16A16_TYPELESS, 9, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16B16A16_FLOAT, 10, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16B16A16_UNORM, 11, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16B16A16_UINT, 12, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16B16A16_SNORM, 13, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16B16A16_SINT, 14, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32_TYPELESS, 15, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32_FLOAT, 16, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32_UINT, 17, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G32_SINT, 18, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R32G8X24_TYPELESS, 19, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_D32_FLOAT_S8X24_UINT, 20, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS, 21, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_X32_TYPELESS_G8X24_UINT, 22, 8) \ | |
RGBFMT(RR_DXGI_FORMAT_R10G10B10A2_TYPELESS, 23, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R10G10B10A2_UNORM, 24, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R10G10B10A2_UINT, 25, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R11G11B10_FLOAT, 26, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R8G8B8A8_TYPELESS, 27, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R8G8B8A8_UNORM, 28, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, 29, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R8G8B8A8_UINT, 30, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R8G8B8A8_SNORM, 31, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R8G8B8A8_SINT, 32, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16_TYPELESS, 33, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16_FLOAT, 34, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16_UNORM, 35, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16_UINT, 36, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16_SNORM, 37, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R16G16_SINT, 38, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R32_TYPELESS, 39, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_D32_FLOAT, 40, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R32_FLOAT, 41, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R32_UINT, 42, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R32_SINT, 43, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R24G8_TYPELESS, 44, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_D24_UNORM_S8_UINT, 45, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R24_UNORM_X8_TYPELESS, 46, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_X24_TYPELESS_G8_UINT, 47, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R8G8_TYPELESS, 48, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_R8G8_UNORM, 49, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_R8G8_UINT, 50, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_R8G8_SNORM, 51, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_R8G8_SINT, 52, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_R16_TYPELESS, 53, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_R16_FLOAT, 54, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_D16_UNORM, 55, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_R16_UNORM, 56, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_R16_UINT, 57, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_R16_SNORM, 58, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_R16_SINT, 59, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_R8_TYPELESS, 60, 1) \ | |
RGBFMT(RR_DXGI_FORMAT_R8_UNORM, 61, 1) \ | |
RGBFMT(RR_DXGI_FORMAT_R8_UINT, 62, 1) \ | |
RGBFMT(RR_DXGI_FORMAT_R8_SNORM, 63, 1) \ | |
RGBFMT(RR_DXGI_FORMAT_R8_SINT, 64, 1) \ | |
RGBFMT(RR_DXGI_FORMAT_A8_UNORM, 65, 1) \ | |
ODDFMT(RR_DXGI_FORMAT_R1_UNORM, 66) \ | |
RGBFMT(RR_DXGI_FORMAT_R9G9B9E5_SHAREDEXP, 67, 4) \ | |
ODDFMT(RR_DXGI_FORMAT_R8G8_B8G8_UNORM, 68) \ | |
ODDFMT(RR_DXGI_FORMAT_G8R8_G8B8_UNORM, 69) \ | |
BCNFMT(RR_DXGI_FORMAT_BC1_TYPELESS, 70, 8) \ | |
BCNFMT(RR_DXGI_FORMAT_BC1_UNORM, 71, 8) \ | |
BCNFMT(RR_DXGI_FORMAT_BC1_UNORM_SRGB, 72, 8) \ | |
BCNFMT(RR_DXGI_FORMAT_BC2_TYPELESS, 73, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC2_UNORM, 74, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC2_UNORM_SRGB, 75, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC3_TYPELESS, 76, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC3_UNORM, 77, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC3_UNORM_SRGB, 78, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC4_TYPELESS, 79, 8) \ | |
BCNFMT(RR_DXGI_FORMAT_BC4_UNORM, 80, 8) \ | |
BCNFMT(RR_DXGI_FORMAT_BC4_SNORM, 81, 8) \ | |
BCNFMT(RR_DXGI_FORMAT_BC5_TYPELESS, 82, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC5_UNORM, 83, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC5_SNORM, 84, 16) \ | |
RGBFMT(RR_DXGI_FORMAT_B5G6R5_UNORM, 85, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_B5G5R5A1_UNORM, 86, 2) \ | |
RGBFMT(RR_DXGI_FORMAT_B8G8R8A8_UNORM, 87, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_B8G8R8X8_UNORM, 88, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM, 89, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_B8G8R8A8_TYPELESS, 90, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, 91, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_B8G8R8X8_TYPELESS, 92, 4) \ | |
RGBFMT(RR_DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, 93, 4) \ | |
BCNFMT(RR_DXGI_FORMAT_BC6H_TYPELESS, 94, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC6H_UF16, 95, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC6H_SF16, 96, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC7_TYPELESS, 97, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC7_UNORM, 98, 16) \ | |
BCNFMT(RR_DXGI_FORMAT_BC7_UNORM_SRGB, 99, 16) \ | |
ODDFMT(RR_DXGI_FORMAT_AYUV, 100) \ | |
ODDFMT(RR_DXGI_FORMAT_Y410, 101) \ | |
ODDFMT(RR_DXGI_FORMAT_Y416, 102) \ | |
ODDFMT(RR_DXGI_FORMAT_NV12, 103) \ | |
ODDFMT(RR_DXGI_FORMAT_P010, 104) \ | |
ODDFMT(RR_DXGI_FORMAT_P016, 105) \ | |
ODDFMT(RR_DXGI_FORMAT_420_OPAQUE, 106) \ | |
ODDFMT(RR_DXGI_FORMAT_YUY2, 107) \ | |
ODDFMT(RR_DXGI_FORMAT_Y210, 108) \ | |
ODDFMT(RR_DXGI_FORMAT_Y216, 109) \ | |
ODDFMT(RR_DXGI_FORMAT_NV11, 110) \ | |
ODDFMT(RR_DXGI_FORMAT_AI44, 111) \ | |
ODDFMT(RR_DXGI_FORMAT_IA44, 112) \ | |
ODDFMT(RR_DXGI_FORMAT_P8, 113) \ | |
ODDFMT(RR_DXGI_FORMAT_A8P8, 114) \ | |
RGBFMT(RR_DXGI_FORMAT_B4G4R4A4_UNORM, 115,2) \ | |
ODDFMT(RR_DXGI_FORMAT_P208, 130) \ | |
ODDFMT(RR_DXGI_FORMAT_V208, 131) \ | |
ODDFMT(RR_DXGI_FORMAT_V408, 132) \ | |
/* end */ | |
enum rr_dxgi_format_t { | |
#define RGBFMT(name,id,bypu) name = id, | |
#define BCNFMT(name,id,bypu) name = id, | |
#define ODDFMT(name,id) name = id, | |
RR_DDS_FORMAT_LIST | |
#undef RGBFMT | |
#undef BCNFMT | |
#undef ODDFMT | |
}; | |
enum rr_dds_err_t { | |
RR_DDS_ERR_OK = 0, // no error | |
RR_DDS_ERR_OUT_OF_MEMORY = -1, // out of memory allocating rr_dds structure or payload | |
RR_DDS_ERR_UNEXPECTED_EOF = -2, // unexpected end of file while reading DDS file | |
RR_DDS_ERR_NOT_A_DDS = -3, // no valid DDS header found | |
RR_DDS_ERR_BAD_RESOURCE_DIMENSION = -4, // resource dimension in DDS is not 1D, 2D or 3D | |
RR_DDS_ERR_BAD_PIXEL_FORMAT = -5, // malformed or unsupported pixel format | |
RR_DDS_ERR_BAD_IMAGE_DIMENSION = -6, // image dimensions (width, height, depth, array size, mip count) are outside supported range | |
RR_DDS_ERR_BAD_MIPMAP_COUNT = -7, // invalid mipmap count for given image dimensions | |
RR_DDS_ERR_BAD_CUBEMAP = -8, // malformed cubemap: faces are not square or number of faces per cube is not 6 | |
RR_DDS_ERR_OPEN_FAILED = -9, // failed to open file | |
RR_DDS_ERR_WRITE_FAILED = -10, // writing to file failed | |
RR_DDS_ERR_BAD_VERSION = -11, // malformed format version argument | |
}; | |
enum rr_dds_version_t { | |
RR_DDS_VERSION_AUTO = 0, // Automatic: write D3D9-supported formats as D3D9-style DDS, other formats use D3D10. | |
RR_DDS_VERSION_D3D9 = 1, // Write a D3D9-version DDS file, even when doing so loses metadata like sRGB-ness flags | |
RR_DDS_VERSION_D3D10 = 2, // Write a D3D10-version DDS file even when not necessary. | |
}; | |
// One of these for each mip of a DDS | |
struct rr_dds_mip_t { | |
uint32_t width, height, depth; | |
size_t row_stride; | |
size_t slice_stride; | |
size_t data_size; | |
uint8_t* data; // just raw bytes; interpretation as per dxgi_format in rr_dds_t. | |
}; | |
// Describes a DDS file in memory | |
struct rr_dds_t { | |
int32_t dimension; // 1, 2 or 3. | |
uint32_t width; | |
uint32_t height; | |
uint32_t depth; | |
uint32_t num_mips; | |
// Array size is D3D10 runtime-style semantics for cubemaps, i.e. a single cube map has an array size of 6 | |
// with each of the array members being a square 2D image, and a N-element cube map array has an | |
// arary size of 6*N. | |
uint32_t array_size; | |
rr_dxgi_format_t dxgi_format; | |
uint32_t flags; | |
// Mips are ordered starting from mip 0 (full-size texture) decreasing in size; | |
// for arrays, we store the full mip chain for one array element before advancing | |
// to the next array element, and have (array_size * num_mips) mips total. | |
rr_dds_mip_t *mips; | |
// For rr_dds_t instances allocated through this code, storage for all mips is in | |
// a single allocation, and this stores the pointer and size. However, users (for | |
// writing) can pass RR_DDS_FLAGS_NO_MIP_STORAGE_ALLOC and allocate their own | |
// payload data, in which case it need not be a single allocation or contiguous. | |
// In this case mip_data_ptr and size are 0. | |
void * mip_data_ptr; | |
size_t mip_data_size; | |
}; | |
// Error context contains an error code and message. | |
struct rr_dds_err_ctx_t { | |
rr_dds_err_t err; // Error code (RR_DDS_ERR_OK on success). | |
char message[256]; // 0-terminated string, meant for user consumption. | |
}; | |
// Write stream struct. This is what the DDS writer uses to write to. | |
// Encapsulates a callback for the writing func and some scaffolding to help with | |
// integration. Derive from this or just put a pointer to your context data in the | |
// user pointer. | |
struct rr_dds_write_stream_t { | |
// Write given number of bytes. | |
// If an error occurs, report to the error context or handle internally. | |
void (*write)(rr_dds_write_stream_t *strm, const void *bytes, size_t byte_count); | |
// Pointer to an error context. You can use this for error reporting. | |
rr_dds_err_ctx_t *err_ctx; | |
// User pointer. Use for your own purposes. | |
void *user_ptr; | |
}; | |
// Returns true if the buffer is a DDS texture | |
bool rr_dds_is_dds(const void *in_buf, size_t in_len); | |
// Returns the name of a DXGI format | |
const char *rr_dds_format_get_name(rr_dxgi_format_t fmt); | |
// Returns whether a given pixel format is sRGB | |
bool rr_dds_format_is_sRGB(rr_dxgi_format_t fmt); | |
// Return the corresponding non-sRGB version of a pixel format if there is one | |
rr_dxgi_format_t rr_dds_format_remove_sRGB(rr_dxgi_format_t fmt); | |
// Return the corresponding sRGB version of a pixel format if there is one | |
rr_dxgi_format_t rr_dds_format_make_sRGB(rr_dxgi_format_t fmt); | |
// Initialize an error context | |
void rr_dds_err_ctx_init(rr_dds_err_ctx_t * err); | |
// Return pointer to dds structure, filled out with the data from the DDS file. | |
// Returns NULL on error. | |
// Reports error information to specified error context (must be non-NULL). | |
// Initialize error context with rr_dds_err_ctx_init. | |
rr_dds_t *rr_dds_read_with_err_ctx(const void *in_buf, size_t in_len, rr_dds_err_ctx_t *err_ctx); | |
// Return pointer to dds structure, filled out with the data from the DDS file. | |
// Returns NULL on error. | |
// Prints error message, if any, to stderr. | |
rr_dds_t *rr_dds_read(const void *in_buf, size_t in_len); | |
// Free's the rr_dds_t pointer and associated data returned from the rr_dds_read or rr_dds_new family of functions | |
void rr_dds_free(rr_dds_t *dds); | |
// Create an empty DDS structure (for writing DDS files typically) | |
// dimension is [1,3] (1D,2D,3D) | |
// Returns NULL on error. | |
// Reports error information to specified error context (must be non-NULL). | |
// Initialize error context with rr_dds_err_ctx_init. | |
rr_dds_t *rr_dds_new_with_err_ctx(int32_t dimension, uint32_t width, uint32_t height, uint32_t depth, uint32_t num_mips, uint32_t array_size, | |
rr_dxgi_format_t format, uint32_t flags, rr_dds_err_ctx_t *err_ctx); | |
// Create an empty DDS structure (for writing DDS files typically) | |
// dimension is [1,3] (1D,2D,3D) | |
// Returns NULL on error. | |
// Prints error message, if any, to stderr. | |
rr_dds_t *rr_dds_new(int dimension, uint32_t width, uint32_t height, uint32_t depth, uint32_t num_mips, uint32_t array_size, rr_dxgi_format_t format, uint32_t flags); | |
// Convenience version of the above to create a 2D texture with mip chain | |
// Returns NULL on error. | |
// Prints error message, if any, to stderr. | |
rr_dds_t *rr_dds_new_2d(uint32_t width, uint32_t height, uint32_t num_mips, rr_dxgi_format_t format, uint32_t flags); | |
// Writes out a DDS to a stream. | |
// Reports error information to specified error context (must be non-NULL). | |
// Initialize error context with rr_dds_err_ctx_init. | |
rr_dds_err_t rr_dds_write_stream_with_err_ctx(rr_dds_t *dds, rr_dds_write_stream_t *strm, rr_dds_version_t file_version, rr_dds_err_ctx_t *err_ctx); | |
// Writes out a DDS to a file. | |
// Reports error information to specified error context (must be non-NULL). | |
// Initialize error context with rr_dds_err_ctx_init. | |
rr_dds_err_t rr_dds_write_file_with_err_ctx(rr_dds_t *dds, const char *filename, rr_dds_version_t file_version, rr_dds_err_ctx_t *err_ctx); | |
// Write out a DDS to a file. | |
// Prints error message, if any, to stderr. | |
rr_dds_err_t rr_dds_write_file(rr_dds_t *dds, const char *filename, rr_dds_version_t file_version); | |
#endif // RR_DDS_INCLUDED | |
// | |
// Implementation | |
// | |
#ifdef RR_DDS_IMPLEMENTATION | |
#ifndef RR_DDS_IMPLEMENTED | |
#define RR_DDS_IMPLEMENTED | |
#include <stdio.h> | |
#include <stdarg.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#define RR_DDS_FOURCC(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) | |
enum { | |
RR_DDSD_CAPS = 0x00000001, | |
RR_DDSD_HEIGHT = 0x00000002, | |
RR_DDSD_WIDTH = 0x00000004, | |
RR_DDSD_PITCH = 0x00000008, | |
RR_DDSD_PIXELFORMAT = 0x00001000, | |
RR_DDSD_MIPMAPCOUNT = 0x00020000, | |
RR_DDSD_DEPTH = 0x00800000, | |
RR_DDPF_ALPHA = 0x00000002, | |
RR_DDPF_FOURCC = 0x00000004, | |
RR_DDPF_RGB = 0x00000040, | |
RR_DDPF_LUMINANCE = 0x00020000, | |
RR_DDPF_BUMPDUDV = 0x00080000, | |
RR_DDSCAPS_COMPLEX = 0x00000008, | |
RR_DDSCAPS_TEXTURE = 0x00001000, | |
RR_DDSCAPS_MIPMAP = 0x00400000, | |
RR_DDSCAPS2_CUBEMAP = 0x00000200, | |
RR_DDSCAPS2_VOLUME = 0x00200000, | |
RR_RESOURCE_DIMENSION_UNKNOWN = 0, | |
RR_RESOURCE_DIMENSION_BUFFER = 1, | |
RR_RESOURCE_DIMENSION_TEXTURE1D = 2, | |
RR_RESOURCE_DIMENSION_TEXTURE2D = 3, | |
RR_RESOURCE_DIMENSION_TEXTURE3D = 4, | |
RR_RESOURCE_MISC_TEXTURECUBE = 0x00000004, | |
RR_DDS_MAGIC = RR_DDS_FOURCC('D','D','S',' '), | |
RR_DX10_MAGIC = RR_DDS_FOURCC('D','X','1','0'), | |
}; | |
struct rr_dds_pixelformat_t { | |
uint32_t size; | |
uint32_t flags; | |
uint32_t fourCC; | |
uint32_t RGBBitCount; | |
uint32_t RBitMask; | |
uint32_t GBitMask; | |
uint32_t BBitMask; | |
uint32_t ABitMask; | |
}; | |
struct rr_dds_header_t { | |
uint32_t size; | |
uint32_t flags; | |
uint32_t height; | |
uint32_t width; | |
uint32_t pitchOrLinearSize; | |
uint32_t depth; | |
uint32_t num_mips; | |
uint32_t reserved1[11]; | |
rr_dds_pixelformat_t ddspf; | |
uint32_t caps; | |
uint32_t caps2; | |
uint32_t caps3; | |
uint32_t caps4; | |
uint32_t reserved2; | |
}; | |
struct rr_dds_header_dx10_t { | |
uint32_t dxgi_format; | |
uint32_t resource_dimension; | |
uint32_t misc_flag; // see D3D11_RESOURCE_MISC_FLAG | |
uint32_t array_size; | |
uint32_t misc_flag2; | |
}; | |
struct rr_dxgi_format_name_t { | |
rr_dxgi_format_t fmt; | |
const char *name; | |
}; | |
const char *rr_dds_format_get_name(rr_dxgi_format_t fmt) { | |
static const rr_dxgi_format_name_t formats[] = { | |
#define RGBFMT(name,id,bypu) { name, #name }, | |
#define BCNFMT(name,id,bypu) { name, #name }, | |
#define ODDFMT(name,id) { name, #name }, | |
RR_DDS_FORMAT_LIST | |
#undef RGBFMT | |
#undef BCNFMT | |
#undef ODDFMT | |
}; | |
for(size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) { | |
if (formats[i].fmt == fmt) { | |
return formats[i].name; | |
} | |
} | |
return formats[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 rr_dxgi_format_t rr_dxgi_format_srgb_tab[] = { | |
RR_DXGI_FORMAT_R8G8B8A8_UNORM, RR_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, | |
RR_DXGI_FORMAT_BC1_UNORM, RR_DXGI_FORMAT_BC1_UNORM_SRGB, | |
RR_DXGI_FORMAT_BC2_UNORM, RR_DXGI_FORMAT_BC2_UNORM_SRGB, | |
RR_DXGI_FORMAT_BC3_UNORM, RR_DXGI_FORMAT_BC3_UNORM_SRGB, | |
RR_DXGI_FORMAT_B8G8R8A8_UNORM, RR_DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, | |
RR_DXGI_FORMAT_B8G8R8X8_UNORM, RR_DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, | |
RR_DXGI_FORMAT_BC7_UNORM, RR_DXGI_FORMAT_BC7_UNORM_SRGB, | |
}; | |
static int rr_dds_find_dxgi_format_in_srgb_tab(rr_dxgi_format_t fmt) { | |
for(size_t i = 0; i < sizeof(rr_dxgi_format_srgb_tab) / sizeof(*rr_dxgi_format_srgb_tab); ++i) { | |
if (rr_dxgi_format_srgb_tab[i] == fmt) { | |
return(int)i; | |
} | |
} | |
return -1; | |
} | |
bool rr_dds_format_is_sRGB(rr_dxgi_format_t fmt) { | |
int idx = rr_dds_find_dxgi_format_in_srgb_tab(fmt); | |
return idx >= 0 && ((idx & 1) == 1); | |
} | |
rr_dxgi_format_t rr_dds_format_remove_sRGB(rr_dxgi_format_t fmt) { | |
int idx = rr_dds_find_dxgi_format_in_srgb_tab(fmt); | |
if(idx >= 0) { | |
return rr_dxgi_format_srgb_tab[idx & ~1]; | |
} else { | |
return fmt; | |
} | |
} | |
rr_dxgi_format_t rr_dds_format_make_sRGB(rr_dxgi_format_t fmt) { | |
int idx = rr_dds_find_dxgi_format_in_srgb_tab(fmt); | |
if(idx >= 0) { | |
return rr_dxgi_format_srgb_tab[idx | 1]; | |
} else { | |
return fmt; | |
} | |
} | |
struct rr_dds_bitmasks_to_format_t { | |
uint32_t flags; | |
uint32_t bits; | |
uint32_t r, g, b, a; | |
rr_dxgi_format_t dxgi; | |
int auto_d3d9; // enable format in auto-D3D9 write mode | |
}; | |
struct rr_dds_fourcc_to_format_t { | |
uint32_t fourcc; | |
rr_dxgi_format_t dxgi; | |
int auto_d3d9; // enable format in auto-D3D9 write mode | |
}; | |
struct rr_dds_format_info_t { | |
rr_dxgi_format_t dxgi; | |
uint32_t unit_w; // width of a coding unit | |
uint32_t unit_h; // height of a coding unit | |
uint32_t unit_bytes; | |
}; | |
static const rr_dds_format_info_t rr_dds_formats[] = { | |
#define RGBFMT(name,id,bypu) { name, 1,1, bypu }, | |
#define BCNFMT(name,id,bypu) { name, 4,4, bypu }, | |
#define ODDFMT(name,id) // these are not supported for reading so they're intentionally not on the list | |
RR_DDS_FORMAT_LIST | |
#undef RGBFMT | |
#undef BCNFMT | |
#undef ODDFMT | |
}; | |
// This is following MS DDSTextureLoader11 | |
// | |
// Formats with auto_d3d9==1 will get emitted as D3D9 DDS in auto-D3D9 mode; | |
// the other ones we read from D3D9 .DDS files but will only write in "force D3D9" | |
// output mode, and otherwise prefer D3D10 mode, because they're not widely supported | |
// in D3D9 apps. | |
static const rr_dds_bitmasks_to_format_t rr_dds9_mask_tab[] = { | |
//flags bits r g b a dxgi auto_d3d9 | |
{ RR_DDPF_RGB, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000, RR_DXGI_FORMAT_R8G8B8A8_UNORM, 1 }, | |
{ RR_DDPF_RGB, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000, RR_DXGI_FORMAT_B8G8R8A8_UNORM, 1 }, | |
{ RR_DDPF_RGB, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000, RR_DXGI_FORMAT_B8G8R8X8_UNORM, 1 }, | |
{ RR_DDPF_RGB, 32, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000, RR_DXGI_FORMAT_R10G10B10A2_UNORM, 0 }, // yes, this mask is backwards, but that's the standard value to write for R10G10B10A2_UNORM! (see comments in DDSTextureLoader11) | |
{ RR_DDPF_RGB, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000, RR_DXGI_FORMAT_R16G16_UNORM, 1 }, | |
{ RR_DDPF_RGB, 32, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, RR_DXGI_FORMAT_R32_FLOAT, 0 }, // only 32-bit color channel format in D3D9 was R32F | |
{ RR_DDPF_RGB, 16, 0x7c00, 0x03e0, 0x001f, 0x8000, RR_DXGI_FORMAT_B5G5R5A1_UNORM, 1 }, | |
{ RR_DDPF_RGB, 16, 0xf800, 0x07e0, 0x001f, 0x0000, RR_DXGI_FORMAT_B5G6R5_UNORM, 1 }, | |
{ RR_DDPF_RGB, 16, 0x0f00, 0x00f0, 0x000f, 0xf000, RR_DXGI_FORMAT_B4G4R4A4_UNORM, 1 }, | |
{ RR_DDPF_LUMINANCE, 8, 0xff, 0x00, 0x00, 0x00, RR_DXGI_FORMAT_R8_UNORM, 1 }, | |
{ RR_DDPF_LUMINANCE, 16, 0xffff, 0x0000, 0x0000, 0x0000, RR_DXGI_FORMAT_R16_UNORM, 1 }, | |
{ RR_DDPF_LUMINANCE, 16, 0x00ff, 0x0000, 0x0000, 0xff00, RR_DXGI_FORMAT_R8G8_UNORM, 1 }, // official way to do it - this must go first! | |
{ RR_DDPF_LUMINANCE, 8, 0xff, 0x00, 0x00, 0xff00, RR_DXGI_FORMAT_R8G8_UNORM, 0 }, // some writers write this instead, ugh. | |
{ RR_DDPF_ALPHA, 8, 0x00, 0x00, 0x00, 0xff, RR_DXGI_FORMAT_A8_UNORM, 1 }, | |
{ RR_DDPF_BUMPDUDV, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000, RR_DXGI_FORMAT_R8G8B8A8_SNORM, 0 }, | |
{ RR_DDPF_BUMPDUDV, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000, RR_DXGI_FORMAT_R16G16_SNORM, 0 }, | |
{ RR_DDPF_BUMPDUDV, 16, 0x00ff, 0xff00, 0x0000, 0x0000, RR_DXGI_FORMAT_R8G8_SNORM, 0 }, | |
}; | |
// this is following MS DDSTextureLoader11 | |
// when multiple FOURCCs map to the same DXGI format, put the preferred FOURCC first | |
// | |
// auto_d3d9 works as above. | |
static const rr_dds_fourcc_to_format_t rr_dds9_fourcc_tab[] = { | |
//fourcc dxgi auto_d3d9 | |
{ RR_DDS_FOURCC('D','X','T','1'), RR_DXGI_FORMAT_BC1_UNORM, 1 }, | |
{ RR_DDS_FOURCC('D','X','T','3'), RR_DXGI_FORMAT_BC2_UNORM, 1 }, // NOTE: before DXT2 so that it's our preferred FOURCC for BC2 export | |
{ RR_DDS_FOURCC('D','X','T','2'), RR_DXGI_FORMAT_BC2_UNORM, 1 }, | |
{ RR_DDS_FOURCC('D','X','T','5'), RR_DXGI_FORMAT_BC3_UNORM, 1 }, // NOTE: before DXT4 so that it's our preferred FOURCC for BC3 export | |
{ RR_DDS_FOURCC('D','X','T','4'), RR_DXGI_FORMAT_BC3_UNORM, 1 }, | |
{ RR_DDS_FOURCC('B','C','4','U'), RR_DXGI_FORMAT_BC4_UNORM, 0 }, | |
{ RR_DDS_FOURCC('B','C','4','S'), RR_DXGI_FORMAT_BC4_SNORM, 0 }, | |
{ RR_DDS_FOURCC('A','T','I','1'), RR_DXGI_FORMAT_BC4_UNORM, 0 }, // NOTE: prefer the more explicit BC4U to ATI1 for export | |
{ RR_DDS_FOURCC('B','C','5','U'), RR_DXGI_FORMAT_BC5_UNORM, 0 }, | |
{ RR_DDS_FOURCC('B','C','5','S'), RR_DXGI_FORMAT_BC5_SNORM, 0 }, | |
{ RR_DDS_FOURCC('A','T','I','2'), RR_DXGI_FORMAT_BC5_UNORM, 0 }, // NOTE: ATI2 is kind of odd (technically swapped block order), so put it below BC5U | |
{ RR_DDS_FOURCC('B','C','6','H'), RR_DXGI_FORMAT_BC6H_UF16, 0 }, | |
{ RR_DDS_FOURCC('B','C','7','L'), RR_DXGI_FORMAT_BC7_UNORM, 0 }, | |
{ RR_DDS_FOURCC('B','C','7', 0 ), RR_DXGI_FORMAT_BC7_UNORM, 0 }, | |
{ 36, RR_DXGI_FORMAT_R16G16B16A16_UNORM, 1 }, // D3DFMT_A16B16G16R16 | |
{ 110, RR_DXGI_FORMAT_R16G16B16A16_SNORM, 0 }, // D3DFMT_Q16W16V16U16 | |
{ 111, RR_DXGI_FORMAT_R16_FLOAT, 1 }, // D3DFMT_R16F | |
{ 112, RR_DXGI_FORMAT_R16G16_FLOAT, 1 }, // D3DFMT_G16R16F | |
{ 113, RR_DXGI_FORMAT_R16G16B16A16_FLOAT, 1 }, // D3DFMT_A16B16G16R16F | |
{ 114, RR_DXGI_FORMAT_R32_FLOAT, 1 }, // D3DFMT_R32F | |
{ 115, RR_DXGI_FORMAT_R32G32_FLOAT, 1 }, // D3DFMT_G32R32F | |
{ 116, RR_DXGI_FORMAT_R32G32B32A32_FLOAT, 1 }, // D3DFMT_A32B32G32R32F | |
}; | |
static const rr_dds_format_info_t *rr_dds_get_format_info(rr_dxgi_format_t dxgi) { | |
// need to handle this special because UNKNOWN _does_ appear in the master list | |
// but we don't want to treat it as legal | |
if (dxgi == RR_DXGI_FORMAT_UNKNOWN) { | |
return 0; | |
} | |
for (size_t i = 0; i < sizeof(rr_dds_formats)/sizeof(*rr_dds_formats); ++i) { | |
if (dxgi == rr_dds_formats[i].dxgi) { | |
return &rr_dds_formats[i]; | |
} | |
} | |
return 0; | |
} | |
static rr_dxgi_format_t rr_dds_dxgi_format_from_dds9(const rr_dds_header_t *dds_header) { | |
const rr_dds_pixelformat_t &ddpf = dds_header->ddspf; | |
if (ddpf.flags & RR_DDPF_FOURCC) { | |
for (size_t i = 0; i < sizeof(rr_dds9_fourcc_tab)/sizeof(*rr_dds9_fourcc_tab); ++i) { | |
if (ddpf.fourCC == rr_dds9_fourcc_tab[i].fourcc) { | |
return rr_dds9_fourcc_tab[i].dxgi; | |
} | |
} | |
} else { | |
uint32_t type_flags = ddpf.flags & (RR_DDPF_RGB | RR_DDPF_LUMINANCE | RR_DDPF_ALPHA | RR_DDPF_BUMPDUDV); | |
for (size_t i = 0; i < sizeof(rr_dds9_mask_tab)/sizeof(*rr_dds9_mask_tab); ++i) { | |
const rr_dds_bitmasks_to_format_t *fmt = &rr_dds9_mask_tab[i]; | |
if (type_flags == fmt->flags && ddpf.RGBBitCount == fmt->bits && | |
ddpf.RBitMask == fmt->r && ddpf.GBitMask == fmt->g && | |
ddpf.BBitMask == fmt->b && ddpf.ABitMask == fmt->a) { | |
return fmt->dxgi; | |
} | |
} | |
} | |
return RR_DXGI_FORMAT_UNKNOWN; | |
} | |
static uint32_t rr_dds_mip_dim(uint32_t dim, uint32_t level) { | |
// mip dimensions truncate at every level and bottom out at 1 | |
uint32_t x = dim >> level; | |
return x ? x : 1; | |
} | |
static void rr_dds_init_mip(rr_dds_mip_t *mip, uint32_t width, uint32_t height, uint32_t depth, const rr_dds_format_info_t *fmt) { | |
uint32_t width_u = (width + fmt->unit_w-1) / fmt->unit_w; | |
uint32_t height_u = (height + fmt->unit_h-1) / fmt->unit_h; | |
mip->width = width; | |
mip->height = height; | |
mip->depth = depth; | |
mip->row_stride = (size_t)width_u * fmt->unit_bytes; | |
mip->slice_stride = height_u * mip->row_stride; | |
mip->data_size = depth * mip->slice_stride; | |
mip->data = 0; | |
} | |
void rr_dds_free(rr_dds_t *dds) { | |
if (!dds) | |
return; | |
if (dds->mips) { | |
free(dds->mips); | |
} | |
if (dds->mip_data_ptr) { | |
free(dds->mip_data_ptr); | |
} | |
free(dds); | |
} | |
void rr_dds_err_ctx_init(rr_dds_err_ctx_t * err) { | |
err->err = RR_DDS_ERR_OK; | |
err->message[0] = 0; | |
} | |
static rr_dds_err_t rr_dds_error(rr_dds_err_ctx_t * ctx, rr_dds_err_t err, const char *fmt, ...) { | |
// if there's already an error set on the context, stick with that | |
if (ctx->err != RR_DDS_ERR_OK) { | |
return ctx->err; | |
} | |
ctx->err = err; | |
va_list arg; | |
va_start(arg, fmt); | |
const size_t buf_size = sizeof(ctx->message) / sizeof(*ctx->message); | |
vsnprintf(ctx->message, buf_size, fmt, arg); | |
ctx->message[buf_size - 1] = 0; | |
va_end(arg); | |
return err; | |
} | |
static rr_dds_err_t rr_dds_validate(rr_dds_t *dds, rr_dds_err_ctx_t *err_ctx) { | |
// Check if the pixel format is supported | |
const rr_dds_format_info_t *format_info = rr_dds_get_format_info(dds->dxgi_format); | |
if (!format_info) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_PIXEL_FORMAT, "DXGI format %d (%s) is not supported.", dds->dxgi_format, rr_dds_format_get_name(dds->dxgi_format)); | |
} | |
// Check that resource and image dimensions agree | |
switch (dds->dimension) | |
{ | |
case 1: | |
if (dds->height != 1 || dds->depth != 1) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_IMAGE_DIMENSION, "1D textures must have height and depth of 1."); | |
} | |
break; | |
case 2: | |
if (dds->depth != 1) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_IMAGE_DIMENSION, "2D textures must have depth of 1."); | |
} | |
break; | |
case 3: | |
if (dds->array_size != 1) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_IMAGE_DIMENSION, "3D textures must have array size of 1."); | |
} | |
break; | |
default: | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_RESOURCE_DIMENSION, "Texture must be 1D, 2D, or 3D."); | |
} | |
// All dimensions must be non-zero | |
if (!dds->width || !dds->height || !dds->depth || !dds->num_mips || !dds->array_size) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_IMAGE_DIMENSION, "One or more image dimensions are zero."); | |
} | |
// Note: max_mips of 16 means maximum dimension of 64k-1... increase this number if you need to. | |
const uint32_t max_dim = (1 << RR_DDS_MAX_MIPS) - 1; // max_dim=0xffff has 16 mip levels, but 0x10000 has 17 | |
if (dds->width > max_dim || dds->height > max_dim || dds->depth > max_dim) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_IMAGE_DIMENSION, "Image dimensions %ux%ux%u of DDS exceed maximum of %u.", dds->width, dds->height, dds->depth, max_dim); | |
} | |
// Check that mipmap count makes sense | |
// Mipmaps halve each dimension (with floor) at every step; dimensions that end up | |
// 0 pixels turn into 1. For the mip map count to be valid, in the final mip map, at least | |
// one dimension should've been non-0 before this adjustment. | |
if (dds->num_mips > RR_DDS_MAX_MIPS) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_MIPMAP_COUNT, "Invalid mipmap count of %u.", dds->num_mips); | |
} | |
const uint32_t final_mip = dds->num_mips - 1; // We check this num_mips != 0 above, so no overflow. | |
const uint32_t last_mip_w = dds->width >> final_mip; | |
const uint32_t last_mip_h = dds->height >> final_mip; | |
const uint32_t last_mip_d = dds->depth >> final_mip; | |
if (last_mip_w == 0 && last_mip_h == 0 && last_mip_d == 0) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_MIPMAP_COUNT, "Invalid mipmap count of %u for %ux%ux%u resource.", dds->num_mips, dds->width, dds->height, dds->depth); | |
} | |
// Cubemaps need to be square | |
bool is_cubemap = (dds->flags & RR_DDS_FLAGS_CUBEMAP) != 0; | |
if (is_cubemap && (dds->width != dds->height || dds->depth != 1)) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_CUBEMAP, "Cubemap is not square or has non-1 depth!"); | |
} | |
if (is_cubemap && (dds->array_size % 6) != 0) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_CUBEMAP, "Cubemap or cubemap array doesn't have a multiple of 6 faces."); | |
} | |
return RR_DDS_ERR_OK; | |
} | |
static bool rr_dds_alloc_mips(rr_dds_t *ret, const rr_dds_format_info_t *info, uint32_t flags) { | |
ret->mips = (rr_dds_mip_t*)calloc(ret->num_mips * ret->array_size, sizeof(rr_dds_mip_t)); | |
if(!ret->mips) { | |
return false; | |
} | |
ret->mip_data_size = 0; | |
ret->mip_data_ptr = 0; | |
if (!(flags & RR_DDS_FLAGS_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 | |
size_t all_mips_size = 0; | |
for (uint32_t array_ind = 0; array_ind < ret->array_size; ++array_ind) { | |
for (uint32_t level = 0; level < ret->num_mips; ++level) { | |
rr_dds_mip_t *mip = ret->mips + (array_ind * ret->num_mips + level); | |
uint32_t mipw = rr_dds_mip_dim(ret->width, level); | |
uint32_t miph = rr_dds_mip_dim(ret->height, level); | |
uint32_t mipd = rr_dds_mip_dim(ret->depth, level); | |
rr_dds_init_mip(mip, mipw, miph, mipd, info); | |
all_mips_size += mip->data_size; | |
} | |
} | |
ret->mip_data_size = all_mips_size; | |
ret->mip_data_ptr = calloc(1,all_mips_size); | |
if(!ret->mip_data_ptr) { | |
return false; | |
} | |
uint8_t * ptr = (uint8_t*) ret->mip_data_ptr; | |
for (uint32_t i = 0; i < ret->array_size * ret->num_mips; ++i) { | |
rr_dds_mip_t *mip = ret->mips + i; | |
mip->data = ptr; | |
ptr += mip->data_size; | |
} | |
} | |
return true; | |
} | |
// Create an empty DDS structure | |
rr_dds_t *rr_dds_new_with_err_ctx(int32_t dim, uint32_t width, uint32_t height, uint32_t depth, uint32_t num_mips, uint32_t array_size, | |
rr_dxgi_format_t format, uint32_t flags, rr_dds_err_ctx_t *err_ctx) | |
{ | |
// Allocate the struct | |
rr_dds_t *ret = (rr_dds_t*)calloc(1, sizeof(rr_dds_t)); | |
if(!ret) { | |
rr_dds_error(err_ctx, RR_DDS_ERR_OUT_OF_MEMORY, "Out of memory allocating DDS structure."); | |
return 0; | |
} | |
ret->dimension = dim; | |
ret->width = width; | |
ret->height = height; | |
ret->depth = depth; | |
ret->num_mips = num_mips; | |
ret->array_size = array_size; | |
ret->dxgi_format = format; | |
ret->flags = flags & ~RR_DDS_FLAGS_NO_MIP_STORAGE_ALLOC; | |
rr_dds_err_t validate_err = rr_dds_validate(ret, err_ctx); | |
if (validate_err != RR_DDS_ERR_OK) { | |
rr_dds_free(ret); | |
return 0; | |
} | |
// rr_dds_validate checks that the pixel format is known and valid, no need to re-check | |
const rr_dds_format_info_t *info = rr_dds_get_format_info(format); | |
if(!rr_dds_alloc_mips(ret, info, flags)) { | |
rr_dds_error(err_ctx, RR_DDS_ERR_OUT_OF_MEMORY, "Out of memory allocating DDS mip levels."); | |
rr_dds_free(ret); | |
return 0; | |
} | |
return ret; | |
} | |
rr_dds_t *rr_dds_new(int32_t dim, uint32_t width, uint32_t height, uint32_t depth, uint32_t num_mips, uint32_t array_size, rr_dxgi_format_t format, uint32_t flags) { | |
rr_dds_err_ctx_t err_ctx; | |
rr_dds_err_ctx_init(&err_ctx); | |
rr_dds_t *res = rr_dds_new_with_err_ctx(dim, width, height, depth, num_mips, array_size, format, flags, &err_ctx); | |
if (err_ctx.err != RR_DDS_ERR_OK) { | |
fprintf(stderr, "error: %s\n", err_ctx.message); | |
} | |
return res; | |
} | |
rr_dds_t *rr_dds_new_2d(uint32_t width, uint32_t height, uint32_t num_mips, rr_dxgi_format_t format, uint32_t flags) { | |
return rr_dds_new(2, width, height, 1, num_mips, 1, format, flags); | |
} | |
bool rr_dds_is_dds(const void *in_buf, size_t in_len) { | |
return in_buf && in_len > 128 && !memcmp(in_buf, "DDS ", 4); | |
} | |
static bool rr_dds_in_bounds(size_t data_size, const void *cur, const void *end) { | |
// NOTE: assuming cur <= end (which we do), this difference is always non-negative | |
size_t bytes_left = (const char *)end - (const char *)cur; | |
return data_size <= bytes_left; | |
} | |
static rr_dds_err_t rr_dds_read_header(rr_dds_t *ret, rr_dds_err_ctx_t *err_ctx, const void *in_buf, size_t in_len, char ** payload_ptr) { | |
char *raw_ptr_end = (char*)in_buf + in_len; | |
char *raw_ptr = (char*)in_buf + 4; // NOTE: we checked that we have at least 128 bytes from start of file | |
if(!rr_dds_in_bounds(sizeof(rr_dds_header_t), raw_ptr, raw_ptr_end)) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_UNEXPECTED_EOF, "Corrupt file, truncated header."); | |
} | |
rr_dds_header_t *dds_header = (rr_dds_header_t*)raw_ptr; | |
raw_ptr += sizeof(*dds_header); | |
// 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. | |
rr_dds_header_dx10_t *dx10_header = 0; | |
const rr_dds_pixelformat_t &ddpf = dds_header->ddspf; | |
if ((ddpf.flags & RR_DDPF_FOURCC) && ddpf.fourCC == RR_DX10_MAGIC) { | |
if(!rr_dds_in_bounds(sizeof(rr_dds_header_dx10_t), raw_ptr, raw_ptr_end)) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_UNEXPECTED_EOF, "Corrupt file, truncated header."); | |
} | |
dx10_header = (rr_dds_header_dx10_t*)(raw_ptr); | |
raw_ptr += sizeof(*dx10_header); | |
if (dx10_header->resource_dimension >= RR_RESOURCE_DIMENSION_TEXTURE1D && dx10_header->resource_dimension <= RR_RESOURCE_DIMENSION_TEXTURE3D) { | |
ret->dimension = (dx10_header->resource_dimension - RR_RESOURCE_DIMENSION_TEXTURE1D) + 1; | |
} else { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_RESOURCE_DIMENSION, "D3D10 resource dimension in DDS is neither 1D, 2D, nor 3D."); | |
} | |
ret->dxgi_format = (rr_dxgi_format_t)dx10_header->dxgi_format; | |
} else { | |
// For D3D9-style files, we guess dimension from the caps bits. | |
// If the volume cap is set, assume 3D, otherwise 2D. | |
ret->dimension = (dds_header->caps2 & RR_DDSCAPS2_VOLUME) ? 3 : 2; | |
ret->dxgi_format = rr_dds_dxgi_format_from_dds9(dds_header); | |
} | |
// More header parsing | |
bool is_cubemap = dx10_header ? (dx10_header->misc_flag & RR_RESOURCE_MISC_TEXTURECUBE) != 0 : (dds_header->caps2 & RR_DDSCAPS2_CUBEMAP) != 0; | |
bool is_volume = ret->dimension == 3; | |
ret->width = dds_header->width; | |
ret->height = dds_header->height; | |
ret->depth = is_volume ? dds_header->depth : 1; | |
ret->num_mips = (dds_header->caps & RR_DDSCAPS_MIPMAP) ? dds_header->num_mips : 1; | |
ret->array_size = dx10_header ? dx10_header->array_size : 1; | |
ret->flags = 0; | |
if(is_cubemap) { | |
ret->flags |= RR_DDS_FLAGS_CUBEMAP; | |
ret->array_size *= 6; | |
} | |
if (!dx10_header) { | |
ret->flags |= RR_DDS_FLAGS_WAS_D3D9; | |
} | |
// Sanity-check all these values | |
rr_dds_err_t err = rr_dds_validate(ret, err_ctx); | |
if (err != RR_DDS_ERR_OK) { | |
return err; | |
} | |
*payload_ptr = raw_ptr; | |
return RR_DDS_ERR_OK; | |
} | |
static rr_dds_err_t rr_dds_read_payload(rr_dds_t *ret, rr_dds_err_ctx_t *err_ctx, const void *in_buf, size_t in_len, char * payload_ptr) { | |
char *raw_ptr_end = (char*)in_buf + in_len; | |
char *raw_ptr = payload_ptr; | |
const rr_dds_format_info_t *format_info = rr_dds_get_format_info(ret->dxgi_format); | |
if(!rr_dds_alloc_mips(ret, format_info, ret->flags)) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_OUT_OF_MEMORY, "Out of memory allocating DDS mip chain."); | |
} | |
// Read all subresources (outer loop is arrays, inner is mips). | |
// | |
// These are always allocs by us as of this writing (no path to pass | |
// RR_DDS_FLAGS_NO_MIP_STORAGE_ALLOC on reads currently) so we could read it | |
// all in one go, but there's not much reason to. | |
for(uint32_t subres = 0; subres < ret->array_size * ret->num_mips; ++subres) { | |
rr_dds_mip_t *rrmip = ret->mips + subres; | |
// Check to make sure we aren't accessing data out of bounds! | |
if(!rr_dds_in_bounds(rrmip->data_size, raw_ptr, raw_ptr_end)) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_UNEXPECTED_EOF, "Corrupt file, texture data truncated."); | |
} | |
// Copy the raw pixel data over | |
memcpy(rrmip->data, raw_ptr, rrmip->data_size); | |
raw_ptr += rrmip->data_size; | |
} | |
return RR_DDS_ERR_OK; | |
} | |
rr_dds_t *rr_dds_read_with_err_ctx(const void *in_buf, size_t in_len, rr_dds_err_ctx_t *err_ctx) { | |
// Make sure this is a DDS file | |
if(!rr_dds_is_dds(in_buf, in_len)) { | |
rr_dds_error(err_ctx, RR_DDS_ERR_NOT_A_DDS, "No valid DDS header."); | |
return 0; | |
} | |
rr_dds_t *ret = (rr_dds_t*)calloc(1, sizeof(rr_dds_t)); | |
if(!ret) { | |
rr_dds_error(err_ctx, RR_DDS_ERR_OUT_OF_MEMORY, "Out of memory allocating DDS structure."); | |
return 0; | |
} | |
char * payload_ptr = 0; | |
if (rr_dds_read_header(ret, err_ctx, in_buf, in_len, &payload_ptr) != RR_DDS_ERR_OK) { | |
rr_dds_free(ret); | |
return 0; | |
} | |
if (rr_dds_read_payload(ret, err_ctx, in_buf, in_len, payload_ptr) != RR_DDS_ERR_OK) { | |
rr_dds_free(ret); | |
return 0; | |
} | |
return ret; | |
} | |
rr_dds_t *rr_dds_read(const void *in_buf, size_t in_len) { | |
rr_dds_err_ctx_t err_ctx; | |
rr_dds_err_ctx_init(&err_ctx); | |
rr_dds_t * result = rr_dds_read_with_err_ctx(in_buf, in_len, &err_ctx); | |
if (err_ctx.err != RR_DDS_ERR_OK) { | |
fprintf(stderr, "error: %s\n", err_ctx.message); | |
} | |
return result; | |
} | |
// Write out a DDS file. | |
rr_dds_err_t rr_dds_write_stream_with_err_ctx(rr_dds_t *dds, rr_dds_write_stream_t *strm, rr_dds_version_t file_version, rr_dds_err_ctx_t *err_ctx) { | |
// If we already have an error set in the error context, don't even start | |
if (err_ctx->err != RR_DDS_ERR_OK) { | |
return err_ctx->err; | |
} | |
// Validate DDS a bit... | |
rr_dds_err_t validate_err = rr_dds_validate(dds, err_ctx); | |
if (validate_err != RR_DDS_ERR_OK) { | |
return validate_err; | |
} | |
bool is_cubemap = (dds->flags & RR_DDS_FLAGS_CUBEMAP) != 0; | |
uint32_t width = dds->width; | |
uint32_t height = dds->height; | |
uint32_t depth = dds->depth; | |
uint32_t effective_dimension = dds->dimension; | |
rr_dxgi_format_t effective_format = dds->dxgi_format; | |
uint32_t depth_flag = (effective_dimension == 3) ? 0x800000 : 0; // DDSD_DEPTH | |
uint32_t array_size = is_cubemap ? dds->array_size / 6 : dds->array_size; | |
uint32_t caps2 = 0; | |
if(effective_dimension == 3) caps2 |= RR_DDSCAPS2_VOLUME; | |
if(is_cubemap) caps2 |= 0xFE00; // DDSCAPS2_CUBEMAP* | |
// D3D9 format DDS can't represent sRGB-ness; in "always write D3D9" mode, strip flag before we | |
// determine how to write the pixels | |
int min_autod3d9 = 1; // by default, only use common D3D9 format for auto-D3D9 | |
if (file_version == RR_DDS_VERSION_D3D9) { | |
effective_format = rr_dds_format_remove_sRGB(effective_format); | |
min_autod3d9 = 0; // in force-D3D9 mode, allow less common ones too | |
} | |
// Look up how to represent effective format as D3D9 bitmasks format | |
// (when a format occurs multiple times, pick first occurence in table) | |
const rr_dds_bitmasks_to_format_t *d3d9_fmt_masks = 0; | |
for (size_t i = 0; i < sizeof(rr_dds9_mask_tab)/sizeof(*rr_dds9_mask_tab); ++i) { | |
if (rr_dds9_mask_tab[i].dxgi == effective_format && rr_dds9_mask_tab[i].auto_d3d9 >= min_autod3d9) { | |
d3d9_fmt_masks = &rr_dds9_mask_tab[i]; | |
break; | |
} | |
} | |
// Look up how to represent effetive format as D3D9 FOURCC format | |
// (when a format occurs multiple times, pick first occurence in table) | |
uint32_t d3d9_fourCC = 0; | |
for (size_t i = 0; i < sizeof(rr_dds9_fourcc_tab)/sizeof(*rr_dds9_fourcc_tab); ++i) { | |
if (rr_dds9_fourcc_tab[i].dxgi == effective_format && rr_dds9_fourcc_tab[i].auto_d3d9 >= min_autod3d9) { | |
d3d9_fourCC = rr_dds9_fourcc_tab[i].fourcc; | |
break; | |
} | |
} | |
// In "force D3D9" mode, there's some extra checks because some things just aren't | |
// representable. | |
if (file_version == RR_DDS_VERSION_D3D9) { | |
if (d3d9_fmt_masks == 0 && d3d9_fourCC == 0) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_PIXEL_FORMAT, "Unsupported pixel format %s for D3D9 DDS.", rr_dds_format_get_name(effective_format)); | |
} | |
if (array_size != 1) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_BAD_IMAGE_DIMENSION, "D3D9 DDS writer does not support arrays."); | |
} | |
if (effective_dimension == 1) { | |
// D3D9 DDS can't do real 1D, it just implicitly turns it into 2D. | |
// Override effective dimension here because the Auto-D3D9 selector that always runs | |
// below only allows D3D9 to be chosen when the dimension is >=2. | |
effective_dimension = 2; | |
} | |
} | |
rr_dds_header_t dds_header = { | |
124, // size value. Required to be 124 | |
RR_DDSD_CAPS | RR_DDSD_HEIGHT | RR_DDSD_WIDTH | RR_DDSD_PIXELFORMAT | RR_DDSD_MIPMAPCOUNT | depth_flag, | |
height, | |
width, | |
0, // pitch or linear size | |
depth, | |
dds->num_mips, | |
{}, // reserved U32's | |
// DDSPF (DDS PixelFormat) | |
{ | |
32, // size, must be 32 | |
RR_DDPF_FOURCC, // DDPF_FOURCC | |
RR_DX10_MAGIC, // set up for writing as D3D10 | |
0,0,0,0,0 // masks and bit counts, if used, are set below | |
}, | |
RR_DDSCAPS_COMPLEX | RR_DDSCAPS_TEXTURE | RR_DDSCAPS_MIPMAP, | |
caps2, | |
0, | |
0, | |
0 | |
}; | |
uint32_t resource_dimension = RR_RESOURCE_DIMENSION_TEXTURE1D + (effective_dimension - 1); | |
uint32_t misc_flags = is_cubemap ? RR_RESOURCE_MISC_TEXTURECUBE : 0; | |
rr_dds_header_dx10_t dx10_header = { | |
(uint32_t)effective_format, // DXGI_FORMAT | |
resource_dimension, | |
misc_flags, | |
array_size, | |
0, // DDS_ALPHA_MODE_UNKNOWN | |
}; | |
// Check if we can write as D3D9 DDS | |
if (file_version != RR_DDS_VERSION_D3D10 && effective_dimension >= 2 && array_size == 1) { | |
rr_dds_pixelformat_t * pf = &dds_header.ddspf; | |
if (d3d9_fmt_masks != 0) { | |
// Can write as a D3D9 bitmasks format; do that! | |
pf->flags = d3d9_fmt_masks->flags; | |
pf->fourCC = 0; // clear D3D10 FOURCC we put here | |
pf->RGBBitCount = d3d9_fmt_masks->bits; | |
pf->RBitMask = d3d9_fmt_masks->r; | |
pf->GBitMask = d3d9_fmt_masks->g; | |
pf->BBitMask = d3d9_fmt_masks->b; | |
pf->ABitMask = d3d9_fmt_masks->a; | |
} else if (d3d9_fourCC != 0) { | |
// Can write as a D3D9 FOURCC format | |
pf->fourCC = d3d9_fourCC; | |
} | |
} | |
// Write the magic and headers | |
strm->write(strm, "DDS ", 4); | |
strm->write(strm, &dds_header, sizeof(dds_header)); | |
// Write D3D10 header if we picked D3D10 mode | |
if (dds_header.ddspf.fourCC == RR_DX10_MAGIC) { | |
strm->write(strm, &dx10_header, sizeof(dx10_header)); | |
} | |
// now go through all subresources in standard order and write them out | |
// not writing just one block at mip_data_ptr with size mip_data_size because | |
// even though _our_ allocations have the mips densely packed and contiguous, | |
// we let the app alloc (via RR_DDS_FLAGS_NO_MIP_STORAGE_ALLOC) or just point | |
// into whatever data they have in memory, and we do not (want to) require that | |
// to be always contigous. | |
for(uint32_t i = 0; i < dds->array_size * dds->num_mips; ++i) { | |
rr_dds_mip_t *rrmip = dds->mips + i; | |
// write in multi-MB chunks because enormous IO requests are often dicey | |
// (especially with network file systems) | |
size_t write_chunk_size = 4*1024*1024; | |
for (size_t pos = 0; pos < rrmip->data_size; pos += write_chunk_size) { | |
size_t len = rrmip->data_size - pos; | |
len = (len < write_chunk_size) ? len : write_chunk_size; | |
strm->write(strm, rrmip->data + pos, len); | |
} | |
} | |
return err_ctx->err; | |
} | |
static void rr_dds_write_stdio_write(rr_dds_write_stream_t *strm, const void *bytes, size_t byte_count) { | |
FILE *fp = (FILE*)strm->user_ptr; | |
// If we already had an error, bail | |
if (strm->err_ctx->err != RR_DDS_ERR_OK) { | |
return; | |
} | |
if (fwrite(bytes, byte_count, 1, fp) != 1) { | |
rr_dds_error(strm->err_ctx, RR_DDS_ERR_WRITE_FAILED, "Error writing DDS file."); | |
} | |
} | |
rr_dds_err_t rr_dds_write_file_with_err_ctx(rr_dds_t *dds, const char *filename, rr_dds_version_t file_version, rr_dds_err_ctx_t *err_ctx) { | |
if (err_ctx->err != RR_DDS_ERR_OK) { | |
return err_ctx->err; | |
} | |
// Open output file | |
FILE *fp; | |
#ifdef _MSC_VER | |
if (fopen_s(&fp, filename, "wb") != 0) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_OPEN_FAILED, "File open failed."); | |
} | |
#else | |
fp = fopen(filename, "wb"); | |
if (!fp) { | |
return rr_dds_error(err_ctx, RR_DDS_ERR_OPEN_FAILED, "File open failed."); | |
} | |
#endif | |
// Set up output stream | |
rr_dds_write_stream_t strm; | |
strm.write = rr_dds_write_stdio_write; | |
strm.err_ctx = err_ctx; | |
strm.user_ptr = fp; | |
// Return value ignored, we keep using the error context after. | |
rr_dds_write_stream_with_err_ctx(dds, &strm, file_version, err_ctx); | |
// Close stream, check for errors on flush | |
if (fclose(fp) == EOF) { | |
rr_dds_error(err_ctx, RR_DDS_ERR_WRITE_FAILED, "Error writing DDS file."); | |
} | |
// If any error occured, remove the output file | |
if (err_ctx->err != RR_DDS_ERR_OK) { | |
remove(filename); | |
} | |
return err_ctx->err; | |
} | |
rr_dds_err_t rr_dds_write_file(rr_dds_t *dds, const char *filename, rr_dds_version_t file_version) { | |
rr_dds_err_ctx_t err_ctx; | |
rr_dds_err_ctx_init(&err_ctx); | |
rr_dds_err_t err = rr_dds_write_file_with_err_ctx(dds, filename, file_version, &err_ctx); | |
if (err != RR_DDS_ERR_OK) { | |
fprintf(stderr, "error: %s\n", err_ctx.message); | |
} | |
return err; | |
} | |
#endif // RR_DDS_IMPLEMENTED | |
#endif // RR_DDS_IMPLEMENTATION |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment