The OpenD Programming Language

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