1 module gfx.decl.engine;
2 
3 import gfx.core.rc : Disposable;
4 import gfx.core.typecons : Option;
5 import gfx.graal.format : Format;
6 import sdlang;
7 
8 class GfxSDLErrorException : Exception
9 {
10     string msg;
11     Location location;
12 
13     this(string msg, Location location) {
14         import std.format : format;
15         this.msg = msg;
16         this.location = location;
17         super(format("%s:(%s,%s): Gfx-SDL error: %s", location.file, location.line, location.col, msg));
18     }
19 }
20 
21 class NoSuchAssetException : GfxSDLErrorException
22 {
23     string assetPath;
24 
25     this(string assetPath, Location location=Location.init)
26     {
27         import std.format : format;
28         this.assetPath = assetPath;
29         super(format("no such asset: %s", assetPath), location);
30     }
31 }
32 
33 class NoSuchViewException : GfxSDLErrorException
34 {
35     string viewName;
36 
37     this(string viewName, Location location=Location.init)
38     {
39         import std.format : format;
40         this.viewName = viewName;
41         super(format("no such view: %s", viewName), location);
42     }
43 }
44 
45 class NoSuchStructException : GfxSDLErrorException
46 {
47     string structName;
48 
49     this(string structName, Location location=Location.init)
50     {
51         import std.format : format;
52         this.structName = structName;
53         super(format("no such struct: %s", structName), location);
54     }
55 }
56 
57 class NoSuchFieldException : GfxSDLErrorException
58 {
59     string structName;
60     string fieldName;
61 
62     this(string structName, string fieldName, Location location=Location.init)
63     {
64         import std.format : format;
65         this.structName = structName;
66         this.fieldName = fieldName;
67         super(format("no such field: %s.%s", structName, fieldName), location);
68     }
69 }
70 
71 class UnknownFieldFormatException : GfxSDLErrorException
72 {
73     string structName;
74     string fieldName;
75 
76     this(string structName, string fieldName, Location location=Location.init)
77     {
78         import std.format : format;
79         this.structName = structName;
80         this.fieldName = fieldName;
81         super(format("field format is not known for: %s.%s", structName, fieldName), location);
82     }
83 }
84 
85 class DeclarativeEngine : Disposable
86 {
87     import gfx.core.rc : Rc;
88     import gfx.core.types;
89     import gfx.decl.store : DeclarativeStore;
90     import gfx.graal.device : Device;
91     import gfx.graal.pipeline;
92     import gfx.graal.renderpass : RenderPass;
93     import std.exception : enforce;
94     import std.format : format;
95 
96     private Rc!Device _device;
97     private DeclarativeStore _store;
98     private string[] _assetPaths;
99     private immutable(void)[][string] _views;
100     private StructDecl[] _structDecls;
101     private string[] _localKeys;
102     private int _storeAnonKey;
103 
104 
105     this(Device device) {
106         _device = device;
107         _store = new DeclarativeStore;
108     }
109 
110     override void dispose() {
111         _store.dispose();
112         _store = null;
113         _device.unload();
114     }
115 
116     @property DeclarativeStore store() {
117         return _store;
118     }
119 
120     void addAssetPath(in string path) {
121         _assetPaths ~= path;
122     }
123 
124     void addView(string name)() {
125         _views[name] = cast(immutable(void)[])import(name);
126     }
127 
128     void declareStruct(T)() {
129         _structDecls ~= StructDecl.makeFor!T();
130     }
131 
132     void parseSDLSource(string sdl, string filename=null)
133     {
134         auto root = parseSource(sdl, filename);
135         parseSDL(root);
136     }
137 
138     void parseSDLFile(string filename)
139     {
140         auto root = parseFile(filename);
141         parseSDL(root);
142     }
143 
144     void parseSDLView(string name)()
145     {
146         const sdl = cast(string)import(name);
147         auto root = parseSource(sdl, name);
148         parseSDL(root);
149     }
150 
151 
152     private void parseSDL(Tag root)
153     {
154         PipelineInfo[] plis;
155         string[] plKeys;
156 
157         foreach (t; root.namespaces["graal"].tags) {
158             if (t.name == "DescriptorSetLayout") {
159                 DescriptorSetLayout dsl;
160                 const key = parseDescriptorSetLayout(t, dsl);
161                 _store.store!DescriptorSetLayout(key, dsl);
162             }
163             else if (t.name == "PipelineLayout") {
164                 PipelineLayout pll;
165                 const key = parsePipelineLayout(t, pll);
166                 _store.store!PipelineLayout(key, pll);
167             }
168             else if (t.name == "ShaderModule") {
169                 ShaderModule sm;
170                 const key = parseShaderModule(t, sm);
171                 _store.store!ShaderModule(key, sm);
172             }
173             else if (t.name == "Pipeline") {
174                 PipelineInfo pli = void;
175                 plKeys ~= parsePipelineInfo(t, pli);
176                 plis ~= pli;
177             }
178             else if (t.name == "PipelineInfo") {
179                 PipelineInfo pli = void;
180                 const key = parsePipelineInfo(t, pli);
181                 _store.store!PipelineInfo(key, pli);
182             }
183         }
184 
185         if (plis.length) {
186             auto pls = _device.createPipelines(plis);
187             foreach(i, pl; pls) {
188                 _store.store!Pipeline(plKeys[i], pl);
189             }
190         }
191 
192         foreach (k; _localKeys) _store.remove(k);
193     }
194 
195     private string parseDescriptorSetLayout(Tag tag, out DescriptorSetLayout dsl)
196     {
197         import std.conv : to;
198 
199         PipelineLayoutBinding[] bindings;
200         auto btags = tag.expectTag("bindings");
201         foreach (bt; btags.all.tags) {
202             PipelineLayoutBinding binding;
203             binding.binding = bt.expectValue!int();
204             binding.descriptorType = bt.expectAttribute!string("descriptorType").to!DescriptorType;
205             binding.descriptorCount = bt.expectAttribute!int("descriptorCount");
206             binding.stages = bt.expectAttribute!string("stages").parseFlags!ShaderStage();
207             bindings ~= binding;
208         }
209 
210         dsl = _device.createDescriptorSetLayout(bindings);
211         return getStoreKey(tag);
212     }
213 
214     private string parsePipelineLayout(Tag tag, out PipelineLayout pll)
215     {
216         DescriptorSetLayout[] layouts;
217         auto layoutsTag = tag.getTag("layouts");
218         if (layoutsTag) {
219             foreach (lt; layoutsTag.all.tags) {
220                 layouts ~= parseStoreRefOrLiteral!(DescriptorSetLayout, parseDescriptorSetLayout)(lt);
221             }
222         }
223 
224         PushConstantRange[] ranges;
225         auto rangesTag = tag.getTag("ranges");
226         if (rangesTag) {
227             foreach (rt; rangesTag.all.tags) {
228                 PushConstantRange pcr;
229                 pcr.stages = rt.expectAttribute!string("stages").parseFlags!ShaderStage();
230                 pcr.size = expectSizeOffsetAttr(rt, "size");
231                 pcr.offset = expectSizeOffsetAttr(rt, "offset");
232                 ranges ~= pcr;
233             }
234         }
235 
236         pll = _device.createPipelineLayout(layouts, ranges);
237         return getStoreKey(tag);
238     }
239 
240     private string parseShaderModule(Tag tag, out ShaderModule mod)
241     {
242         immutable(uint)[] src;
243         auto st = tag.expectTag("source");
244         string srcStr;
245         ubyte[] srcData;
246         if (st.tryGetValue!string(srcStr)) {
247             src = getShaderSrcFromString(srcStr, st.location);
248         }
249         else if (st.tryGetValue!(ubyte[])(srcData)) {
250             import std.exception : assumeUnique;
251             src = assumeUnique(cast(uint[])srcData);
252         }
253         else {
254             throw new GfxSDLErrorException(
255                 "ill-formed source tag in graal:ShaderModule", tag.location
256             );
257         }
258 
259         const ep = tag.getTagValue!string("entryPoint", "main");
260 
261         mod = _device.createShaderModule(src, ep);
262         return getStoreKey(tag);
263     }
264 
265     private string parseShaderModuleAttr(Tag tag, out ShaderModule mod)
266     {
267         immutable(uint)[] src;
268         auto srcAttr = enforce(findAttr(tag, "source"), new GfxSDLErrorException(
269             "source attribute is mandatory in graal:ShaderModule", tag.location
270         ));
271         string srcStr;
272         ubyte[] srcData;
273         if (srcAttr.tryGetValue!string(srcStr)) {
274             src = getShaderSrcFromString(srcStr, srcAttr.location);
275         }
276         else if (srcAttr.tryGetValue!(ubyte[])(srcData)) {
277             import std.exception : assumeUnique;
278             src = assumeUnique(cast(uint[])srcData);
279         }
280         else {
281             throw new GfxSDLErrorException(
282                 "ill-formed source attribute in graal:ShaderModule", tag.location
283             );
284         }
285 
286         const ep = tag.getAttribute!string("entryPoint", "main");
287 
288         mod = _device.createShaderModule(src, ep);
289         return getStoreKeyAttr(tag);
290     }
291 
292     immutable(uint)[] getShaderSrcFromString(in string srcStr, in Location location)
293     {
294         import std.algorithm : startsWith;
295 
296         if (srcStr.startsWith("view:")) {
297             const name = srcStr["view:".length .. $];
298             return cast(immutable(uint)[])getView(name, location);
299         }
300         else if (srcStr.startsWith("asset:")) {
301             const name = srcStr["asset:".length .. $];
302             return cast(immutable(uint)[])getAsset(name, location);
303         }
304         else {
305             throw new GfxSDLErrorException(
306                 format("\"%s\" could not lead to a shader source file", srcStr),
307                 location
308             );
309         }
310     }
311 
312     private string parsePipelineInfo(Tag tag, out PipelineInfo plInfo)
313     {
314         foreach (t; tag.expectTag("shaders").tags) {
315             ShaderModule sm;
316             if (t.attributes.length) {
317                 // inline shader
318                 const key = parseShaderModuleAttr(t, sm);
319                 _store.store!ShaderModule(key, sm);
320             }
321             else {
322                 // literal or alreadystored
323                 sm = parseStoreRefOrLiteral!(ShaderModule, parseShaderModule)(t);
324             }
325             switch (t.name) {
326             case "vertex":      plInfo.shaders.vertex = sm; break;
327             case "tessControl": plInfo.shaders.tessControl = sm; break;
328             case "tessEval":    plInfo.shaders.tessEval = sm; break;
329             case "geometry":    plInfo.shaders.geometry = sm; break;
330             case "fragment":    plInfo.shaders.fragment = sm; break;
331             default:
332                 throw new GfxSDLErrorException(
333                     "Unknown shader stage: " ~ t.name, t.location
334                 );
335             }
336         }
337 
338         foreach(t; tag.expectTag("inputBindings").tags) {
339             VertexInputBinding vib;
340             vib.binding = t.expectValue!int();
341             vib.stride = expectSizeOffsetAttr(t, "stride");
342             import std.typecons : Flag;
343             vib.instanced = cast(Flag!"instanced")t.getAttribute!bool("instanced", false);
344             plInfo.inputBindings ~= vib;
345         }
346 
347         foreach(t; tag.expectTag("inputAttribs").tags) {
348             VertexInputAttrib via;
349             via.location = t.expectValue!int();
350             via.binding = t.expectAttribute!int("binding");
351             auto attr = t.findAttr("member");
352             if (attr) {
353                 import std.algorithm : findSplit;
354                 const ms = attr.value.get!string();
355                 const sf = ms.findSplit(".");
356                 if (!sf[2].length) {
357                     throw new GfxSDLErrorException(format(
358                         "could not resolve \"%s\" to a struct field", ms
359                     ), attr.location);
360                 }
361                 const s = findStruct(sf[0], attr.location);
362                 via.format = s.getFormat(sf[2], attr.location);
363                 via.offset = s.getOffset(sf[2], attr.location);
364             }
365             else {
366                 via.format = expectFormatAttr(t, "format");
367                 via.offset = expectSizeOffsetAttr(t, "offset");
368             }
369             plInfo.inputAttribs ~= via;
370         }
371 
372         plInfo.assembly = parseInputAssembly(tag.expectTag("assembly"));
373         plInfo.rasterizer = parseRasterizer(tag.expectTag("rasterizer"));
374         plInfo.viewports = parseViewportConfigs(tag);
375         plInfo.depthInfo = parseDepthInfo(tag.getTag("depthInfo"));
376         plInfo.stencilInfo = parseStencilInfo(tag.getTag("stencilInfo"));
377         plInfo.blendInfo = parseColorBlendInfo(tag.expectTag("blendInfo"));
378         auto dynTag = tag.getTag("dynamicStates");
379         if (dynTag) {
380             import std.algorithm : map;
381             import std.array : array;
382             import std.conv : to;
383             plInfo.dynamicStates = dynTag.values.map!(v => v.get!string.to!DynamicState).array;
384         }
385         plInfo.layout = parseStoreRefOrLiteral!(PipelineLayout, parsePipelineLayout)(tag.expectTag("layout"));
386         auto rpTag = tag.expectTag("renderPass");
387         plInfo.renderPass = expectStoreRef!RenderPass(rpTag);
388         plInfo.subpassIndex = rpTag.expectAttribute!int("subpass");
389 
390         return getStoreKey(tag);
391     }
392 
393     private InputAssembly parseInputAssembly(Tag tag)
394     {
395         import std.conv : to;
396         import std.typecons : Flag;
397 
398         InputAssembly assembly;
399         assembly.primitive = tag.expectAttribute!string("primitive").to!Primitive;
400         assembly.primitiveRestart =
401                 cast(Flag!"primitiveRestart")tag.getAttribute!bool("primitiveRestart", false);
402         return assembly;
403     }
404 
405     private Rasterizer parseRasterizer(Tag tag)
406     {
407         import std.conv : to;
408         import std.typecons : Flag;
409         import gfx.core.typecons : some;
410 
411         Rasterizer res;
412         res.mode = tag.expectAttribute!string("polygonMode").to!PolygonMode;
413         res.cull = tag.getAttribute!string("cull", "none").parseFlags!Cull;
414         res.front = tag.getAttribute!string("front", "ccw").to!FrontFace;
415         res.depthClamp = cast(Flag!"depthClamp")tag.getAttribute!bool("depthClamp", false);
416         res.lineWidth = tag.getAttribute!float("lineWidth", 1f);
417         auto dbt = tag.getTag("depthBias");
418         if (dbt) {
419             DepthBias db;
420             db.slopeFactor = dbt.expectAttribute!float("slope");
421             db.constantFactor = dbt.expectAttribute!float("const");
422             db.clamp = dbt.getAttribute!float("clamp", 0f);
423             res.depthBias = some(db);
424         }
425         return res;
426     }
427 
428     private ViewportConfig[] parseViewportConfigs(Tag plTag)
429     {
430         Viewport[] vps;
431         Rect[] scs;
432         Location loc;
433         auto vpt = plTag.getTag("viewports");
434         if (vpt) {
435             foreach(t; plTag.tags) {
436                 if (t.name == "viewport") {
437                     vps ~= parseViewport(t);
438                 }
439                 else if (t.name == "scissors") {
440                     scs ~= parseRect(t);
441                 }
442             }
443             loc = vpt.location;
444         }
445         else {
446             vpt = plTag.getTag("viewport");
447             if (vpt) {
448                 vps ~= parseViewport(vpt);
449                 loc = vpt.location;
450             }
451         }
452 
453         if (!vps.length && !scs.length) return null;
454         if (scs.length && scs.length != vps.length) {
455             throw new GfxSDLErrorException("must state the same number of viewport and scissors", loc);
456         }
457 
458         ViewportConfig[] configs;
459         if (vps.length == scs.length) {
460             foreach (i; 0 .. vps.length) {
461                 configs ~= ViewportConfig(vps[i], scs[i]);
462             }
463         }
464         else {
465             assert(vps.length && !scs.length);
466             foreach (vp; vps) {
467                 const sc = Rect(cast(uint)vp.x, cast(uint)vp.y, cast(uint)vp.width, cast(uint)vp.height);
468                 configs ~= ViewportConfig(vp, sc);
469             }
470         }
471         return configs;
472     }
473 
474 
475     private Viewport parseViewport(Tag tag)
476     {
477         Viewport vp = void;
478         vp.x = expectLiteralOrStoreValAttr!float(tag, "x");
479         vp.y = expectLiteralOrStoreValAttr!float(tag, "y");
480         vp.width = expectLiteralOrStoreValAttr!float(tag, "width");
481         vp.height = expectLiteralOrStoreValAttr!float(tag, "height");
482         vp.minDepth = getLiteralOrStoreValAttr!float(tag, "minDepth", 0f);
483         vp.minDepth = getLiteralOrStoreValAttr!float(tag, "maxDepth", 1f);
484         return vp;
485     }
486 
487     private Rect parseRect(Tag tag)
488     {
489         Rect r = void;
490         r.x = expectLiteralOrStoreValAttr!int(tag, "x");
491         r.y = expectLiteralOrStoreValAttr!int(tag, "y");
492         r.width = expectLiteralOrStoreValAttr!int(tag, "width");
493         r.height = expectLiteralOrStoreValAttr!int(tag, "height");
494         return r;
495     }
496 
497     private DepthInfo parseDepthInfo(Tag tag)
498     {
499         import std.conv : to;
500         import std.typecons : Flag;
501 
502         DepthInfo res;
503         // no tag = disabled = init
504         if (tag) {
505             // can optionally specify on/off as value (defaults to on)
506             res.enabled = cast(Flag!"enabled")tag.getValue!bool(true);
507             if (res.enabled) {
508                 res.write = cast(Flag!"write")tag.expectAttribute!bool("write");
509                 res.compareOp = tag.expectAttribute!string("compareOp").to!CompareOp;
510                 auto bt = tag.getTag("boundsTest");
511                 if (bt) {
512                     // same as depth test, on/off is optional and defaults to on
513                     res.boundsTest = cast(Flag!"boundsTest")tag.getValue!bool(true);
514                     if (res.boundsTest) {
515                         res.minBounds = expectLiteralOrStoreValAttr!float(bt, "min");
516                         res.maxBounds = expectLiteralOrStoreValAttr!float(bt, "max");
517                     }
518                 }
519             }
520         }
521         return res;
522     }
523 
524     StencilInfo parseStencilInfo(Tag tag)
525     {
526         import std.typecons : Flag;
527 
528         StencilInfo res;
529         if (tag) {
530             res.enabled = cast(Flag!"enabled")tag.getValue!bool(true);
531             res.front = parseStencilOpState(tag.getTag("front"));
532             res.back = parseStencilOpState(tag.getTag("back"));
533         }
534         return res;
535     }
536 
537     StencilOpState parseStencilOpState(Tag tag)
538     {
539         import std.conv : to;
540 
541         if (!tag) return StencilOpState.init;
542 
543         StencilOpState res = void;
544         res.compareOp = tag.expectAttribute!string("compareOp").to!CompareOp;
545         string ops;
546         if (tag.tryGetAttr!string("op", ops)) {
547             const op = ops.to!StencilOp;
548             res.failOp = op; res.passOp = op; res.depthFailOp = op;
549         }
550         else {
551             res.failOp = tag.expectAttribute!string("failOp").to!StencilOp;
552             res.passOp = tag.expectAttribute!string("passOp").to!StencilOp;
553             res.depthFailOp = tag.expectAttribute!string("depthFailOp").to!StencilOp;
554         }
555         int mask;
556         if (tag.tryGetAttr!int("mask", mask)) {
557             res.compareMask = mask; res.writeMask = mask; res.refMask = mask;
558         }
559         else {
560             res.compareMask = tag.expectAttribute!int("compareMask");
561             res.writeMask = tag.expectAttribute!int("writeMask");
562             res.refMask = tag.expectAttribute!int("refMask");
563         }
564         return res;
565     }
566 
567     private ColorBlendInfo parseColorBlendInfo(Tag tag)
568     {
569         import gfx.core.typecons : some;
570         import std.conv : to;
571 
572         ColorBlendInfo res;
573 
574         bool lopEnabled;
575         string lopStr;
576         if (tag.tryGetAttr!bool("logicOp", lopEnabled)) {
577             if (lopEnabled) {
578                 throw new GfxSDLErrorException("logicOp can only be set to off or to LogicOp value", tag.location);
579             }
580         }
581         else if (tag.tryGetAttr!string("logicOp", lopStr)) {
582             res.logicOp = some(lopStr.to!LogicOp);
583         }
584 
585         auto attTag = tag.getTag("attachments");
586         if (attTag) foreach(t; attTag.tags) {
587             ColorBlendAttachment attachment;
588             if (t.name == "blend") {
589                 import std.typecons : Yes;
590                 attachment.enabled = Yes.enabled;
591                 attachment.colorBlend = parseBlendState(t.expectTag("color"));
592                 attachment.alphaBlend = parseBlendState(t.expectTag("alpha"));
593             }
594             else if (t.name != "solid") {
595                 throw new GfxSDLErrorException(format(
596                     "tag \"%s\" is not allowed in color attachments", t.name
597                 ), t.location);
598             }
599             string maskStr;
600             if (t.tryGetAttr!string("colorMask", maskStr)) {
601                 attachment.colorMask = maskStr.to!ColorMask;
602             }
603             else {
604                 attachment.colorMask = ColorMask.all;
605             }
606             res.attachments ~= attachment;
607         }
608 
609         auto constTag = tag.getTag("blendConstants");
610         if (constTag) {
611             import std.algorithm : map;
612             import std.array : array;
613 
614             const bc = constTag.values.map!(v => v.coerce!float).array;
615             if (bc.length != 4) {
616                 throw new GfxSDLErrorException("blendConstants must have 4 floats", constTag.location);
617             }
618             res.blendConstants = bc;
619         }
620 
621         return res;
622     }
623 
624     private BlendState parseBlendState(Tag tag)
625     {
626         import std.conv : to;
627 
628         BlendState state;
629         state.factor.from = tag.expectAttribute!string("srcFactor").to!BlendFactor;
630         state.factor.to = tag.expectAttribute!string("destFactor").to!BlendFactor;
631         state.op = tag.expectAttribute!string("op").to!BlendOp;
632         return state;
633     }
634 
635     private string getStoreKey(Tag tag)
636     {
637         auto t = tag.getTag("store");
638         if (t) return t.expectValue!string();
639         t = tag.getTag("local-store");
640         if (t) {
641             const key = t.expectValue!string();
642             _localKeys ~= key;
643             return key;
644         }
645         else {
646             const key = newAnonKey();
647             _localKeys ~= key;
648             return key;
649         }
650     }
651 
652     private string getStoreKeyAttr(Tag tag)
653     {
654         foreach (attr; tag.attributes) {
655             if (attr.name == "store") {
656                 return attr.value.get!string();
657             }
658             if (attr.name == "local-store") {
659                 const key = attr.value.get!string();
660                 _localKeys ~= key;
661                 return key;
662             }
663         }
664         {
665             const key = newAnonKey();
666             _localKeys ~= key;
667             return key;
668         }
669     }
670 
671     private string newAnonKey()
672     {
673         import std.conv : to;
674         const key = ++_storeAnonKey;
675         return key.to!string;
676     }
677 
678     private immutable(void)[] getAsset(in string name, in Location location)
679     {
680         import std.algorithm : map;
681         import std.file : exists, read;
682         import std.path : buildPath;
683         import std.exception : assumeUnique;
684 
685         foreach (p; _assetPaths.map!(ap => buildPath(ap, name))) {
686             if (exists(p)) {
687                 return assumeUnique(read(p));
688             }
689         }
690         throw new NoSuchAssetException(name, location);
691     }
692 
693     private immutable(void)[] getView(in string name, in Location location)
694     {
695         auto p = name in _views;
696         if (!p) {
697             throw new NoSuchViewException(name, location);
698         }
699         return *p;
700     }
701 
702     private T expectStoreRef(T)(Tag tag)
703     {
704         import std.algorithm : startsWith;
705 
706         string storeStr = tag.expectValue!string();
707         if (!storeStr.startsWith("store:")) {
708             throw new GfxSDLErrorException(
709                 format("Cannot parse %s: " ~
710                     "must specify a store reference or a literal " ~
711                     "(\"%s\" is not a valid store reference)", T.stringof, storeStr),
712                 tag.location
713             );
714         }
715         const key = storeStr["store:".length .. $];
716         return _store.expect!T(key);
717     }
718 
719     private T parseStoreRefOrLiteral(T, alias parseF)(Tag tag)
720     {
721         import std.format : format;
722 
723         string storeStr = tag.getValue!string();
724         const hasChildren = tag.all.tags.length > 0;
725 
726         if (storeStr && hasChildren) {
727             throw new GfxSDLErrorException(
728                 format("Cannot parse %s: both value and children specified", T.stringof),
729                 tag.location
730             );
731         }
732         if (!storeStr && !hasChildren) {
733             // likely value was not a string, or only attributes specified
734             throw new GfxSDLErrorException(
735                 format("Cannot parse %s", T.stringof), tag.location
736             );
737         }
738         T res;
739         if (storeStr) {
740             import std.algorithm : startsWith;
741             if (!storeStr.startsWith("store:")) {
742                 throw new GfxSDLErrorException(
743                     format("Cannot parse %s: " ~
744                         "must specify a store reference or a literal " ~
745                         "(\"%s\" is not a valid store reference)", T.stringof, storeStr),
746                     tag.location
747                 );
748             }
749             const key = storeStr["store:".length .. $];
750             res = _store.expect!T(key);
751         }
752         else {
753             const key = parseF(tag, res);
754             _store.store(key, res);
755         }
756         return enforce(res);
757     }
758 
759     private T getLiteralOrStoreValAttr(T)(Tag tag, in string attrName, T def=T.init)
760     {
761         T res;
762         if (!literalOrStoreValAttr!T(tag, attrName, res)) {
763             res = def;
764         }
765         return res;
766     }
767 
768     private T expectLiteralOrStoreValAttr(T)(Tag tag, in string attrName)
769     {
770         T res;
771         if (!literalOrStoreValAttr!T(tag, attrName, res)) {
772             throw new GfxSDLErrorException(
773                 format("Could not resolveValue %s value from attribute \"%s\"", T.stringof, attrName),
774                 tag.location
775             );
776         }
777         return res;
778     }
779 
780     private bool literalOrStoreValAttr(T)(Tag tag, in string attrName, out T res)
781     {
782         import std.algorithm : startsWith;
783 
784         if (tag.tryGetAttr!T(attrName, res)) {
785             return true;
786         }
787         else {
788             string str;
789             if (tryGetAttr(tag, attrName, str)) {
790                 if (str.startsWith("store:")) {
791                     const key = str["store:".length .. $];
792                     res = _store.expect!T(key);
793                     return true;
794                 }
795             }
796             return false;
797         }
798     }
799 
800     private uint expectSizeOffsetAttr(Tag tag, in string attrName)
801     {
802         auto attr = tag.findAttr(attrName);
803         if (!attr) {
804             throw new GfxSDLErrorException(
805                 "Could not find attribute "~attrName, tag.location
806             );
807         }
808 
809         if (attr.value.convertsTo!int) {
810             return attr.value.get!int();
811         }
812 
813         import std.algorithm : findSplit, startsWith;
814         string s = attr.value.get!string();
815         if (s.startsWith("sizeof:")) {
816             const sf = s["sizeof:".length .. $].findSplit(".");
817             const struct_ = sf[0];
818             const field = sf[2];
819             const sd = findStruct(struct_, attr.location);
820             if (field.length) {
821                 return cast(uint)sd.getSize(field, attr.location);
822             }
823             else {
824                 return cast(uint)sd.size;
825             }
826         }
827         if (s.startsWith("offsetof:")) {
828             const sf = s["offsetof:".length .. $].findSplit(".");
829             const struct_ = sf[0];
830             const field = sf[2];
831             if (!field.length) {
832                 throw new GfxSDLErrorException(
833                     format("\"%s\" cannot resolve to a struct field (required by \"offsetof:\"", sf),
834                     attr.location
835                 );
836             }
837             return cast(uint)findStruct(struct_, attr.location).getOffset(field, attr.location);
838         }
839         throw new GfxSDLErrorException(
840             "Could not find attribute "~attrName, tag.location
841         );
842     }
843 
844 
845     private Format expectFormatAttr(Tag tag, in string attrName="format")
846     {
847         import std.algorithm : findSplit, startsWith;
848 
849         auto formatStr = tag.expectAttribute!string(attrName);
850         if (formatStr.startsWith("formatof:")) {
851             const sf = formatStr["formatof:".length .. $].findSplit(".");
852             const struct_ = sf[0];
853             const field = sf[2];
854             if (!field.length) {
855                 throw new GfxSDLErrorException(
856                     format("\"%s\" cannot resolve to a struct field (required by \"formatof:\"", sf),
857                     tag.location
858                 );
859             }
860             return findStruct(struct_, tag.location).getFormat(field, tag.location);
861         }
862         else {
863             import std.conv : to;
864             return formatStr.to!Format;
865         }
866     }
867 
868     private StructDecl findStruct(in string name, Location location) {
869         foreach (ref sd; _structDecls) {
870             if (sd.name == name) return sd;
871         }
872         throw new NoSuchStructException(name, location);
873     }
874 }
875 
876 private:
877 
878 Attribute findAttr(Tag tag, string attrName)
879 {
880     foreach (attr; tag.attributes)
881         if (attr.name == attrName) return attr;
882     return null;
883 }
884 
885 bool tryGetValue(T)(Tag tag, out T val)
886 {
887     foreach (ref v; tag.values) {
888         if (v.convertsTo!T) {
889             val = v.get!T();
890             return true;
891         }
892     }
893     return false;
894 }
895 
896 bool tryGetValue(T)(Attribute attr, out T val)
897 {
898     if (attr.value.convertsTo!T) {
899         val = attr.value.get!T();
900         return true;
901     }
902     return false;
903 }
904 
905 bool tryGetAttr(T)(Tag tag, in string name, out T val)
906 {
907     foreach (attr; tag.attributes) {
908         if (attr.name == name) {
909             if (attr.value.convertsTo!T) {
910                 val = attr.value.get!T();
911                 return true;
912             }
913         }
914     }
915     return false;
916 }
917 
918 E parseFlags(E)(in string s) if (is(E == enum))
919 {
920     import std.array : split;
921     import std.string : strip;
922     import std.conv : to;
923 
924     const fl = s.split('|');
925     E res = cast(E)0;
926     foreach (f; fl) {
927         res |= f.strip().to!E;
928     }
929     return res;
930 }
931 
932 
933 struct FieldDecl
934 {
935     string name;
936     size_t size;
937     size_t offset;
938     Option!Format format;
939 }
940 
941 struct StructDecl
942 {
943     string name;
944     size_t size;
945     FieldDecl[] fields;
946 
947     static StructDecl makeFor(T)()
948     {
949         import std.traits : FieldNameTuple, Fields, getUDAs;
950 
951         alias F = Fields!T;
952         alias N = FieldNameTuple!T;
953 
954         const offsets = T.init.tupleof.offsetof;
955         StructDecl sd;
956         sd.name = T.stringof;
957         sd.size = T.sizeof;
958         foreach(i, FT; F) {
959             FieldDecl fd;
960             fd.name = N[i];
961             fd.size = FT.sizeof;
962             fd.offset = offsets[i];
963 
964             auto udas = getUDAs!(__traits(getMember, T, N[i]), Format);
965             static if (udas.length) {
966                 fd.format = udas[0];
967             }
968             else {
969                 fd.format = inferFieldFormat!FT();
970             }
971 
972             sd.fields ~= fd;
973         }
974         return sd;
975     }
976 
977     size_t getSize(in string field, in Location location=Location.init) const
978     {
979         foreach(f; fields) {
980             if (f.name == field)
981                 return f.size;
982         }
983         throw new NoSuchFieldException(name, field, location);
984     }
985 
986     size_t getOffset(in string field, in Location location=Location.init) const
987     {
988         foreach(f; fields) {
989             if (f.name == field)
990                 return f.offset;
991         }
992         throw new NoSuchFieldException(name, field, location);
993     }
994 
995     Format getFormat(in string field, in Location location=Location.init) const
996     {
997         foreach(f; fields) {
998             if (f.name == field) {
999                 if (f.format.isSome) {
1000                     return f.format.get;
1001                 }
1002                 else {
1003                     throw new UnknownFieldFormatException(name, field, location);
1004                 }
1005             }
1006         }
1007         throw new NoSuchFieldException(name, field, location);
1008     }
1009 }
1010 
1011 ///
1012 version(unittest)
1013 {
1014     struct SomeVecType
1015     {
1016         private float[3] rep;
1017     }
1018 
1019     struct SomeVertexType
1020     {
1021         SomeVecType pos;
1022 
1023         float[3] normal;
1024 
1025         @(Format.rgba32_sFloat)
1026         float[4] color;
1027 
1028         @(Format.rgba8_uInt)
1029         ubyte[4] otherField;
1030 
1031         ubyte[4] yetAnotherField;
1032 
1033         @(Format.rgba16_sFloat)
1034         float[3] problemField;
1035     }
1036 
1037     unittest {
1038         const sd = StructDecl.makeFor!SomeVertexType();
1039         assert(sd.name == "SomeVertexType");
1040         assert(sd.size == SomeVertexType.sizeof);
1041         assert(sd.fields[0].name == "pos");
1042         assert(sd.fields[1].name == "normal");
1043         assert(sd.fields[2].name == "color");
1044         assert(sd.fields[3].name == "otherField");
1045         assert(sd.fields[4].name == "yetAnotherField");
1046         assert(sd.fields[5].name == "problemField");
1047 
1048         assert(sd.getSize("pos") == SomeVertexType.pos.sizeof);
1049         assert(sd.getSize("normal") == SomeVertexType.normal.sizeof);
1050         assert(sd.getSize("color") == SomeVertexType.color.sizeof);
1051         assert(sd.getSize("otherField") == SomeVertexType.otherField.sizeof);
1052         assert(sd.getSize("yetAnotherField") == SomeVertexType.yetAnotherField.sizeof);
1053         assert(sd.getSize("problemField") == SomeVertexType.problemField.sizeof);
1054 
1055         assert(sd.getOffset("pos") == SomeVertexType.pos.offsetof);
1056         assert(sd.getOffset("normal") == SomeVertexType.normal.offsetof);
1057         assert(sd.getOffset("color") == SomeVertexType.color.offsetof);
1058         assert(sd.getOffset("otherField") == SomeVertexType.otherField.offsetof);
1059         assert(sd.getOffset("yetAnotherField") == SomeVertexType.yetAnotherField.offsetof);
1060         assert(sd.getOffset("problemField") == SomeVertexType.problemField.offsetof);
1061 
1062         assert(sd.getFormat("pos") == Format.rgb32_sFloat);
1063         assert(sd.getFormat("normal") == Format.rgb32_sFloat);
1064         assert(sd.getFormat("color") == Format.rgba32_sFloat);
1065         assert(sd.getFormat("otherField") == Format.rgba8_uInt);
1066         assert(sd.getFormat("problemField") == Format.rgba16_sFloat);
1067 
1068         import std.exception : assertThrown;
1069         assertThrown!NoSuchFieldException(sd.getSize("jfidjf"));
1070         assertThrown!UnknownFieldFormatException(sd.getFormat("yetAnotherField"));
1071     }
1072 }
1073 
1074 Option!Format inferFieldFormat(T)()
1075 {
1076     import gfx.core.typecons : none, some;
1077     import std.traits : RepresentationTypeTuple;
1078 
1079     alias rtt = RepresentationTypeTuple!T;
1080 
1081     static if (is(T == float)) {
1082         return some(Format.r32_sFloat);
1083     }
1084     else static if (is(T V : V[N], int N)) {
1085         static if (is(V == float) && N == 1) {
1086             return some(Format.r32_sFloat);
1087         }
1088         else static if (is(V == float) && N == 2) {
1089             return some(Format.rg32_sFloat);
1090         }
1091         else static if (is(V == float) && N == 3) {
1092             return some(Format.rgb32_sFloat);
1093         }
1094         else static if (is(V == float) && N == 4) {
1095             return some(Format.rgba32_sFloat);
1096         }
1097         else {
1098             return none!Format;
1099         }
1100     }
1101     else static if (rtt.length == 1 && !is(rtt[0] == T)) {
1102         return inferFieldFormat!(rtt[0])();
1103     }
1104     else {
1105         return none!Format;
1106     }
1107 }