Skip to content

Instantly share code, notes, and snippets.

@rygorous
Created March 7, 2025 01:45
Show Gist options
  • Save rygorous/46839aa005461ae34419c7ebefadb869 to your computer and use it in GitHub Desktop.
Save rygorous/46839aa005461ae34419c7ebefadb869 to your computer and use it in GitHub Desktop.
rr_dds loader/writer
//===================================================
// 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