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 gfx.decl.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+1, location.col+1, 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.typecons; 89 import gfx.decl.store : DeclarativeStore; 90 import gfx.graal.cmd : Access, PipelineStage; 91 import gfx.graal.device : Device; 92 import gfx.graal.image : ImageLayout; 93 import gfx.graal.pipeline; 94 import gfx.graal.renderpass; 95 import gfx.graal.types; 96 import std.exception : enforce; 97 import std.format : format; 98 99 private Rc!Device _device; 100 private DeclarativeStore _store; 101 private string[] _assetPaths; 102 private immutable(void)[][string] _views; 103 private StructDecl[] _structDecls; 104 private string[] _localKeys; 105 private int _storeAnonKey; 106 107 108 this(Device device) { 109 _device = device; 110 _store = new DeclarativeStore; 111 } 112 113 override void dispose() { 114 _store.dispose(); 115 _store = null; 116 _device.unload(); 117 } 118 119 @property DeclarativeStore store() { 120 return _store; 121 } 122 123 void addAssetPath(in string path) { 124 _assetPaths ~= path; 125 } 126 127 void addView(string name)() { 128 _views[name] = cast(immutable(void)[])import(name); 129 } 130 131 void declareStruct(T)() { 132 _structDecls ~= StructDecl.makeFor!T(); 133 } 134 135 void parseSDLSource(string sdl, string filename=null) 136 { 137 auto root = parseSource(sdl, filename); 138 parseSDL(root); 139 } 140 141 void parseSDLFile(string filename) 142 { 143 auto root = parseFile(filename); 144 parseSDL(root); 145 } 146 147 void parseSDLView(string name)() 148 { 149 const sdl = cast(string)import(name); 150 auto root = parseSource(sdl, name); 151 parseSDL(root); 152 } 153 154 155 private void parseSDL(Tag root) 156 { 157 PipelineInfo[] plis; 158 string[] plKeys; 159 160 foreach (t; root.namespaces["graal"].tags) { 161 if (t.name == "RenderPass") { 162 RenderPass rp; 163 const key = parseRenderPass(t, rp); 164 _store.store!RenderPass(key, rp); 165 } 166 else if (t.name == "DescriptorSetLayout") { 167 DescriptorSetLayout dsl; 168 const key = parseDescriptorSetLayout(t, dsl); 169 _store.store!DescriptorSetLayout(key, dsl); 170 } 171 else if (t.name == "PipelineLayout") { 172 PipelineLayout pll; 173 const key = parsePipelineLayout(t, pll); 174 _store.store!PipelineLayout(key, pll); 175 } 176 else if (t.name == "ShaderModule") { 177 ShaderModule sm; 178 const key = parseShaderModule(t, sm); 179 _store.store!ShaderModule(key, sm); 180 } 181 else if (t.name == "Pipeline") { 182 PipelineInfo pli = void; 183 plKeys ~= parsePipelineInfo(t, pli); 184 plis ~= pli; 185 } 186 else if (t.name == "PipelineInfo") { 187 PipelineInfo pli = void; 188 const key = parsePipelineInfo(t, pli); 189 _store.store!PipelineInfo(key, pli); 190 } 191 } 192 193 if (plis.length) { 194 auto pls = _device.createPipelines(plis); 195 foreach(i, pl; pls) { 196 _store.store!Pipeline(plKeys[i], pl); 197 } 198 } 199 200 foreach (k; _localKeys) _store.remove(k); 201 } 202 203 private string parseRenderPass(Tag tag, out RenderPass rp) 204 { 205 auto attachments = parseAttachmentDescriptions(tag.getTag("attachments")); 206 auto subpasses = parseSubpassDescriptions(tag); 207 auto dependencies = parseSubpassDependencies(tag); 208 209 rp = _device.createRenderPass(attachments, subpasses, dependencies); 210 211 return getStoreKey(tag); 212 } 213 214 private AttachmentDescription[] parseAttachmentDescriptions(Tag tag) 215 { 216 import std.conv : to; 217 import std.typecons : Flag; 218 if(!tag) return []; 219 220 AttachmentDescription[] res; 221 222 foreach (adt; tag.tags) { 223 AttachmentDescription ad; 224 ad.samples = cast(uint)adt.getAttribute!int("samples", ad.samples); 225 ad.mayAlias = cast(Flag!"mayAlias")adt.getAttribute!bool("mayAlias", cast(bool)ad.mayAlias); 226 foreach (c; adt.tags) { 227 if (c.name == "format") { 228 ad.format = expectLiteralOrStoreValValue!Format(c); 229 } 230 else if (c.name == "layout") { 231 ad.layoutTrans.from = c.expectAttribute!string("from").to!ImageLayout; 232 ad.layoutTrans.to = c.expectAttribute!string("to").to!ImageLayout; 233 } 234 } 235 if (adt.name == "color" || adt.name == "depth") { 236 ad.ops = parseAttachmentOps(adt.expectTag("ops")); 237 } 238 else if (adt.name == "stencil") { 239 ad.stencilOps = parseAttachmentOps(adt.expectTag("ops")); 240 } 241 else if (adt.name == "depthStencil") { 242 ad.ops = parseAttachmentOps(adt.expectTag("depthOps")); 243 ad.stencilOps = parseAttachmentOps(adt.expectTag("stencilOps")); 244 } 245 else { 246 throw new GfxSDLErrorException("unexpected attachment type: "~adt.name, adt.location); 247 } 248 res ~= ad; 249 } 250 return res; 251 } 252 253 AttachmentOps parseAttachmentOps(Tag tag) 254 { 255 import std.conv : to; 256 AttachmentOps ops; 257 ops.load = tag.expectAttribute!string("load").to!LoadOp; 258 ops.store = tag.expectAttribute!string("store").to!StoreOp; 259 return ops; 260 } 261 262 SubpassDescription[] parseSubpassDescriptions(Tag tag) 263 { 264 import std.algorithm : filter; 265 266 SubpassDescription[] res; 267 foreach(sdt; tag.tags.filter!(t => t.name == "subpass")) { 268 SubpassDescription sd; 269 foreach (t; sdt.tags) { 270 if (t.name == "input") { 271 sd.inputs ~= parseAttachmentRef(t); 272 } 273 else if (t.name == "color") { 274 sd.colors ~= parseAttachmentRef(t); 275 } 276 else if (t.name == "depthStencil") { 277 sd.depthStencil = parseAttachmentRef(t); 278 } 279 else if (t.name == "preserves") { 280 import std.algorithm : map; 281 import std.array : array; 282 sd.preserves = t.values.map!(v => cast(uint)v.get!int()).array(); 283 } 284 } 285 res ~= sd; 286 } 287 return res; 288 } 289 290 AttachmentRef parseAttachmentRef(Tag tag) 291 { 292 import std.conv : to; 293 AttachmentRef res=void; 294 res.attachment = cast(uint)tag.expectAttribute!int("attachment"); 295 res.layout = tag.expectAttribute!string("layout").to!ImageLayout; 296 return res; 297 } 298 299 SubpassDependency[] parseSubpassDependencies(Tag tag) 300 { 301 import std.algorithm : filter; 302 import std.conv : to; 303 304 SubpassDependency[] res; 305 foreach(sdt; tag.tags.filter!(t => t.name == "dependency")) { 306 SubpassDependency sd=void; 307 auto t = sdt.expectTag("subpass"); 308 sd.subpass.from = parseSubpassRef(t, "from"); 309 sd.subpass.to = parseSubpassRef(t, "to"); 310 t = sdt.expectTag("stageMask"); 311 sd.stageMask.from = t.expectAttribute!string("from").parseFlags!PipelineStage(); 312 sd.stageMask.to = t.expectAttribute!string("to").parseFlags!PipelineStage(); 313 t = sdt.expectTag("accessMask"); 314 sd.accessMask.from = t.expectAttribute!string("from").parseFlags!Access(); 315 sd.accessMask.to = t.expectAttribute!string("to").parseFlags!Access(); 316 res ~= sd; 317 } 318 return res; 319 } 320 321 uint parseSubpassRef(Tag tag, string attrName) 322 { 323 import std.conv : to; 324 int val; 325 string str; 326 if (tryGetAttr!int(tag, attrName, val)) { 327 return cast(uint)val; 328 } 329 if (tryGetAttr!string(tag, attrName, str)) { 330 if (str == "external") { 331 return subpassExternal; 332 } 333 } 334 throw new GfxSDLErrorException( 335 "could not resolve subpass", tag.location 336 ); 337 } 338 339 private string parseDescriptorSetLayout(Tag tag, out DescriptorSetLayout dsl) 340 { 341 import std.conv : to; 342 343 PipelineLayoutBinding[] bindings; 344 auto btags = tag.expectTag("bindings"); 345 foreach (bt; btags.all.tags) { 346 PipelineLayoutBinding binding; 347 binding.binding = bt.expectValue!int(); 348 binding.descriptorType = bt.expectAttribute!string("descriptorType").to!DescriptorType; 349 binding.descriptorCount = bt.expectAttribute!int("descriptorCount"); 350 binding.stages = bt.expectAttribute!string("stages").parseFlags!ShaderStage(); 351 bindings ~= binding; 352 } 353 354 dsl = _device.createDescriptorSetLayout(bindings); 355 return getStoreKey(tag); 356 } 357 358 private string parsePipelineLayout(Tag tag, out PipelineLayout pll) 359 { 360 DescriptorSetLayout[] layouts; 361 auto layoutsTag = tag.getTag("layouts"); 362 if (layoutsTag) { 363 foreach (lt; layoutsTag.all.tags) { 364 layouts ~= parseStoreRefOrLiteral!(DescriptorSetLayout, parseDescriptorSetLayout)(lt); 365 } 366 } 367 368 PushConstantRange[] ranges; 369 auto rangesTag = tag.getTag("ranges"); 370 if (rangesTag) { 371 foreach (rt; rangesTag.all.tags) { 372 PushConstantRange pcr; 373 pcr.stages = rt.expectAttribute!string("stages").parseFlags!ShaderStage(); 374 pcr.size = expectSizeOffsetAttr(rt, "size"); 375 pcr.offset = expectSizeOffsetAttr(rt, "offset"); 376 ranges ~= pcr; 377 } 378 } 379 380 pll = _device.createPipelineLayout(layouts, ranges); 381 return getStoreKey(tag); 382 } 383 384 private string parseShaderModule(Tag tag, out ShaderModule mod) 385 { 386 immutable(uint)[] src; 387 auto st = tag.expectTag("source"); 388 string srcStr; 389 ubyte[] srcData; 390 if (st.tryGetValue!string(srcStr)) { 391 src = getShaderSrcFromString(srcStr, st.location); 392 } 393 else if (st.tryGetValue!(ubyte[])(srcData)) { 394 import std.exception : assumeUnique; 395 src = assumeUnique(cast(uint[])srcData); 396 } 397 else { 398 throw new GfxSDLErrorException( 399 "ill-formed source tag in graal:ShaderModule", tag.location 400 ); 401 } 402 403 const ep = tag.getTagValue!string("entryPoint", "main"); 404 405 mod = _device.createShaderModule(src, ep); 406 return getStoreKey(tag); 407 } 408 409 private string parseShaderModuleAttr(Tag tag, out ShaderModule mod) 410 { 411 immutable(uint)[] src; 412 auto srcAttr = enforce(findAttr(tag, "source"), new GfxSDLErrorException( 413 "source attribute is mandatory in graal:ShaderModule", tag.location 414 )); 415 string srcStr; 416 ubyte[] srcData; 417 if (srcAttr.tryGetValue!string(srcStr)) { 418 src = getShaderSrcFromString(srcStr, srcAttr.location); 419 } 420 else if (srcAttr.tryGetValue!(ubyte[])(srcData)) { 421 import std.exception : assumeUnique; 422 src = assumeUnique(cast(uint[])srcData); 423 } 424 else { 425 throw new GfxSDLErrorException( 426 "ill-formed source attribute in graal:ShaderModule", tag.location 427 ); 428 } 429 430 const ep = tag.getAttribute!string("entryPoint", "main"); 431 432 mod = _device.createShaderModule(src, ep); 433 return getStoreKeyAttr(tag); 434 } 435 436 immutable(uint)[] getShaderSrcFromString(in string srcStr, in Location location) 437 { 438 import std.algorithm : startsWith; 439 440 if (srcStr.startsWith("view:")) { 441 const name = srcStr["view:".length .. $]; 442 return cast(immutable(uint)[])getView(name, location); 443 } 444 else if (srcStr.startsWith("asset:")) { 445 const name = srcStr["asset:".length .. $]; 446 return cast(immutable(uint)[])getAsset(name, location); 447 } 448 else { 449 throw new GfxSDLErrorException( 450 format("\"%s\" could not lead to a shader source file", srcStr), 451 location 452 ); 453 } 454 } 455 456 private string parsePipelineInfo(Tag tag, out PipelineInfo plInfo) 457 { 458 foreach (t; tag.expectTag("shaders").tags) { 459 ShaderModule sm; 460 if (t.attributes.length) { 461 // inline shader 462 const key = parseShaderModuleAttr(t, sm); 463 _store.store!ShaderModule(key, sm); 464 } 465 else { 466 // literal or alreadystored 467 sm = parseStoreRefOrLiteral!(ShaderModule, parseShaderModule)(t); 468 } 469 switch (t.name) { 470 case "vertex": plInfo.shaders.vertex = sm; break; 471 case "tessControl": plInfo.shaders.tessControl = sm; break; 472 case "tessEval": plInfo.shaders.tessEval = sm; break; 473 case "geometry": plInfo.shaders.geometry = sm; break; 474 case "fragment": plInfo.shaders.fragment = sm; break; 475 default: 476 throw new GfxSDLErrorException( 477 "Unknown shader stage: " ~ t.name, t.location 478 ); 479 } 480 } 481 482 foreach(t; tag.expectTag("inputBindings").tags) { 483 VertexInputBinding vib; 484 vib.binding = t.expectValue!int(); 485 vib.stride = expectSizeOffsetAttr(t, "stride"); 486 import std.typecons : Flag; 487 vib.instanced = cast(Flag!"instanced")t.getAttribute!bool("instanced", false); 488 plInfo.inputBindings ~= vib; 489 } 490 491 foreach(t; tag.expectTag("inputAttribs").tags) { 492 VertexInputAttrib via; 493 via.location = t.expectValue!int(); 494 via.binding = t.expectAttribute!int("binding"); 495 auto attr = t.findAttr("member"); 496 if (attr) { 497 import std.algorithm : findSplit; 498 const ms = attr.value.get!string(); 499 const sf = ms.findSplit("."); 500 if (!sf[2].length) { 501 throw new GfxSDLErrorException(format( 502 "could not resolve \"%s\" to a struct field", ms 503 ), attr.location); 504 } 505 const s = findStruct(sf[0], attr.location); 506 via.format = s.getFormat(sf[2], attr.location); 507 via.offset = s.getOffset(sf[2], attr.location); 508 } 509 else { 510 via.format = expectFormatAttr(t, "format"); 511 via.offset = expectSizeOffsetAttr(t, "offset"); 512 } 513 plInfo.inputAttribs ~= via; 514 } 515 516 plInfo.assembly = parseInputAssembly(tag.expectTag("assembly")); 517 plInfo.rasterizer = parseRasterizer(tag.expectTag("rasterizer")); 518 plInfo.viewports = parseViewportConfigs(tag); 519 plInfo.depthInfo = parseDepthInfo(tag.getTag("depthInfo")); 520 plInfo.stencilInfo = parseStencilInfo(tag.getTag("stencilInfo")); 521 plInfo.blendInfo = parseColorBlendInfo(tag.expectTag("blendInfo")); 522 auto dynTag = tag.getTag("dynamicStates"); 523 if (dynTag) { 524 import std.algorithm : map; 525 import std.array : array; 526 import std.conv : to; 527 plInfo.dynamicStates = dynTag.values.map!(v => v.get!string.to!DynamicState).array; 528 } 529 plInfo.layout = parseStoreRefOrLiteral!(PipelineLayout, parsePipelineLayout)(tag.expectTag("layout")); 530 auto rpTag = tag.expectTag("renderPass"); 531 plInfo.renderPass = expectStoreRef!RenderPass(rpTag); 532 plInfo.subpassIndex = rpTag.expectAttribute!int("subpass"); 533 534 return getStoreKey(tag); 535 } 536 537 private InputAssembly parseInputAssembly(Tag tag) 538 { 539 import std.conv : to; 540 import std.typecons : Flag; 541 542 InputAssembly assembly; 543 assembly.primitive = tag.expectAttribute!string("primitive").to!Primitive; 544 assembly.primitiveRestart = 545 cast(Flag!"primitiveRestart")tag.getAttribute!bool("primitiveRestart", false); 546 return assembly; 547 } 548 549 private Rasterizer parseRasterizer(Tag tag) 550 { 551 import std.conv : to; 552 import std.typecons : Flag; 553 import gfx.core.typecons : some; 554 555 Rasterizer res; 556 res.mode = tag.expectAttribute!string("polygonMode").to!PolygonMode; 557 res.cull = tag.getAttribute!string("cull", "none").parseFlags!Cull; 558 res.front = tag.getAttribute!string("front", "ccw").to!FrontFace; 559 res.depthClamp = cast(Flag!"depthClamp")tag.getAttribute!bool("depthClamp", false); 560 res.lineWidth = tag.getAttribute!float("lineWidth", 1f); 561 auto dbt = tag.getTag("depthBias"); 562 if (dbt) { 563 DepthBias db; 564 db.slopeFactor = dbt.expectAttribute!float("slope"); 565 db.constantFactor = dbt.expectAttribute!float("const"); 566 db.clamp = dbt.getAttribute!float("clamp", 0f); 567 res.depthBias = some(db); 568 } 569 return res; 570 } 571 572 private ViewportConfig[] parseViewportConfigs(Tag plTag) 573 { 574 Viewport[] vps; 575 Rect[] scs; 576 Location loc; 577 auto vpt = plTag.getTag("viewports"); 578 if (vpt) { 579 foreach(t; plTag.tags) { 580 if (t.name == "viewport") { 581 vps ~= parseViewport(t); 582 } 583 else if (t.name == "scissors") { 584 scs ~= parseRect(t); 585 } 586 } 587 loc = vpt.location; 588 } 589 else { 590 vpt = plTag.getTag("viewport"); 591 if (vpt) { 592 vps ~= parseViewport(vpt); 593 loc = vpt.location; 594 } 595 } 596 597 if (!vps.length && !scs.length) return null; 598 if (scs.length && scs.length != vps.length) { 599 throw new GfxSDLErrorException("must state the same number of viewport and scissors", loc); 600 } 601 602 ViewportConfig[] configs; 603 if (vps.length == scs.length) { 604 foreach (i; 0 .. vps.length) { 605 configs ~= ViewportConfig(vps[i], scs[i]); 606 } 607 } 608 else { 609 assert(vps.length && !scs.length); 610 foreach (vp; vps) { 611 const sc = Rect(cast(uint)vp.x, cast(uint)vp.y, cast(uint)vp.width, cast(uint)vp.height); 612 configs ~= ViewportConfig(vp, sc); 613 } 614 } 615 return configs; 616 } 617 618 619 private Viewport parseViewport(Tag tag) 620 { 621 Viewport vp = void; 622 vp.x = expectLiteralOrStoreValAttr!float(tag, "x"); 623 vp.y = expectLiteralOrStoreValAttr!float(tag, "y"); 624 vp.width = expectLiteralOrStoreValAttr!float(tag, "width"); 625 vp.height = expectLiteralOrStoreValAttr!float(tag, "height"); 626 vp.minDepth = getLiteralOrStoreValAttr!float(tag, "minDepth", 0f); 627 vp.minDepth = getLiteralOrStoreValAttr!float(tag, "maxDepth", 1f); 628 return vp; 629 } 630 631 private Rect parseRect(Tag tag) 632 { 633 Rect r = void; 634 r.x = expectLiteralOrStoreValAttr!int(tag, "x"); 635 r.y = expectLiteralOrStoreValAttr!int(tag, "y"); 636 r.width = expectLiteralOrStoreValAttr!int(tag, "width"); 637 r.height = expectLiteralOrStoreValAttr!int(tag, "height"); 638 return r; 639 } 640 641 private DepthInfo parseDepthInfo(Tag tag) 642 { 643 import std.conv : to; 644 import std.typecons : Flag; 645 646 DepthInfo res; 647 // no tag = disabled = init 648 if (tag) { 649 // can optionally specify on/off as value (defaults to on) 650 res.enabled = cast(Flag!"enabled")tag.getValue!bool(true); 651 if (res.enabled) { 652 res.write = cast(Flag!"write")tag.expectAttribute!bool("write"); 653 res.compareOp = tag.expectAttribute!string("compareOp").to!CompareOp; 654 auto bt = tag.getTag("boundsTest"); 655 if (bt) { 656 // same as depth test, on/off is optional and defaults to on 657 res.boundsTest = cast(Flag!"boundsTest")tag.getValue!bool(true); 658 if (res.boundsTest) { 659 res.minBounds = expectLiteralOrStoreValAttr!float(bt, "min"); 660 res.maxBounds = expectLiteralOrStoreValAttr!float(bt, "max"); 661 } 662 } 663 } 664 } 665 return res; 666 } 667 668 StencilInfo parseStencilInfo(Tag tag) 669 { 670 import std.typecons : Flag; 671 672 StencilInfo res; 673 if (tag) { 674 res.enabled = cast(Flag!"enabled")tag.getValue!bool(true); 675 res.front = parseStencilOpState(tag.getTag("front")); 676 res.back = parseStencilOpState(tag.getTag("back")); 677 } 678 return res; 679 } 680 681 StencilOpState parseStencilOpState(Tag tag) 682 { 683 import std.conv : to; 684 685 if (!tag) return StencilOpState.init; 686 687 StencilOpState res = void; 688 res.compareOp = tag.expectAttribute!string("compareOp").to!CompareOp; 689 string ops; 690 if (tag.tryGetAttr!string("op", ops)) { 691 const op = ops.to!StencilOp; 692 res.failOp = op; res.passOp = op; res.depthFailOp = op; 693 } 694 else { 695 res.failOp = tag.expectAttribute!string("failOp").to!StencilOp; 696 res.passOp = tag.expectAttribute!string("passOp").to!StencilOp; 697 res.depthFailOp = tag.expectAttribute!string("depthFailOp").to!StencilOp; 698 } 699 int mask; 700 if (tag.tryGetAttr!int("mask", mask)) { 701 res.compareMask = mask; res.writeMask = mask; res.refMask = mask; 702 } 703 else { 704 res.compareMask = tag.expectAttribute!int("compareMask"); 705 res.writeMask = tag.expectAttribute!int("writeMask"); 706 res.refMask = tag.expectAttribute!int("refMask"); 707 } 708 return res; 709 } 710 711 private ColorBlendInfo parseColorBlendInfo(Tag tag) 712 { 713 import gfx.core.typecons : some; 714 import std.conv : to; 715 716 ColorBlendInfo res; 717 718 bool lopEnabled; 719 string lopStr; 720 if (tag.tryGetAttr!bool("logicOp", lopEnabled)) { 721 if (lopEnabled) { 722 throw new GfxSDLErrorException("logicOp can only be set to off or to LogicOp value", tag.location); 723 } 724 } 725 else if (tag.tryGetAttr!string("logicOp", lopStr)) { 726 res.logicOp = some(lopStr.to!LogicOp); 727 } 728 729 auto attTag = tag.getTag("attachments"); 730 if (attTag) foreach(t; attTag.tags) { 731 ColorBlendAttachment attachment; 732 if (t.name == "blend") { 733 import std.typecons : Yes; 734 attachment.enabled = Yes.enabled; 735 auto stateT = t.getTag("state"); 736 if (stateT) { 737 const state = parseBlendState(stateT); 738 attachment.colorBlend = state; 739 attachment.alphaBlend = state; 740 } 741 else { 742 attachment.colorBlend = parseBlendState(t.expectTag("color")); 743 attachment.alphaBlend = parseBlendState(t.expectTag("alpha")); 744 } 745 } 746 else if (t.name != "solid") { 747 throw new GfxSDLErrorException(format( 748 "tag \"%s\" is not allowed in color attachments", t.name 749 ), t.location); 750 } 751 string maskStr; 752 if (t.tryGetAttr!string("colorMask", maskStr)) { 753 attachment.colorMask = maskStr.to!ColorMask; 754 } 755 else { 756 attachment.colorMask = ColorMask.all; 757 } 758 res.attachments ~= attachment; 759 } 760 761 auto constTag = tag.getTag("blendConstants"); 762 if (constTag) { 763 import std.algorithm : map; 764 import std.array : array; 765 766 const bc = constTag.values.map!(v => v.coerce!float).array; 767 if (bc.length != 4) { 768 throw new GfxSDLErrorException("blendConstants must have 4 floats", constTag.location); 769 } 770 res.blendConstants = bc; 771 } 772 773 return res; 774 } 775 776 private BlendState parseBlendState(Tag tag) 777 { 778 import std.conv : to; 779 780 BlendState state; 781 state.factor.from = tag.expectAttribute!string("srcFactor").to!BlendFactor; 782 state.factor.to = tag.expectAttribute!string("dstFactor").to!BlendFactor; 783 state.op = tag.expectAttribute!string("op").to!BlendOp; 784 return state; 785 } 786 787 private string getStoreKey(Tag tag) 788 { 789 auto t = tag.getTag("store"); 790 if (t) return t.expectValue!string(); 791 t = tag.getTag("local-store"); 792 if (t) { 793 const key = t.expectValue!string(); 794 _localKeys ~= key; 795 return key; 796 } 797 else { 798 const key = newAnonKey(); 799 _localKeys ~= key; 800 return key; 801 } 802 } 803 804 private string getStoreKeyAttr(Tag tag) 805 { 806 foreach (attr; tag.attributes) { 807 if (attr.name == "store") { 808 return attr.value.get!string(); 809 } 810 if (attr.name == "local-store") { 811 const key = attr.value.get!string(); 812 _localKeys ~= key; 813 return key; 814 } 815 } 816 { 817 const key = newAnonKey(); 818 _localKeys ~= key; 819 return key; 820 } 821 } 822 823 private string newAnonKey() 824 { 825 import std.conv : to; 826 const key = ++_storeAnonKey; 827 return key.to!string; 828 } 829 830 private immutable(void)[] getAsset(in string name, in Location location) 831 { 832 import std.algorithm : map; 833 import std.file : exists, read; 834 import std.path : buildPath; 835 import std.exception : assumeUnique; 836 837 foreach (p; _assetPaths.map!(ap => buildPath(ap, name))) { 838 if (exists(p)) { 839 return assumeUnique(read(p)); 840 } 841 } 842 throw new NoSuchAssetException(name, location); 843 } 844 845 private immutable(void)[] getView(in string name, in Location location) 846 { 847 auto p = name in _views; 848 if (!p) { 849 throw new NoSuchViewException(name, location); 850 } 851 return *p; 852 } 853 854 private T expectStoreRef(T)(Tag tag) 855 { 856 import std.algorithm : startsWith; 857 858 string storeStr = tag.expectValue!string(); 859 if (!storeStr.startsWith("store:")) { 860 throw new GfxSDLErrorException( 861 format("Cannot parse %s: " ~ 862 "must specify a store reference or a literal " ~ 863 "(\"%s\" is not a valid store reference)", T.stringof, storeStr), 864 tag.location 865 ); 866 } 867 const key = storeStr["store:".length .. $]; 868 return _store.expect!T(key); 869 } 870 871 private T parseStoreRefOrLiteral(T, alias parseF)(Tag tag) 872 { 873 import std.format : format; 874 875 string storeStr = tag.getValue!string(); 876 const hasChildren = tag.all.tags.length > 0; 877 878 if (storeStr && hasChildren) { 879 throw new GfxSDLErrorException( 880 format("Cannot parse %s: both value and children specified", T.stringof), 881 tag.location 882 ); 883 } 884 if (!storeStr && !hasChildren) { 885 // likely value was not a string, or only attributes specified 886 throw new GfxSDLErrorException( 887 format("Cannot parse %s", T.stringof), tag.location 888 ); 889 } 890 T res; 891 if (storeStr) { 892 import std.algorithm : startsWith; 893 if (!storeStr.startsWith("store:")) { 894 throw new GfxSDLErrorException( 895 format("Cannot parse %s: " ~ 896 "must specify a store reference or a literal " ~ 897 "(\"%s\" is not a valid store reference)", T.stringof, storeStr), 898 tag.location 899 ); 900 } 901 const key = storeStr["store:".length .. $]; 902 res = _store.expect!T(key); 903 } 904 else { 905 const key = parseF(tag, res); 906 _store.store(key, res); 907 } 908 return enforce(res); 909 } 910 911 private T getLiteralOrStoreValValue(T)(Tag tag, T def=T.init) 912 { 913 T res; 914 if (!literalOrStoreValValue!T(tag, res)) { 915 res = def; 916 } 917 return res; 918 } 919 920 private T expectLiteralOrStoreValValue(T)(Tag tag) 921 { 922 T res; 923 if (!literalOrStoreValValue!T(tag, res)) { 924 throw new GfxSDLErrorException( 925 format("Could not resolve %s value", T.stringof), 926 tag.location 927 ); 928 } 929 return res; 930 } 931 932 private bool literalOrStoreValValue(T)(Tag tag, out T res) 933 { 934 import std.algorithm : startsWith; 935 936 static if (!is(T == enum)) { 937 if (tryGetValue(tag, res)) { 938 return true; 939 } 940 } 941 942 string str; 943 if (tryGetValue(tag, str)) { 944 if (str.startsWith("store:")) { 945 const key = str["store:".length .. $]; 946 res = _store.expect!T(key); 947 return true; 948 } 949 static if (is(T == enum)) { 950 import std.conv : to; 951 res = str.to!T; 952 return true; 953 } 954 } 955 return false; 956 } 957 958 private T getLiteralOrStoreValAttr(T)(Tag tag, in string attrName, T def=T.init) 959 { 960 T res; 961 if (!literalOrStoreValAttr!T(tag, attrName, res)) { 962 res = def; 963 } 964 return res; 965 } 966 967 private T expectLiteralOrStoreValAttr(T)(Tag tag, in string attrName) 968 { 969 T res; 970 if (!literalOrStoreValAttr!T(tag, attrName, res)) { 971 throw new GfxSDLErrorException( 972 format("Could not resolveValue %s value from attribute \"%s\"", T.stringof, attrName), 973 tag.location 974 ); 975 } 976 return res; 977 } 978 979 private bool literalOrStoreValAttr(T)(Tag tag, in string attrName, out T res) 980 { 981 import std.algorithm : startsWith; 982 983 static if (!is(T == enum)) { 984 if (tag.tryGetAttr!T(attrName, res)) { 985 return true; 986 } 987 } 988 989 string str; 990 if (tryGetAttr(tag, attrName, str)) { 991 if (str.startsWith("store:")) { 992 const key = str["store:".length .. $]; 993 res = _store.expect!T(key); 994 return true; 995 } 996 static if (is(T == enum)) { 997 import std.conv : to; 998 return str.to!T; 999 } 1000 } 1001 return false; 1002 } 1003 1004 private T getLiteralOrStoreValTagValue(T)(Tag tag, in string tagName, T def=T.init) 1005 { 1006 T res; 1007 if (!literalOrStoreValTagValue!T(tag, tagName, res)) { 1008 res = def; 1009 } 1010 return res; 1011 } 1012 1013 private T expectLiteralOrStoreValTagValue(T)(Tag tag, in string tagName) 1014 { 1015 T res; 1016 if (!literalOrStoreValTagValue!T(tag, tagName, res)) { 1017 throw new GfxSDLErrorException( 1018 format("Could not resolveValue %s value from tag \"%s\"", T.stringof, tagName), 1019 tag.location 1020 ); 1021 } 1022 return res; 1023 } 1024 1025 1026 private bool literalOrStoreValTagValue(T)(Tag tag, in string tagName, out T res) 1027 { 1028 import std.algorithm : startsWith; 1029 1030 static if (!is(T == enum)) { 1031 if (tag.tryGetTagValue!T(tagName, res)) { 1032 return true; 1033 } 1034 } 1035 1036 string str; 1037 if (tryGetTagValue(tag, tagName, str)) { 1038 if (str.startsWith("store:")) { 1039 const key = str["store:".length .. $]; 1040 res = _store.expect!T(key); 1041 return true; 1042 } 1043 static if (is(T == enum)) { 1044 import std.conv : to; 1045 res = str.to!T; 1046 return true; 1047 } 1048 } 1049 return false; 1050 } 1051 1052 private uint expectSizeOffsetAttr(Tag tag, in string attrName) 1053 { 1054 auto attr = tag.findAttr(attrName); 1055 if (!attr) { 1056 throw new GfxSDLErrorException( 1057 "Could not find attribute "~attrName, tag.location 1058 ); 1059 } 1060 1061 if (attr.value.convertsTo!int) { 1062 return attr.value.get!int(); 1063 } 1064 1065 import std.algorithm : findSplit, startsWith; 1066 string s = attr.value.get!string(); 1067 if (s.startsWith("sizeof:")) { 1068 const sf = s["sizeof:".length .. $].findSplit("."); 1069 const struct_ = sf[0]; 1070 const field = sf[2]; 1071 const sd = findStruct(struct_, attr.location); 1072 if (field.length) { 1073 return cast(uint)sd.getSize(field, attr.location); 1074 } 1075 else { 1076 return cast(uint)sd.size; 1077 } 1078 } 1079 if (s.startsWith("offsetof:")) { 1080 const sf = s["offsetof:".length .. $].findSplit("."); 1081 const struct_ = sf[0]; 1082 const field = sf[2]; 1083 if (!field.length) { 1084 throw new GfxSDLErrorException( 1085 format("\"%s\" cannot resolve to a struct field (required by \"offsetof:\"", sf), 1086 attr.location 1087 ); 1088 } 1089 return cast(uint)findStruct(struct_, attr.location).getOffset(field, attr.location); 1090 } 1091 throw new GfxSDLErrorException( 1092 "Could not find attribute "~attrName, tag.location 1093 ); 1094 } 1095 1096 1097 private Format expectFormatAttr(Tag tag, in string attrName="format") 1098 { 1099 import std.algorithm : findSplit, startsWith; 1100 1101 auto formatStr = tag.expectAttribute!string(attrName); 1102 if (formatStr.startsWith("formatof:")) { 1103 const sf = formatStr["formatof:".length .. $].findSplit("."); 1104 const struct_ = sf[0]; 1105 const field = sf[2]; 1106 if (!field.length) { 1107 throw new GfxSDLErrorException( 1108 format("\"%s\" cannot resolve to a struct field (required by \"formatof:\"", sf), 1109 tag.location 1110 ); 1111 } 1112 return findStruct(struct_, tag.location).getFormat(field, tag.location); 1113 } 1114 else { 1115 import std.conv : to; 1116 return formatStr.to!Format; 1117 } 1118 } 1119 1120 private StructDecl findStruct(in string name, Location location) { 1121 foreach (ref sd; _structDecls) { 1122 if (sd.name == name) return sd; 1123 } 1124 throw new NoSuchStructException(name, location); 1125 } 1126 } 1127 1128 private: 1129 1130 Attribute findAttr(Tag tag, string attrName) 1131 { 1132 foreach (attr; tag.attributes) 1133 if (attr.name == attrName) return attr; 1134 return null; 1135 } 1136 1137 bool tryGetValue(T)(Tag tag, out T val) 1138 { 1139 foreach (ref v; tag.values) { 1140 if (v.convertsTo!T) { 1141 val = v.get!T(); 1142 return true; 1143 } 1144 } 1145 return false; 1146 } 1147 1148 bool tryGetValue(T)(Attribute attr, out T val) 1149 { 1150 if (attr.value.convertsTo!T) { 1151 val = attr.value.get!T(); 1152 return true; 1153 } 1154 return false; 1155 } 1156 1157 bool tryGetAttr(T)(Tag tag, in string name, out T val) 1158 { 1159 foreach (attr; tag.attributes) { 1160 if (attr.name == name) { 1161 if (attr.value.convertsTo!T) { 1162 val = attr.value.get!T(); 1163 return true; 1164 } 1165 } 1166 } 1167 return false; 1168 } 1169 1170 bool tryGetTagValue(T)(Tag tag, in string name, out T val) 1171 { 1172 foreach (t; tag.tags) { 1173 if (t.name == name) { 1174 foreach (v; t.values) { 1175 if (v.convertsTo!T) { 1176 val = v.get!T(); 1177 return true; 1178 } 1179 } 1180 } 1181 } 1182 return false; 1183 } 1184 1185 E parseFlags(E)(in string s) if (is(E == enum)) 1186 { 1187 import std.array : split; 1188 import std.string : strip; 1189 import std.conv : to; 1190 1191 const fl = s.split('|'); 1192 E res = cast(E)0; 1193 foreach (f; fl) { 1194 res |= f.strip().to!E; 1195 } 1196 return res; 1197 } 1198 1199 1200 struct FieldDecl 1201 { 1202 string name; 1203 size_t size; 1204 size_t offset; 1205 Option!Format format; 1206 } 1207 1208 struct StructDecl 1209 { 1210 string name; 1211 size_t size; 1212 FieldDecl[] fields; 1213 1214 static StructDecl makeFor(T)() 1215 { 1216 import std.traits : FieldNameTuple, Fields, getUDAs; 1217 1218 alias F = Fields!T; 1219 alias N = FieldNameTuple!T; 1220 1221 const offsets = T.init.tupleof.offsetof; 1222 StructDecl sd; 1223 sd.name = T.stringof; 1224 sd.size = T.sizeof; 1225 foreach(i, FT; F) { 1226 FieldDecl fd; 1227 fd.name = N[i]; 1228 fd.size = FT.sizeof; 1229 fd.offset = offsets[i]; 1230 1231 auto udas = getUDAs!(__traits(getMember, T, N[i]), Format); 1232 static if (udas.length) { 1233 fd.format = udas[0]; 1234 } 1235 else { 1236 fd.format = inferFieldFormat!FT(); 1237 } 1238 1239 sd.fields ~= fd; 1240 } 1241 return sd; 1242 } 1243 1244 size_t getSize(in string field, in Location location=Location.init) const 1245 { 1246 foreach(f; fields) { 1247 if (f.name == field) 1248 return f.size; 1249 } 1250 throw new NoSuchFieldException(name, field, location); 1251 } 1252 1253 size_t getOffset(in string field, in Location location=Location.init) const 1254 { 1255 foreach(f; fields) { 1256 if (f.name == field) 1257 return f.offset; 1258 } 1259 throw new NoSuchFieldException(name, field, location); 1260 } 1261 1262 Format getFormat(in string field, in Location location=Location.init) const 1263 { 1264 foreach(f; fields) { 1265 if (f.name == field) { 1266 if (f.format.isSome) { 1267 return f.format.get; 1268 } 1269 else { 1270 throw new UnknownFieldFormatException(name, field, location); 1271 } 1272 } 1273 } 1274 throw new NoSuchFieldException(name, field, location); 1275 } 1276 } 1277 1278 /// 1279 version(unittest) 1280 { 1281 struct SomeVecType 1282 { 1283 private float[3] rep; 1284 } 1285 1286 struct SomeVertexType 1287 { 1288 SomeVecType pos; 1289 1290 float[3] normal; 1291 1292 @(Format.rgba32_sFloat) 1293 float[4] color; 1294 1295 @(Format.rgba8_uInt) 1296 ubyte[4] otherField; 1297 1298 ubyte[4] yetAnotherField; 1299 1300 @(Format.rgba16_sFloat) 1301 float[3] problemField; 1302 } 1303 1304 unittest { 1305 const sd = StructDecl.makeFor!SomeVertexType(); 1306 assert(sd.name == "SomeVertexType"); 1307 assert(sd.size == SomeVertexType.sizeof); 1308 assert(sd.fields[0].name == "pos"); 1309 assert(sd.fields[1].name == "normal"); 1310 assert(sd.fields[2].name == "color"); 1311 assert(sd.fields[3].name == "otherField"); 1312 assert(sd.fields[4].name == "yetAnotherField"); 1313 assert(sd.fields[5].name == "problemField"); 1314 1315 assert(sd.getSize("pos") == SomeVertexType.pos.sizeof); 1316 assert(sd.getSize("normal") == SomeVertexType.normal.sizeof); 1317 assert(sd.getSize("color") == SomeVertexType.color.sizeof); 1318 assert(sd.getSize("otherField") == SomeVertexType.otherField.sizeof); 1319 assert(sd.getSize("yetAnotherField") == SomeVertexType.yetAnotherField.sizeof); 1320 assert(sd.getSize("problemField") == SomeVertexType.problemField.sizeof); 1321 1322 assert(sd.getOffset("pos") == SomeVertexType.pos.offsetof); 1323 assert(sd.getOffset("normal") == SomeVertexType.normal.offsetof); 1324 assert(sd.getOffset("color") == SomeVertexType.color.offsetof); 1325 assert(sd.getOffset("otherField") == SomeVertexType.otherField.offsetof); 1326 assert(sd.getOffset("yetAnotherField") == SomeVertexType.yetAnotherField.offsetof); 1327 assert(sd.getOffset("problemField") == SomeVertexType.problemField.offsetof); 1328 1329 assert(sd.getFormat("pos") == Format.rgb32_sFloat); 1330 assert(sd.getFormat("normal") == Format.rgb32_sFloat); 1331 assert(sd.getFormat("color") == Format.rgba32_sFloat); 1332 assert(sd.getFormat("otherField") == Format.rgba8_uInt); 1333 assert(sd.getFormat("problemField") == Format.rgba16_sFloat); 1334 1335 import std.exception : assertThrown; 1336 assertThrown!NoSuchFieldException(sd.getSize("jfidjf")); 1337 assertThrown!UnknownFieldFormatException(sd.getFormat("yetAnotherField")); 1338 } 1339 } 1340 1341 Option!Format inferFieldFormat(T)() 1342 { 1343 import gfx.core.typecons : none, some; 1344 import std.traits : RepresentationTypeTuple; 1345 1346 alias rtt = RepresentationTypeTuple!T; 1347 1348 static if (is(T == float)) { 1349 return some(Format.r32_sFloat); 1350 } 1351 else static if (is(T V : V[N], int N)) { 1352 static if (is(V == float) && N == 1) { 1353 return some(Format.r32_sFloat); 1354 } 1355 else static if (is(V == float) && N == 2) { 1356 return some(Format.rg32_sFloat); 1357 } 1358 else static if (is(V == float) && N == 3) { 1359 return some(Format.rgb32_sFloat); 1360 } 1361 else static if (is(V == float) && N == 4) { 1362 return some(Format.rgba32_sFloat); 1363 } 1364 else { 1365 return none!Format; 1366 } 1367 } 1368 else static if (rtt.length == 1 && !is(rtt[0] == T)) { 1369 return inferFieldFormat!(rtt[0])(); 1370 } 1371 else { 1372 return none!Format; 1373 } 1374 }