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