Skip to content

Instantly share code, notes, and snippets.

@Bigfoot71
Created September 5, 2025 16:40
Show Gist options
  • Select an option

  • Save Bigfoot71/312ab7760e18713fb803c3c0f4089f7e to your computer and use it in GitHub Desktop.

Select an option

Save Bigfoot71/312ab7760e18713fb803c3c0f4089f7e to your computer and use it in GitHub Desktop.
DDS loader which was used in R3D
/*
* Copyright (c) 2025 Le Juez Victor
*
* This software is provided "as-is", without any express or implied warranty. In no event
* will the authors be held liable for any damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose, including commercial
* applications, and to alter it and redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not claim that you
* wrote the original software. If you use this software in a product, an acknowledgment
* in the product documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
* as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*/
#include "./r3d_dds.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <glad.h>
/* === DDS Structures === */
typedef struct {
uint32_t dwSize;
uint32_t dwFlags;
uint32_t dwFourCC;
uint32_t dwRGBBitCount;
uint32_t dwRBitMask;
uint32_t dwGBitMask;
uint32_t dwBBitMask;
uint32_t dwABitMask;
} dds_pixelformat_t;
typedef struct {
uint32_t dwSize;
uint32_t dwFlags;
uint32_t dwHeight;
uint32_t dwWidth;
uint32_t dwPitchOrLinearSize;
uint32_t dwDepth;
uint32_t dwMipMapCount;
uint32_t dwReserved1[11];
dds_pixelformat_t ddspf;
uint32_t dwCaps;
uint32_t dwCaps2;
uint32_t dwCaps3;
uint32_t dwCaps4;
uint32_t dwReserved2;
} dds_header_t;
typedef struct {
uint32_t dxgiFormat;
uint32_t resourceDimension;
uint32_t miscFlag;
uint32_t arraySize;
uint32_t miscFlags2;
} dds_header_dx10_t;
/* === Constants === */
#define DDS_MAGIC 0x20534444 // "DDS "
#define DDS_FOURCC 0x00000004
#define DDS_RGB 0x00000040
#define DDS_ALPHAPIXELS 0x00000001
#define FOURCC_DX10 0x30315844 // "DX10"
/* === DXGI Formats === */
#define DXGI_FORMAT_UNKNOWN 0
#define DXGI_FORMAT_R32G32B32A32_FLOAT 2
#define DXGI_FORMAT_R32G32B32_FLOAT 6
#define DXGI_FORMAT_R16G16B16A16_FLOAT 10
#define DXGI_FORMAT_R32G32_FLOAT 16
#define DXGI_FORMAT_R8G8B8A8_UNORM 28
#define DXGI_FORMAT_R16G16_FLOAT 34
#define DXGI_FORMAT_R32_FLOAT 41
#define DXGI_FORMAT_R8G8_UNORM 49
#define DXGI_FORMAT_R16_FLOAT 54
#define DXGI_FORMAT_R8_UNORM 61
#define DXGI_FORMAT_BC1_UNORM 71
#define DXGI_FORMAT_BC2_UNORM 74
#define DXGI_FORMAT_BC3_UNORM 77
#define DXGI_FORMAT_BC4_UNORM 80
#define DXGI_FORMAT_BC5_UNORM 83
/* === Helper Functions === */
static inline uint32_t get_bytes_per_pixel(r3d_dds_format_t format)
{
switch (format) {
case R3D_DDS_FORMAT_R8_UNORM: return 1;
case R3D_DDS_FORMAT_R8G8_UNORM: return 2;
case R3D_DDS_FORMAT_R8G8B8_UNORM: return 3;
case R3D_DDS_FORMAT_R8G8B8A8_UNORM: return 4;
case R3D_DDS_FORMAT_R16_FLOAT: return 2;
case R3D_DDS_FORMAT_R32_FLOAT: return 4;
case R3D_DDS_FORMAT_R16G16_FLOAT: return 4;
case R3D_DDS_FORMAT_R32G32_FLOAT: return 8;
case R3D_DDS_FORMAT_R16G16B16A16_FLOAT: return 8;
case R3D_DDS_FORMAT_R32G32B32_FLOAT: return 12;
case R3D_DDS_FORMAT_R32G32B32A32_FLOAT: return 16;
default: return 0; // Compressed formats don't have simple BPP
}
}
static inline r3d_dds_format_t dxgi_to_r3d_format(uint32_t dxgi_format)
{
switch (dxgi_format) {
case DXGI_FORMAT_R8_UNORM: return R3D_DDS_FORMAT_R8_UNORM;
case DXGI_FORMAT_R8G8_UNORM: return R3D_DDS_FORMAT_R8G8_UNORM;
case DXGI_FORMAT_R8G8B8A8_UNORM: return R3D_DDS_FORMAT_R8G8B8A8_UNORM;
case DXGI_FORMAT_R16_FLOAT: return R3D_DDS_FORMAT_R16_FLOAT;
case DXGI_FORMAT_R32_FLOAT: return R3D_DDS_FORMAT_R32_FLOAT;
case DXGI_FORMAT_R16G16_FLOAT: return R3D_DDS_FORMAT_R16G16_FLOAT;
case DXGI_FORMAT_R32G32_FLOAT: return R3D_DDS_FORMAT_R32G32_FLOAT;
case DXGI_FORMAT_R16G16B16A16_FLOAT: return R3D_DDS_FORMAT_R16G16B16A16_FLOAT;
case DXGI_FORMAT_R32G32B32_FLOAT: return R3D_DDS_FORMAT_R32G32B32_FLOAT;
case DXGI_FORMAT_R32G32B32A32_FLOAT: return R3D_DDS_FORMAT_R32G32B32A32_FLOAT;
case DXGI_FORMAT_BC1_UNORM: return R3D_DDS_FORMAT_BC1;
case DXGI_FORMAT_BC2_UNORM: return R3D_DDS_FORMAT_BC2;
case DXGI_FORMAT_BC3_UNORM: return R3D_DDS_FORMAT_BC3;
case DXGI_FORMAT_BC4_UNORM: return R3D_DDS_FORMAT_BC4;
case DXGI_FORMAT_BC5_UNORM: return R3D_DDS_FORMAT_BC5;
default: return R3D_DDS_FORMAT_UNKNOWN;
}
}
static inline r3d_dds_format_t legacy_format_to_r3d(const dds_pixelformat_t* pf)
{
// Check for compressed formats first
if (pf->dwFlags & DDS_FOURCC) {
switch (pf->dwFourCC) {
case 0x31545844: return R3D_DDS_FORMAT_BC1; // "DXT1"
case 0x33545844: return R3D_DDS_FORMAT_BC2; // "DXT3"
case 0x35545844: return R3D_DDS_FORMAT_BC3; // "DXT5"
case 0x31495441: return R3D_DDS_FORMAT_BC4; // "ATI1"
case 0x32495441: return R3D_DDS_FORMAT_BC5; // "ATI2"
}
}
// Check for uncompressed formats
if (pf->dwFlags & DDS_RGB) {
switch (pf->dwRGBBitCount) {
case 32:
if (pf->dwABitMask != 0) return R3D_DDS_FORMAT_R8G8B8A8_UNORM;
return R3D_DDS_FORMAT_R8G8B8_UNORM;
case 24: return R3D_DDS_FORMAT_R8G8B8_UNORM;
case 16: return R3D_DDS_FORMAT_R8G8_UNORM;
case 8: return R3D_DDS_FORMAT_R8_UNORM;
}
}
return R3D_DDS_FORMAT_UNKNOWN;
}
/* === OpenGL Format Mapping === */
static inline GLenum r3d_format_to_gl_internal_format(r3d_dds_format_t format)
{
switch (format) {
case R3D_DDS_FORMAT_R8_UNORM: return GL_R8;
case R3D_DDS_FORMAT_R8G8_UNORM: return GL_RG8;
case R3D_DDS_FORMAT_R8G8B8_UNORM: return GL_RGB8;
case R3D_DDS_FORMAT_R8G8B8A8_UNORM: return GL_RGBA8;
case R3D_DDS_FORMAT_R16_FLOAT: return GL_R16F;
case R3D_DDS_FORMAT_R32_FLOAT: return GL_R32F;
case R3D_DDS_FORMAT_R16G16_FLOAT: return GL_RG16F;
case R3D_DDS_FORMAT_R32G32_FLOAT: return GL_RG32F;
case R3D_DDS_FORMAT_R16G16B16A16_FLOAT: return GL_RGBA16F;
case R3D_DDS_FORMAT_R32G32B32A32_FLOAT: return GL_RGBA32F;
case R3D_DDS_FORMAT_BC1: return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
case R3D_DDS_FORMAT_BC2: return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
case R3D_DDS_FORMAT_BC3: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
case R3D_DDS_FORMAT_BC4: return GL_COMPRESSED_RED_RGTC1;
case R3D_DDS_FORMAT_BC5: return GL_COMPRESSED_RG_RGTC2;
default: return 0;
}
}
static inline GLenum r3d_format_to_gl_format(r3d_dds_format_t format)
{
switch (format) {
case R3D_DDS_FORMAT_R8_UNORM:
case R3D_DDS_FORMAT_R16_FLOAT:
case R3D_DDS_FORMAT_R32_FLOAT: return GL_RED;
case R3D_DDS_FORMAT_R8G8_UNORM:
case R3D_DDS_FORMAT_R16G16_FLOAT:
case R3D_DDS_FORMAT_R32G32_FLOAT: return GL_RG;
case R3D_DDS_FORMAT_R8G8B8_UNORM:
case R3D_DDS_FORMAT_R32G32B32_FLOAT: return GL_RGB;
case R3D_DDS_FORMAT_R8G8B8A8_UNORM:
case R3D_DDS_FORMAT_R16G16B16A16_FLOAT:
case R3D_DDS_FORMAT_R32G32B32A32_FLOAT: return GL_RGBA;
default: return 0; // Compressed formats don't use this
}
}
static inline GLenum r3d_format_to_gl_type(r3d_dds_format_t format)
{
switch (format) {
case R3D_DDS_FORMAT_R8_UNORM:
case R3D_DDS_FORMAT_R8G8_UNORM:
case R3D_DDS_FORMAT_R8G8B8_UNORM:
case R3D_DDS_FORMAT_R8G8B8A8_UNORM: return GL_UNSIGNED_BYTE;
case R3D_DDS_FORMAT_R16_FLOAT:
case R3D_DDS_FORMAT_R16G16_FLOAT:
case R3D_DDS_FORMAT_R16G16B16A16_FLOAT: return GL_HALF_FLOAT;
case R3D_DDS_FORMAT_R32_FLOAT:
case R3D_DDS_FORMAT_R32G32_FLOAT:
case R3D_DDS_FORMAT_R32G32B32_FLOAT:
case R3D_DDS_FORMAT_R32G32B32A32_FLOAT: return GL_FLOAT;
default: return 0; // Compressed formats don't use this
}
}
static inline int r3d_format_is_compressed(r3d_dds_format_t format) {
return (format >= R3D_DDS_FORMAT_BC1 && format <= R3D_DDS_FORMAT_BC5);
}
/* === Main Loader Function === */
const void* r3d_load_dds_from_memory(const unsigned char* file_data, uint32_t* w, uint32_t* h, r3d_dds_format_t* format, uint32_t* data_size)
{
if (!file_data || !w || !h || !format) {
return NULL;
}
const unsigned char* ptr = file_data;
// Check DDS magic
uint32_t magic = *(uint32_t*)ptr;
if (magic != DDS_MAGIC) {
return NULL;
}
ptr += sizeof(uint32_t);
// Read main header
const dds_header_t* header = (dds_header_t*)ptr;
if (header->dwSize != sizeof(dds_header_t)) {
return NULL;
}
ptr += sizeof(dds_header_t);
*w = header->dwWidth;
*h = header->dwHeight;
// Determine format
r3d_dds_format_t detected_format = R3D_DDS_FORMAT_UNKNOWN;
uint32_t calculated_data_size = 0;
if (header->ddspf.dwFourCC == FOURCC_DX10) {
// DX10 extended header
const dds_header_dx10_t* dx10_header = (dds_header_dx10_t*)ptr;
ptr += sizeof(dds_header_dx10_t);
detected_format = dxgi_to_r3d_format(dx10_header->dxgiFormat);
} else {
// Legacy format
detected_format = legacy_format_to_r3d(&header->ddspf);
}
*format = detected_format;
if (detected_format == R3D_DDS_FORMAT_UNKNOWN) {
return NULL;
}
// Calculate data size
uint32_t bpp = get_bytes_per_pixel(detected_format);
if (bpp > 0) {
// Uncompressed format
calculated_data_size = (*w) * (*h) * bpp;
} else {
// Compressed format - use pitch from header or calculate
if (header->dwPitchOrLinearSize > 0) {
calculated_data_size = header->dwPitchOrLinearSize;
} else {
// Fallback calculation for compressed formats
uint32_t block_size = 0;
switch (detected_format) {
case R3D_DDS_FORMAT_BC1: block_size = 8; break;
case R3D_DDS_FORMAT_BC2:
case R3D_DDS_FORMAT_BC3: block_size = 16; break;
case R3D_DDS_FORMAT_BC4: block_size = 8; break;
case R3D_DDS_FORMAT_BC5: block_size = 16; break;
default: return NULL;
}
uint32_t blocks_x = ((*w) + 3) / 4;
uint32_t blocks_y = ((*h) + 3) / 4;
calculated_data_size = blocks_x * blocks_y * block_size;
}
}
if (data_size) {
*data_size = calculated_data_size;
}
// Return direct pointer to pixel data (no copy)
return ptr;
}
GLuint r3d_load_dds_texture_from_memory(const unsigned char* file_data, uint32_t* w, uint32_t* h)
{
r3d_dds_format_t format;
uint32_t data_size;
const void* pixel_data = r3d_load_dds_from_memory(file_data, w, h, &format, &data_size);
if (!pixel_data) {
return 0;
}
// Generate OpenGL texture
GLuint texture_id;
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
// Set texture parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Upload texture data
if (r3d_format_is_compressed(format)) {
// Compressed texture
GLenum internal_format = r3d_format_to_gl_internal_format(format);
if (internal_format == 0) {
glDeleteTextures(1, &texture_id);
return 0;
}
glCompressedTexImage2D(GL_TEXTURE_2D, 0, internal_format, *w, *h, 0, data_size, pixel_data);
}
else {
// Uncompressed texture
GLenum internal_format = r3d_format_to_gl_internal_format(format);
GLenum gl_format = r3d_format_to_gl_format(format);
GLenum gl_type = r3d_format_to_gl_type(format);
if (internal_format == 0 || gl_format == 0 || gl_type == 0) {
glDeleteTextures(1, &texture_id);
return 0;
}
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, *w, *h, 0, gl_format, gl_type, pixel_data);
}
glBindTexture(GL_TEXTURE_2D, 0);
return texture_id;
}
/*
* Copyright (c) 2025 Le Juez Victor
*
* This software is provided "as-is", without any express or implied warranty. In no event
* will the authors be held liable for any damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose, including commercial
* applications, and to alter it and redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not claim that you
* wrote the original software. If you use this software in a product, an acknowledgment
* in the product documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
* as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifndef R3D_DETAILS_DDS_H
#define R3D_DETAILS_DDS_H
#include <stdint.h>
#include <glad.h>
/* === Format Enum === */
typedef enum {
R3D_DDS_FORMAT_UNKNOWN = 0,
R3D_DDS_FORMAT_R8G8B8A8_UNORM, // 32-bit RGBA
R3D_DDS_FORMAT_R8G8B8_UNORM, // 24-bit RGB
R3D_DDS_FORMAT_R8G8_UNORM, // 16-bit RG
R3D_DDS_FORMAT_R8_UNORM, // 8-bit R
R3D_DDS_FORMAT_R32G32B32A32_FLOAT, // 128-bit RGBA float
R3D_DDS_FORMAT_R32G32B32_FLOAT, // 96-bit RGB float
R3D_DDS_FORMAT_R32G32_FLOAT, // 64-bit RG float
R3D_DDS_FORMAT_R32_FLOAT, // 32-bit R float
R3D_DDS_FORMAT_R16G16B16A16_FLOAT, // 64-bit RGBA half
R3D_DDS_FORMAT_R16G16_FLOAT, // 32-bit RG half
R3D_DDS_FORMAT_R16_FLOAT, // 16-bit R half
R3D_DDS_FORMAT_BC1, // DXT1 compressed
R3D_DDS_FORMAT_BC2, // DXT3 compressed
R3D_DDS_FORMAT_BC3, // DXT5 compressed
R3D_DDS_FORMAT_BC4, // ATI1 compressed
R3D_DDS_FORMAT_BC5 // ATI2 compressed
} r3d_dds_format_t;
/* === Main Loader Function === */
const void* r3d_load_dds_from_memory(const unsigned char* file_data, uint32_t* w, uint32_t* h, r3d_dds_format_t* format, uint32_t* data_size);
GLuint r3d_load_dds_texture_from_memory(const unsigned char* file_data, uint32_t* w, uint32_t* h);
#endif // R3D_DETAILS_DDS_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment