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 = cast(const(ubyte)*) 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 // PERF: use gamut.scanline here 153 154 switch (channels) 155 { 156 case 1: 157 { 158 for (int lx = 0; lx < avail_x; ++lx) 159 { 160 color_quad_u8* p = &pixels[lx + ly * 4]; 161 p.m_c[0] = p.m_c[1] = p.m_c[2] = pixel[lx]; 162 p.m_c[3] = 255; 163 } 164 break; 165 } 166 case 2: 167 { 168 for (int lx = 0; lx < avail_x; ++lx) 169 { 170 color_quad_u8* p = &pixels[lx + ly * 4]; 171 p.m_c[0] = p.m_c[1] = p.m_c[2] = pixel[lx*2]; 172 p.m_c[3] = pixel[lx*2+1]; 173 } 174 break; 175 } 176 case 3: 177 { 178 for (int lx = 0; lx < avail_x; ++lx) 179 { 180 color_quad_u8* p = &pixels[lx + ly * 4]; 181 p.m_c[0] = pixel[lx*3+0]; 182 p.m_c[1] = pixel[lx*3+1]; 183 p.m_c[2] = pixel[lx*3+2]; 184 p.m_c[3] = 255; 185 } 186 break; 187 } 188 case 4: 189 { 190 for (int lx = 0; lx < avail_x; ++lx) 191 { 192 color_quad_u8* p = &pixels[lx + ly * 4]; 193 p.m_c[0] = pixel[lx*4+0]; 194 p.m_c[1] = pixel[lx*4+1]; 195 p.m_c[2] = pixel[lx*4+2]; 196 p.m_c[3] = pixel[lx*4+3]; 197 } 198 break; 199 } 200 default: 201 assert(false); 202 } 203 } 204 } 205 } 206 207 bc7_block_t block; 208 209 if (bc7enc16_compress_block(block.ptr, pixels.ptr, &pack_params)) 210 hasAlpha = true; // Note: hasAlpha unused with .dds 211 212 if (1 != io.write(&block, block.sizeof, 1, handle)) 213 return false; 214 } 215 } 216 217 return true; 218 } 219 220 221 struct DDCOLORKEY 222 { 223 uint dwUnused0; 224 uint dwUnused1; 225 }; 226 227 struct DDPIXELFORMAT 228 { 229 uint dwSize; 230 uint dwFlags; 231 uint dwFourCC; 232 uint dwRGBBitCount; // ATI compressonator will place a FOURCC code here for swizzled/cooked DXTn formats 233 uint dwRBitMask; 234 uint dwGBitMask; 235 uint dwBBitMask; 236 uint dwRGBAlphaBitMask; 237 } 238 239 struct DDSCAPS2 240 { 241 uint dwCaps; 242 uint dwCaps2; 243 uint dwCaps3; 244 uint dwCaps4; 245 } 246 247 struct DDSURFACEDESC2 248 { 249 uint dwSize; 250 uint dwFlags; 251 uint dwHeight; 252 uint dwWidth; 253 union 254 { 255 int lPitch; 256 uint dwLinearSize; 257 } 258 uint dwBackBufferCount; 259 uint dwMipMapCount; 260 uint dwAlphaBitDepth; 261 uint dwUnused0; 262 uint lpSurface; 263 DDCOLORKEY unused0; 264 DDCOLORKEY unused1; 265 DDCOLORKEY unused2; 266 DDCOLORKEY unused3; 267 DDPIXELFORMAT ddpfPixelFormat; 268 DDSCAPS2 ddsCaps; 269 uint dwUnused1; 270 } 271 272 enum uint DDSD_CAPS = 0x00000001; 273 enum uint DDSD_HEIGHT = 0x00000002; 274 enum uint DDSD_WIDTH = 0x00000004; 275 enum uint DDSD_PIXELFORMAT = 0x00001000; 276 enum uint DDSD_LINEARSIZE = 0x00080000; 277 enum uint DDPF_FOURCC = 0x00000004; 278 enum uint DDSCAPS_TEXTURE = 0x00001000; 279 280 alias DXGI_FORMAT = int; 281 enum : DXGI_FORMAT 282 { 283 DXGI_FORMAT_UNKNOWN = 0, 284 DXGI_FORMAT_BC7_UNORM = 98, 285 DXGI_FORMAT_BC7_UNORM_SRGB = 99, 286 } 287 288 struct DDS_HEADER_DXT10 289 { 290 DXGI_FORMAT dxgiFormat; 291 int resourceDimension; 292 uint miscFlag; 293 uint arraySize; 294 uint miscFlags2; 295 } 296