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 }