1 /// Format description module
2 module gfx.graal.format;
3 
4 import std.meta : AliasSeq;
5 
6 /// numeric format of texel channels
7 enum NumFormat
8 {
9     uNorm,
10     sNorm,
11     uScaled,
12     sScaled,
13     uInt,
14     sInt,
15     uFloat,
16     sFloat,
17     sRgb,
18 }
19 
20 mixin(surfaceCode!());
21 mixin(formatCode!());
22 
23 /// description of a format
24 struct FormatDesc
25 {
26     SurfaceType surfaceType;
27     NumFormat numFormat;
28     bool packed;
29 }
30 
31 /// get the description of a format
32 FormatDesc formatDesc(Format fmt) {
33     return fmtDescs[cast(uint)fmt];
34 }
35 
36 /// Format properties
37 struct FormatProperties {
38     FormatFeatures linearTiling;
39     FormatFeatures optimalTiling;
40     FormatFeatures buffer;
41 }
42 
43 /// Format features
44 enum FormatFeatures {
45     sampledImage                = 0x0001,
46     storageImage                = 0x0002,
47     storageImageAtomic          = 0x0004,
48     uniformTexelBuffer          = 0x0008,
49     storageTexelBuffer          = 0x0010,
50     storageTexelBufferAtomic    = 0x0020,
51     vertexBuffer                = 0x0040,
52     colorAttachment             = 0x0080,
53     colorAttachmentBlend        = 0x0100,
54     depthStencilAttachment      = 0x0200,
55     blitSrc                     = 0x0400,
56     blitDst                     = 0x0800,
57     sampledImageFilterLinear    = 0x1000,
58 }
59 
60 
61 private:
62 
63 enum fmtDescriptors = AliasSeq!(
64     "R4_G4",            true,       NumFormat.uNorm,
65     "R4_G4_B4_A4",      true,       NumFormat.uNorm,
66     "B4_G4_R4_A4",      true,       NumFormat.uNorm,
67     "R5_G6_B5",         true,       NumFormat.uNorm,
68     "B5_G6_R5",         true,       NumFormat.uNorm,
69     "R5_G5_B5_A1",      true,       NumFormat.uNorm,
70     "B5_G5_R5_A1",      true,       NumFormat.uNorm,
71     "A1_R5_G5_B5",      true,       NumFormat.uNorm,
72     "R8",               false,      NumFormat.uNorm,    NumFormat.sNorm,
73                                     NumFormat.uScaled,  NumFormat.sScaled,
74                                     NumFormat.uInt,     NumFormat.sInt,     NumFormat.sRgb,
75     "R8_G8",            false,      NumFormat.uNorm,    NumFormat.sNorm,
76                                     NumFormat.uScaled,  NumFormat.sScaled,
77                                     NumFormat.uInt,     NumFormat.sInt,     NumFormat.sRgb,
78     "R8_G8_B8",         false,      NumFormat.uNorm,    NumFormat.sNorm,
79                                     NumFormat.uScaled,  NumFormat.sScaled,
80                                     NumFormat.uInt,     NumFormat.sInt,     NumFormat.sRgb,
81     "B8_G8_R8",         false,      NumFormat.uNorm,    NumFormat.sNorm,
82                                     NumFormat.uScaled,  NumFormat.sScaled,
83                                     NumFormat.uInt,     NumFormat.sInt,     NumFormat.sRgb,
84     "R8_G8_B8_A8",      false,      NumFormat.uNorm,    NumFormat.sNorm,
85                                     NumFormat.uScaled,  NumFormat.sScaled,
86                                     NumFormat.uInt,     NumFormat.sInt,     NumFormat.sRgb,
87     "B8_G8_R8_A8",      false,      NumFormat.uNorm,    NumFormat.sNorm,
88                                     NumFormat.uScaled,  NumFormat.sScaled,
89                                     NumFormat.uInt,     NumFormat.sInt,     NumFormat.sRgb,
90     "A8_B8_G8_R8",      true,       NumFormat.uNorm,    NumFormat.sNorm,
91                                     NumFormat.uScaled,  NumFormat.sScaled,
92                                     NumFormat.uInt,     NumFormat.sInt,     NumFormat.sRgb,
93     "A2_R10_G10_B10",   true,       NumFormat.uNorm,    NumFormat.sNorm,
94                                     NumFormat.uScaled,  NumFormat.sScaled,
95                                     NumFormat.uInt,     NumFormat.sInt,
96     "A2_B10_G10_R10",   true,       NumFormat.uNorm,    NumFormat.sNorm,
97                                     NumFormat.uScaled,  NumFormat.sScaled,
98                                     NumFormat.uInt,     NumFormat.sInt,
99     "R16",              false,      NumFormat.uNorm,    NumFormat.sNorm,
100                                     NumFormat.uScaled,  NumFormat.sScaled,
101                                     NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
102     "R16_G16",          false,      NumFormat.uNorm,    NumFormat.sNorm,
103                                     NumFormat.uScaled,  NumFormat.sScaled,
104                                     NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
105     "R16_G16_B16",      false,      NumFormat.uNorm,    NumFormat.sNorm,
106                                     NumFormat.uScaled,  NumFormat.sScaled,
107                                     NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
108     "R16_G16_B16_A16",  false,      NumFormat.uNorm,    NumFormat.sNorm,
109                                     NumFormat.uScaled,  NumFormat.sScaled,
110                                     NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
111     "R32",              false,      NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
112     "R32_G32",          false,      NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
113     "R32_G32_B32",      false,      NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
114     "R32_G32_B32_A32",  false,      NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
115     "R64",              false,      NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
116     "R64_G64",          false,      NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
117     "R64_G64_B64",      false,      NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
118     "R64_G64_B64_A64",  false,      NumFormat.uInt,     NumFormat.sInt,     NumFormat.sFloat,
119     "B10_G11_R11",      true,       NumFormat.uFloat,
120     "E5_B9_G9_R9",      true,       NumFormat.uFloat,
121     "D16",              false,      NumFormat.uNorm,
122     "X8_D24",           true,       NumFormat.uNorm,
123     "D32",              false,      NumFormat.uNorm,
124     "S8",               false,      NumFormat.uInt,
125     "D16_S8",           false,      NumFormat.uNorm,  // note: stencil is always uint
126     "D24_S8",           false,      NumFormat.uNorm,
127     "D32_S8",           false,      NumFormat.sFloat,
128 );
129 
130 template SurfFmtDesc(string n, bool p, NF...) {
131     alias surfName = n;
132     alias packed = p;
133     alias numFormats = NF;
134 }
135 
136 alias surfFmtDescs = parseSurfFmtDescs!(fmtDescriptors);
137 
138 
139 immutable(FormatDesc)[] fmtDescs;
140 
141 shared static this()
142 {
143     import std.format : format;
144 
145     FormatDesc[] fd;
146 
147     foreach (sfd; surfFmtDescs) {
148         mixin(format("alias SurfT = %s;", sfd.surfName));
149         foreach (nf; sfd.numFormats) {
150             mixin(format("fd ~= FormatDesc(SurfaceType.%s, NumFormat.%s, %s);\n",
151                         sfd.surfName, nf, sfd.packed));
152         }
153     }
154 
155     import std.exception : assumeUnique;
156     fmtDescs = assumeUnique(fd);
157 }
158 
159 
160 template parseSurfFmtDescs(Descs...) {
161     static if (Descs.length == 0) {
162         alias parseSurfFmtDescs = AliasSeq!();
163     }
164     else {
165         static assert( is(typeof(Descs[0]) == string) );
166 
167         enum ns = nextString!(Descs[1 .. $]);
168 
169         alias parseSurfFmtDescs = AliasSeq!(
170             SurfFmtDesc!(Descs[0 .. ns+1]),
171             parseSurfFmtDescs!(Descs[ns+1..$])
172         );
173     }
174 }
175 
176 size_t nextString(Args...)() {
177     size_t n=0;
178     foreach(arg; Args) {
179         static if(is(typeof(arg) == string)) {
180             break;
181         }
182         else {
183             ++n;
184         }
185     }
186     return n;
187 }
188 
189 template surfaceCode() {
190     import std.meta : staticMap;
191 
192     enum surfaceCode = buildCode();
193 
194     alias surfDescs = staticMap!(SurfDesc, surfFmtDescs);
195 
196     template SurfDesc(alias fmt) {
197         alias name = fmt.surfName;
198         alias numFormats = fmt.numFormats;
199         enum redBits = name.parseNumAftLetter('R');
200         enum greenBits = name.parseNumAftLetter('G');
201         enum blueBits = name.parseNumAftLetter('B');
202         enum alphaBits = name.parseNumAftLetter('A');
203         enum stencilBits = name.parseNumAftLetter('S');
204         enum depthBits = name.parseNumAftLetter('D');
205         enum sharedExponentBits = name.parseNumAftLetter('E');
206         enum numComponents = name.parseNumComponents();
207 
208         enum totalBits = redBits + greenBits + blueBits + alphaBits +
209                          depthBits + stencilBits + sharedExponentBits +
210                          name.parseNumAftLetter('X');
211     }
212 
213     string buildCode()
214     {
215         string codeStr;
216 
217         void code(Args...)(string fmt, Args args) {
218             import std.format : format;
219             codeStr ~= format(fmt, args);
220         }
221 
222         // enum SurfaceType
223 
224         code("/// the type of texture surface\n");
225         code("public enum SurfaceType {\n");
226         foreach(sd; surfDescs) {
227             code("    %s,\n", sd.name);
228         }
229         code("}\n\n");
230 
231         // properties
232         void switchProperty (string name)() {
233             code("/// get the %s of an image surface\n", name);
234             code("public @property uint %s(in SurfaceType st) {\n", name);
235             code("    switch(st) {\n");
236             foreach(sd; surfDescs) {
237                 mixin("enum num = sd."~name~";");
238                 static if (num > 0) {
239                     code("    case SurfaceType.%s: return %s;\n", sd.name, num);
240                 }
241             }
242             code("    default: return 0;\n");
243             code("    }\n");
244             code("}\n\n");
245         }
246 
247         switchProperty!("totalBits")();
248         switchProperty!("numComponents")();
249         switchProperty!("redBits")();
250         switchProperty!("greenBits")();
251         switchProperty!("blueBits")();
252         switchProperty!("alphaBits")();
253         switchProperty!("depthBits")();
254         switchProperty!("stencilBits")();
255         switchProperty!("sharedExponentBits")();
256 
257         // trait structs
258         foreach(sd; surfDescs) {
259             code("/// static descriptor of surface type %s\n", sd.name);
260             code("public struct %s {\n", sd.name);
261             code("    enum surfaceType = SurfaceType.%s;\n", sd.name);
262             code("    enum totalBits = %s;\n", sd.totalBits);
263             code("    enum redBits = %s;\n", sd.redBits);
264             code("    enum greenBits = %s;\n", sd.greenBits);
265             code("    enum blueBits = %s;\n", sd.blueBits);
266             code("    enum alphaBits = %s;\n", sd.alphaBits);
267             code("    enum depthBits = %s;\n", sd.depthBits);
268             code("    enum stencilBits = %s;\n", sd.stencilBits);
269             code("    enum numComponents = %s;\n", sd.numComponents);
270             code("    alias numFormats = AliasSeq!(");
271                 foreach(nf; sd.numFormats) code("NumFormat.%s, ", nf);
272             code(");\n");
273             // evaluate fmtBaseName for each surface instead of each format to lower ctfe work
274             code(
275                 "    private enum fmtBaseName = \"%s\";\n",
276                 formatBaseName(sd.name, sd.redBits, sd.greenBits, sd.blueBits, sd.alphaBits)
277             );
278             code("}\n");
279         }
280         return codeStr;
281     }
282 
283 }
284 
285 template formatCode()
286 {
287     enum formatCode = buildCode();
288 
289     string buildCode() {
290         string codeStr;
291 
292         void code(Args...)(string fmt, Args args) {
293             import std.format : format;
294             codeStr ~= format(fmt, args);
295         }
296 
297         code("/// the format of an image\n");
298         code("public enum Format {\n");
299         code("    undefined = 0,\n");
300         int i = 1;
301         foreach (sfd; surfFmtDescs) {
302             mixin("alias SurfT = "~sfd.surfName~";");
303             foreach (nf; sfd.numFormats) {
304                 const name = formatEnumName(SurfT.fmtBaseName, nf);
305                 code("    %s = %s,\n", name, i++);
306             }
307         }
308         code("}\n");
309 
310         foreach (sfd; surfFmtDescs) {
311             mixin("alias SurfT = "~sfd.surfName~";");
312             foreach (nf; sfd.numFormats) {
313                 const name = formatTypeName(SurfT.fmtBaseName, nf);
314                 code("/// static descriptor of image format %s\n", name);
315                 code("public struct %s {\n", name);
316                 code("    enum surfaceType = SurfaceType.%s;\n", sfd.surfName);
317                 code("    enum numFormat = NumFormat.%s;\n", nf);
318                 code("    enum packed = %s;\n", sfd.packed);
319                 code("}\n");
320             }
321         }
322 
323         return codeStr;
324     }
325 }
326 
327 string formatBaseName(in string name, in uint redBits, in uint greenBits,
328                       in uint blueBits, in uint alphaBits) {
329     import std.algorithm : all, filter;
330     import std.array : array;
331     import std.conv : to;
332     import std.range : chain, only;
333     import std.uni : isNumber, toLower;
334 
335     const contractNumComps = redBits > 0 &&
336         only(greenBits, blueBits, alphaBits)
337             .filter!(n => n != 0)
338             .all!(n => n == redBits);
339 
340     string res = name.toLower
341                 .filter!(c => c != '_').to!string;
342     if (contractNumComps) {
343         res = res.filter!(c => !c.isNumber).to!string ~ redBits.to!string;
344     }
345     return res;
346 }
347 
348 string formatEnumName(string baseName, NumFormat nf) {
349     import std.format : format;
350     return format("%s_%s", baseName, nf);
351 }
352 string formatTypeName(string baseName, NumFormat nf) {
353     import std.format : format;
354     import std.string : capitalize;
355     return format("%s_%s", capitalize(baseName), capitalize(format("%s", nf)));
356 }
357 
358 ubyte parseNumComponents(string name) {
359     import std.uni : isAlpha;
360     import std.algorithm : count;
361 
362     return cast(ubyte)name.count!((c) {
363         return c.isAlpha && c != '_' && c != 'E'; // shared exponent does not count as a component
364     });
365 }
366 
367 ubyte parseNumAftLetter(string name, char letter) {
368     import std.algorithm : find, countUntil;
369     import std.uni : isNumber;
370     import std.conv : to;
371 
372     string n = name.find(letter);
373     if (n.length == 0) return 0;
374     n = n[1 .. $];
375     auto end = n.countUntil!((c) {
376         return !c.isNumber;
377     });
378     if (end != -1) n = n[0 .. end];
379     return n.to!ubyte;
380 }