Skip to content

Instantly share code, notes, and snippets.

@yasinkaraaslan
Last active August 20, 2025 11:10
Show Gist options
  • Save yasinkaraaslan/e2921d075981ca5d2a017d9c9fcd5156 to your computer and use it in GitHub Desktop.
Save yasinkaraaslan/e2921d075981ca5d2a017d9c9fcd5156 to your computer and use it in GitHub Desktop.
DDS Loader in Jai
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-reference
// NOTE(Yasin): This doesn't support some niche formats for simplicity sake.
// Namely R8G8_B8G8, G8R8_G8B8, legacy UYVY-packed, and legacy YUY2-packed formats.
// Though, it shouldn't be too hard to implement them.
load_dds :: (file_name: string) -> bool, DDS_Data {
content, read_success := read_entire_file(file_name);
if !read_success {
log_error("[dds] Unable to load file %!", file_name);
return false, .{};
}
load_success, data: = load_dds_from_memory(cast([]u8)content, file_name);
return load_success, data;
}
load_dds_from_memory :: (_content: []u8, name: string) -> bool, DDS_Data {
content := _content;
magic := (cast(*u32)content.data).*;
if magic != DDS_MAGIC {
log_error("[dds] '%' has incorrect magic! Wanted 0x%, got 0x%.", name, formatInt(DDS_MAGIC, 16), formatInt(magic, 16));
return false, .{};
}
content.data += size_of(type_of(DDS_MAGIC));
content.count -= size_of(type_of(DDS_MAGIC));
if content.count < size_of(DDS_Header) {
log_error("[dds] '%' is too small to even contain a header!", name);
return false, .{};
}
dds_data: DDS_Data;
header := cast(*DDS_Header)content.data;
if header.size != size_of(DDS_Header) {
log_error("[dds] '%' has incorrect header size! Wanted %, got %.", name, size_of(DDS_Header), header.size);
return false, dds_data;
}
if !(header.flags & DDS_HEADER_REQUIRED_FLAGS) {
log_error("[dds] '%' has header flags that are not valid! Required ones are %, got %.", name, DDS_HEADER_REQUIRED_FLAGS, header.flags);
return false, dds_data;
}
dds_data.header = header;
content.data += size_of(DDS_Header);
content.count -= size_of(DDS_Header);
pixel_format := header.pixel_format;
if pixel_format.size != size_of(DDS_Pixel_Format) {
log_error("[dds] '%' has incorrect pixel format structure size! Wanted %, got %.", name, size_of(DDS_Pixel_Format), pixel_format.size);
return false, dds_data;
}
array_size: u32 = 1;
bytes_per_block: u32;
if pixel_format.flags & .Four_CC {
format: DXGI_FORMAT;
if pixel_format.four_cc == {
case DXT1_CC; format = .BC1_UNORM;
case DXT2_CC; format = .BC2_UNORM;
case DXT3_CC; format = .BC2_UNORM;
case DXT4_CC; format = .BC3_UNORM;
case DXT5_CC; format = .BC3_UNORM;
case ATI1_CC; format = .BC4_UNORM;
case ATI2_CC; format = .BC5_UNORM;
case BC4U_CC; format = .BC4_UNORM;
case BC4S_CC; format = .BC4_SNORM;
case BC5U_CC; format = .BC5_UNORM;
case BC5S_CC; format = .BC5_SNORM;
case DX10_CC;
optional_header := cast(*DDS_Header_DXT)content.data;
array_size = optional_header.array_size;
format = optional_header.format;
if optional_header.misc_flags & .TEXTURECUBE
array_size *= 6;
dds_data.optional_header = optional_header;
content.data += size_of(DDS_Header_DXT);
content.count -= size_of(DDS_Header_DXT);
case;
log("[dds] Unknown FourCC: %", pixel_format.four_cc);
}
// Check for block compression.
if format >= .BC1_TYPELESS && format <= .BC5_SNORM || format >= .BC6H_TYPELESS && format <= .BC7_UNORM_SRGB {
if format >= .BC1_TYPELESS && format <= .BC1_UNORM_SRGB || format >= .BC4_TYPELESS && format <= .BC4_SNORM
bytes_per_block = 8;
else
bytes_per_block = 16;
}
}
if header.caps2 & .Cubemap && array_size == 1
array_size = 6;
dds_data.image_data = NewArray(array_size * header.mipmap_count, []u8);
for i: 0..array_size-1 {
for j: 0..header.mipmap_count-1 {
index := j + i * header.mipmap_count;
mip_width := cast(u32)max(1, header.width >> j);
mip_height := cast(u32)max(1, header.height >> j);
mip_depth := cast(u32)max(1, header.depth >> j); // Only for 3D textures
size: u32;
if bytes_per_block > 0 {
block_width := (mip_width + 3) / 4;
block_height := (mip_height + 3) / 4;
block_depth := (mip_depth + 3) / 4;
size = block_width * block_height * block_depth * bytes_per_block;
}
else {
size = mip_width * mip_height * mip_depth * pixel_format.rgb_bit_count / 8;
}
dds_data.image_data[index].data = content.data;
dds_data.image_data[index].count = size;
content.data += size;
content.count -= size;
}
}
// We really did something wrong if this fires up.
assert(content.count == 0);
return true, dds_data;
}
deinit :: (using dds_data: *DDS_Data) {
array_free(image_data);
// Also free the original memory you created the data with.
}
DDS_Data :: struct {
header: *DDS_Header;
optional_header: *DDS_Header_DXT;
image_data: [][]u8; // Index: Mip slice + (Array slice * Mip levels)
}
DDS_Header :: struct {
size: u32;
flags: Flags;
height: u32;
width: u32;
pitch_or_linear_size: u32;
depth: u32;
mipmap_count: u32;
reserved1: [11]u32;
pixel_format: DDS_Pixel_Format;
caps: Caps;
caps2: Caps2;
caps3: u32; // Unused
caps4: u32; // Unused
reserved2: u32;
Flags :: enum_flags u32 {
Caps;
Height;
Width;
Pitch;
Pixel_Format :: 0x1000;
Mipmap_Count :: 0x20000;
Linear_Size :: 0x80000;
Depth :: 0x800000;
}
Caps :: enum_flags u32 {
Complex :: 0x8;
Texture :: 0x1000;
Mipmap :: 0x400000;
}
Caps2 :: enum_flags u32 {
Cubemap :: 0x200;
Cubemap_Positive_X :: 0x400;
Cubemap_Negative_X :: 0x800;
Cubemap_Positive_Y :: 0x1000;
Cubemap_Negative_Y :: 0x2000;
Cubemap_Positive_Z :: 0x4000;
Cubemap_Negative_Z :: 0x8000;
Volume :: 0x200000;
}
}
DDS_Header_DXT :: struct {
format: DXGI_FORMAT;
dimension: D3D11_RESOURCE_DIMENSION;
misc_flags: D3D11_RESOURCE_MISC_FLAG;
array_size: u32;
misc_flags2: Misc_Flags;
Misc_Flags :: enum u32 {
Alpha_Mode_Unknown;
Alpha_Mode_Straight;
Alpha_Mode_Premultiplied;
Alpha_Mode_Opaque;
Alpha_Mode_Custom;
}
}
DDS_Pixel_Format :: struct {
size: u32;
flags: Flags;
four_cc: u32;
rgb_bit_count: u32;
r_bit_mask: u32;
g_bit_mask: u32;
b_bit_mask: u32;
a_bit_mask: u32;
Flags :: enum_flags u32 {
Alpha_Pixels;
Alpha;
Four_CC;
RGB :: 0x40;
YUV :: 0x200;
Luminance :: 0x20000;
}
}
#scope_file
// You might copy the necessary enums if you don't want to pollute the namespace.
#import "dxgi"; // For DXGI_FORMAT
#import "d3d11"; // For D3D11_RESOURCE_MISC_FLAG and D3D11_RESOURCE_DIMENSION
DXT1_CC :: #run make_magic4("DXT1");
DXT2_CC :: #run make_magic4("DXT2");
DXT3_CC :: #run make_magic4("DXT3");
DXT4_CC :: #run make_magic4("DXT4");
DXT5_CC :: #run make_magic4("DXT5");
ATI1_CC :: #run make_magic4("ATI1");
ATI2_CC :: #run make_magic4("ATI2");
BC4U_CC :: #run make_magic4("BC4U");
BC4S_CC :: #run make_magic4("BC4S");
BC5U_CC :: #run make_magic4("BC5U");
BC5S_CC :: #run make_magic4("BC5S");
DX10_CC :: #run make_magic4("DX10");
DDS_MAGIC :: #run make_magic4("DDS ");
DDS_HEADER_REQUIRED_FLAGS : DDS_Header.Flags : .Caps | .Height | .Width | .Pixel_Format;
make_magic4 :: ($text: string) -> u32 {
#assert text.count == 4;
return (cast(*u32) text.data).*;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment