1 /** 2 DDS support, containing BC7 encoded textures. 3 4 Copyright: Copyright Guillaume Piolat 2022 5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 module gamut.plugins.dds; 8 9 nothrow @nogc @safe: 10 11 //port core.stdc.stdlib: malloc, free, realloc; 12 import core.stdc.string: memset; 13 import gamut.types; 14 import gamut.io; 15 import gamut.image; 16 import gamut.plugin; 17 import gamut.internals.errors; 18 19 version(encodeDDS) 20 import gamut.codecs.bc7enc16; 21 22 ImageFormatPlugin makeDDSPlugin() 23 { 24 ImageFormatPlugin p; 25 p.format = "DDS"; 26 p.extensionList = "dds"; 27 28 p.mimeTypes = "image/vnd.ms-dds"; 29 30 p.loadProc = null; 31 32 version(encodeDDS) 33 p.saveProc = &saveDDS; 34 else 35 p.saveProc = null; 36 p.detectProc = &detectDDS; 37 return p; 38 } 39 40 bool detectDDS(IOStream *io, IOHandle handle) @trusted 41 { 42 static immutable ubyte[4] ddsSignature = [0x44, 0x44, 0x53, 0x20]; // "DDS " 43 return fileIsStartingWithSignature(io, handle, ddsSignature); 44 } 45 46 version(encodeDDS) 47 bool saveDDS(ref const(Image) image, IOStream *io, IOHandle handle, int page, int flags, void *data) @trusted 48 { 49 if (page != 0) 50 return false; 51 52 int channels = 0; 53 54 // The following format are accepted: 8-bit with 1/2/3/4 channels. 55 switch (image._type) 56 { 57 case PixelType.l8: channels = 1; break; 58 case PixelType.la8: channels = 2; break; 59 case PixelType.rgb8: channels = 3; break; 60 case PixelType.rgba8: channels = 4; break; 61 default: 62 return false; // not supported 63 } 64 65 // Encode to blocks. How many 4x4 block do we need? 66 int width = image.width(); 67 int height = image.height(); 68 int block_W = (image.width + 3) / 4; 69 int block_H = (image.height + 3) / 4; 70 int numBlocks = block_W * block_H; 71 72 73 // 1. Write DDS header and stuff 74 { 75 char[4] magic = "DDS "; 76 if (4 != io.write(magic.ptr, 1, 4, handle)) 77 return false; 78 79 static uint PIXEL_FMT_FOURCC(ubyte a, ubyte b, ubyte c, ubyte d) 80 { 81 return ((a) | ((b) << 8U) | ((c) << 16U) | ((d) << 24U)); 82 } 83 84 DDSURFACEDESC2 desc; 85 memset(&desc, 0, desc.sizeof); 86 desc.dwSize = desc.sizeof; 87 desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS; 88 desc.dwWidth = width; 89 desc.dwHeight = height; 90 desc.ddsCaps.dwCaps = DDSCAPS_TEXTURE; 91 desc.ddpfPixelFormat.dwSize = (desc.ddpfPixelFormat).sizeof; 92 desc.ddpfPixelFormat.dwFlags |= DDPF_FOURCC; 93 desc.ddpfPixelFormat.dwFourCC = cast(uint) PIXEL_FMT_FOURCC('D', 'X', '1', '0'); 94 desc.ddpfPixelFormat.dwRGBBitCount = 0; 95 const uint pixel_format_bpp = 8; 96 desc.lPitch = (((desc.dwWidth + 3) & ~3) * ((desc.dwHeight + 3) & ~3) * pixel_format_bpp) >> 3; 97 desc.dwFlags |= DDSD_LINEARSIZE; 98 99 if (1 != io.write(&desc, desc.sizeof, 1, handle)) 100 return false; 101 102 DDS_HEADER_DXT10 hdr10; 103 memset(&hdr10, 0, hdr10.sizeof); 104 105 // Not all tools support DXGI_FORMAT_BC7_UNORM_SRGB (like NVTT), but ddsview in DirectXTex pays attention to it. So not sure what to do here. 106 // For best compatibility just write DXGI_FORMAT_BC7_UNORM. 107 //hdr10.dxgiFormat = srgb ? DXGI_FORMAT_BC7_UNORM_SRGB : DXGI_FORMAT_BC7_UNORM; 108 hdr10.dxgiFormat = DXGI_FORMAT_BC7_UNORM; 109 hdr10.resourceDimension = 3; /* D3D10_RESOURCE_DIMENSION_TEXTURE2D; */ 110 hdr10.arraySize = 1; 111 112 if (1 != io.write(&hdr10, hdr10.sizeof, 1, handle)) 113 return false; 114 } 115 116 117 // 2. Write compressed blocks 118 119 bc7enc16_compress_block_init(); 120 alias bc7_block_t = ubyte[16]; // A 128-bit block containing a 4x4 pixel patch. 121 122 enum bool perceptual = true; 123 124 bc7enc16_compress_block_params pack_params; 125 bc7enc16_compress_block_params_init(&pack_params); 126 if (!perceptual) 127 bc7enc16_compress_block_params_init_linear_weights(&pack_params); 128 129 bool hasAlpha = false; 130 131 for (int y = 0; y < block_H; ++y) 132 { 133 for (int x = 0; x < block_W; ++x) 134 { 135 color_quad_u8[16] pixels; // init important, in case dimension not multiple of 4. 136 137 // Read a patch of 4x4 pixels, put it in `pixels`. 138 { 139 for (int ly = 0; ly < 4; ++ly) 140 { 141 if (y*4 + ly < height) 142 { 143 const(ubyte)* line = image.scanline(y*4 + ly); 144 const(ubyte)* pixel = &line[x * 4 * channels]; 145 146 assert(x*4 < width); 147 148 int avail_x = 4; 149 if (x*4 + 4 > width) 150 avail_x = width - x*4; 151 152 switch (channels) 153 { 154 case 1: 155 { 156 for (int lx = 0; lx < avail_x; ++lx) 157 { 158 color_quad_u8* p = &pixels[lx + ly * 4]; 159 p.m_c[0] = p.m_c[1] = p.m_c[2] = pixel[lx]; 160 p.m_c[3] = 255; 161 } 162 break; 163 } 164 case 2: 165 { 166 for (int lx = 0; lx < avail_x; ++lx) 167 { 168 color_quad_u8* p = &pixels[lx + ly * 4]; 169 p.m_c[0] = p.m_c[1] = p.m_c[2] = pixel[lx*2]; 170 p.m_c[3] = pixel[lx*2+1]; 171 } 172 break; 173 } 174 case 3: 175 { 176 for (int lx = 0; lx < avail_x; ++lx) 177 { 178 color_quad_u8* p = &pixels[lx + ly * 4]; 179 p.m_c[0] = pixel[lx*3+0]; 180 p.m_c[1] = pixel[lx*3+1]; 181 p.m_c[2] = pixel[lx*3+2]; 182 p.m_c[3] = 255; 183 } 184 break; 185 } 186 case 4: 187 { 188 for (int lx = 0; lx < avail_x; ++lx) 189 { 190 color_quad_u8* p = &pixels[lx + ly * 4]; 191 p.m_c[0] = pixel[lx*4+0]; 192 p.m_c[1] = pixel[lx*4+1]; 193 p.m_c[2] = pixel[lx*4+2]; 194 p.m_c[3] = pixel[lx*4+3]; 195 } 196 break; 197 } 198 default: 199 assert(false); 200 } 201 } 202 } 203 } 204 205 bc7_block_t block; 206 207 if (bc7enc16_compress_block(block.ptr, pixels.ptr, &pack_params)) 208 hasAlpha = true; // Note: hasAlpha unused with .dds 209 210 if (1 != io.write(&block, block.sizeof, 1, handle)) 211 return false; 212 } 213 } 214 215 return true; 216 } 217 218 219 struct DDCOLORKEY 220 { 221 uint dwUnused0; 222 uint dwUnused1; 223 }; 224 225 struct DDPIXELFORMAT 226 { 227 uint dwSize; 228 uint dwFlags; 229 uint dwFourCC; 230 uint dwRGBBitCount; // ATI compressonator will place a FOURCC code here for swizzled/cooked DXTn formats 231 uint dwRBitMask; 232 uint dwGBitMask; 233 uint dwBBitMask; 234 uint dwRGBAlphaBitMask; 235 } 236 237 struct DDSCAPS2 238 { 239 uint dwCaps; 240 uint dwCaps2; 241 uint dwCaps3; 242 uint dwCaps4; 243 } 244 245 struct DDSURFACEDESC2 246 { 247 uint dwSize; 248 uint dwFlags; 249 uint dwHeight; 250 uint dwWidth; 251 union 252 { 253 int lPitch; 254 uint dwLinearSize; 255 } 256 uint dwBackBufferCount; 257 uint dwMipMapCount; 258 uint dwAlphaBitDepth; 259 uint dwUnused0; 260 uint lpSurface; 261 DDCOLORKEY unused0; 262 DDCOLORKEY unused1; 263 DDCOLORKEY unused2; 264 DDCOLORKEY unused3; 265 DDPIXELFORMAT ddpfPixelFormat; 266 DDSCAPS2 ddsCaps; 267 uint dwUnused1; 268 } 269 270 enum uint DDSD_CAPS = 0x00000001; 271 enum uint DDSD_HEIGHT = 0x00000002; 272 enum uint DDSD_WIDTH = 0x00000004; 273 enum uint DDSD_PIXELFORMAT = 0x00001000; 274 enum uint DDSD_LINEARSIZE = 0x00080000; 275 enum uint DDPF_FOURCC = 0x00000004; 276 enum uint DDSCAPS_TEXTURE = 0x00001000; 277 278 alias DXGI_FORMAT = int; 279 enum : DXGI_FORMAT 280 { 281 DXGI_FORMAT_UNKNOWN = 0, 282 DXGI_FORMAT_BC7_UNORM = 98, 283 DXGI_FORMAT_BC7_UNORM_SRGB = 99, 284 } 285 286 struct DDS_HEADER_DXT10 287 { 288 DXGI_FORMAT dxgiFormat; 289 int resourceDimension; 290 uint miscFlag; 291 uint arraySize; 292 uint miscFlags2; 293 } 294