|
36 | 36 | // lines are not interchangeable. glslang's `-x --vn <name>` emits a COMPLETE C |
37 | 37 | // declaration -- `const uint32_t <name>[] = { ... };` -- while glslc's |
38 | 38 | // `-mfmt=c` emits a BARE INITIALISER LIST, `{ ... }`, which is not a |
39 | | -// translation unit on its own. A rule that takes both therefore has to write |
40 | | -// the declaration around glslc's output itself, which `wrap_glslc_output` |
41 | | -// below does. |
| 39 | +// translation unit on its own. So on both routes the compiler's output is a |
| 40 | +// `<base>.inc` and the PUBLIC header `<base>.h` is written by this rule -- |
| 41 | +// `write_header` below. It has to be that way round for a second reason: a |
| 42 | +// generated header must be includable on its own, and glslang's complete |
| 43 | +// declaration names `uint32_t` while including nothing. |
42 | 44 | // |
43 | 45 | // An earlier revision of this file supported glslang alone, and said why: |
44 | 46 | // nothing in this ecosystem published glslc, "and a route with no payload |
@@ -447,20 +449,47 @@ inline std::vector<std::string> device_shaders() { |
447 | 449 | // plan time, before any action runs. Its content does not depend on the |
448 | 450 | // shader's text, which is why nothing has to re-derive it when the shader |
449 | 451 | // changes: ninja rebuilds the `.inc`, the `#include` picks it up. |
450 | | -inline bool wrap_glslc_output(const std::string& header, const std::string& inc, |
451 | | - const std::string& sym) { |
| 452 | +// THE PUBLIC HEADER IS WRITTEN BY THE RULE ON BOTH ROUTES, AND IT HAS TO BE |
| 453 | +// INCLUDABLE ON ITS OWN. |
| 454 | +// |
| 455 | +// glslc emits an initialiser list, so a declaration had to be written around it |
| 456 | +// and that header was self-contained by construction. glslang's `-x --vn` emits |
| 457 | +// a complete C declaration, so the rule wrote nothing -- and that file names |
| 458 | +// `uint32_t` while including nothing: |
| 459 | +// |
| 460 | +// tri_vert.h:3:7: error: 'uint32_t' does not name a type |
| 461 | +// |
| 462 | +// Measured in a sandbox, on a program whose first include was the generated |
| 463 | +// header. Every consumer that had worked put a Vulkan header ahead of it, which |
| 464 | +// is why an incomplete header read as a working one for as long as nobody |
| 465 | +// included it first. Two compilers producing an EQUIVALENT header is the whole |
| 466 | +// premise of this rule choosing between them, and "equivalent" has to include |
| 467 | +// this. |
| 468 | +// |
| 469 | +// So both routes now produce the same two files: `<base>.inc` from the |
| 470 | +// compiler, and `<base>.h` from here. |
| 471 | +inline bool write_header(const std::string& header, const std::string& inc, |
| 472 | + const std::string& sym, flavour kind) { |
452 | 473 | std::ofstream out{header, std::ios::trunc}; |
453 | 474 | if (!out) { |
454 | 475 | std::cerr << std::format("mcpp.rules.spirv: cannot write {}", header) << '\n'; |
455 | 476 | return false; |
456 | 477 | } |
457 | | - out << "// Generated by mcpp.rules.spirv. glslc emits an initialiser list;\n" |
458 | | - "// this declaration is what makes it a translation unit.\n" |
| 478 | + const auto incName = std::filesystem::path(inc).filename().string(); |
| 479 | + out << "// Generated by mcpp.rules.spirv.\n" |
459 | 480 | "#pragma once\n" |
460 | | - "#include <cstdint>\n" |
461 | | - "static const uint32_t " << sym << "[] =\n" |
462 | | - "#include \"" << std::filesystem::path(inc).filename().string() << "\"\n" |
463 | | - ";\n"; |
| 481 | + "#include <cstdint>\n"; |
| 482 | + if (kind == flavour::glslc) { |
| 483 | + out << "// glslc emits an initialiser list; this declaration is what makes it\n" |
| 484 | + "// a translation unit.\n" |
| 485 | + "static const uint32_t " << sym << "[] =\n" |
| 486 | + "#include \"" << incName << "\"\n" |
| 487 | + ";\n"; |
| 488 | + } else { |
| 489 | + out << "// glslang emits a complete `const uint32_t " << sym << "[]`; what this\n" |
| 490 | + "// adds is the type it names and a guard.\n" |
| 491 | + "#include \"" << incName << "\"\n"; |
| 492 | + } |
464 | 493 | return out.good(); |
465 | 494 | } |
466 | 495 |
|
@@ -573,12 +602,14 @@ inline bool compile(std::span<const std::string> shaders, options opt = {}) { |
573 | 602 | // named strings for the life of the statement that submits. |
574 | 603 | const std::string id = "spirv:" + src; |
575 | 604 | const std::string desc = std::string(cc.name()) + " " + src; |
576 | | - // For glslc the action's output is the initialiser list; for glslang it |
577 | | - // is the header itself. |
| 605 | + // ONE SHAPE FOR BOTH ROUTES: the compiler writes `<base>.inc` and this |
| 606 | + // rule writes `<base>.h` around it. glslang used to write the header |
| 607 | + // itself, which made the two routes' headers differ in whether they |
| 608 | + // could be included first -- see `write_header`. |
578 | 609 | const std::string inc = base + ".inc"; |
579 | | - const std::string output = cc.kind == flavour::glslc ? inc : header; |
| 610 | + const std::string output = inc; |
580 | 611 |
|
581 | | - if (cc.kind == flavour::glslc && !wrap_glslc_output(header, inc, sym)) return false; |
| 612 | + if (!write_header(header, inc, sym, cc.kind)) return false; |
582 | 613 |
|
583 | 614 | mcpp::action a; |
584 | 615 | a.id = id.c_str(); |
|
0 commit comments