Skip to content

Instantly share code, notes, and snippets.

@alvinhochun
Created April 3, 2025 09:23
Show Gist options
  • Select an option

  • Save alvinhochun/426ed7f450702c103139f5986635e2a2 to your computer and use it in GitHub Desktop.

Select an option

Save alvinhochun/426ed7f450702c103139f5986635e2a2 to your computer and use it in GitHub Desktop.

Revisions

  1. alvinhochun created this gist Apr 3, 2025.
    154 changes: 154 additions & 0 deletions picasso_wrapper.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,154 @@
    #!/usr/bin/env python3

    import argparse
    import pathlib
    import re
    import subprocess
    from typing import Optional, List


    def picasso(
    files: List[str], out: str, header: Optional[str] = None, no_nop: bool = False
    ) -> None:
    cmd = [
    "picasso",
    "--out=" + out,
    *(["--header" + header] if header else []),
    *(["--no-nop"] if no_nop else []),
    *files,
    ]
    result = subprocess.run(cmd)
    result.check_returncode()


    IDENTIFIER_FILTER_REGEX = re.compile(r"[^A-Za-z0-9]+")


    def filename_to_identifier(file: str) -> str:
    name = pathlib.Path(file).stem
    return IDENTIFIER_FILTER_REGEX.sub("_", name)


    DVLE_EMITTING_DIRECTIVES = set(
    [
    # "uniform",
    "fvec",
    "ivec",
    "bool",
    # "const",
    "constf",
    "consti",
    "constfa",
    "in",
    "out",
    "entry",
    # "nodvle",
    "gsh",
    # "setfi",
    "setf",
    "seti",
    "setb",
    ]
    )

    NON_DVLE_DIRECTIVES = set(
    [
    "proc",
    "else",
    "end",
    "alias",
    ]
    )

    DIRECTIVE_REGEX = re.compile(r"^\s*\.(\w+)")


    def is_program_emit_dvle(path: str) -> bool:
    has_dvle_directive = False

    with open(path, "r", encoding="utf8") as file:
    while line := file.readline():
    line = line.strip()

    if line.startswith(";"):
    # comment
    continue

    if match := DIRECTIVE_REGEX.match(line):
    directive = match[1]

    if directive == "nodvle":
    return False

    if directive in DVLE_EMITTING_DIRECTIVES:
    has_dvle_directive = True
    elif directive not in NON_DVLE_DIRECTIVES:
    raise RuntimeError(
    f"Unexpected directive '.{directive}' in shader file '{path}'"
    )

    return has_dvle_directive


    def main() -> None:
    parser = argparse.ArgumentParser(
    prog="picasso_wrapper",
    description="Wrapper for picasso that generates an additional header (ends in '.dvle.h') with enum constants for indexing the DVLE of each program.",
    add_help=False,
    )
    parser.add_argument("-o", "--out", required=True)
    parser.add_argument("-h", "--header")
    parser.add_argument("-n", "--no-nop", action="store_true")
    parser.add_argument("files", nargs="+")

    args = parser.parse_args()

    # if args.help:

    names = [filename_to_identifier(f).upper() for f in args.files]
    unique_names = set()
    for name, path in zip(names, args.files):
    if name in unique_names:
    raise argparse.ArgumentError(
    None, f"File '{path}' produces duplicate constant name {name}."
    )
    unique_names.add(name)

    picasso(args.files, args.out, args.header, args.no_nop)

    bin_name = filename_to_identifier(args.out).upper()

    file_content = ""
    file_content += f"/* Generated by {pathlib.Path(__file__).name} - do not edit */\n"
    file_content += "#pragma once\n\n"
    file_content += f"/** DVLE indices for the programs in '{args.out}' */\n"
    file_content += f"enum {bin_name}_DVLE_enum {{\n"

    for name, path in zip(names, args.files):
    if is_program_emit_dvle(path):
    file_content += f"\t{bin_name}_DVLE_{name}, /**< {path} */\n"
    else:
    file_content += f"\t/* '{path}' does not emit a DVLE entry */\n"

    file_content += "};\n"

    old_content = ""

    out_file = pathlib.Path(args.out).with_suffix(".dvle.h")

    try:
    with open(out_file, "r", encoding="utf8", newline="\n") as file:
    old_content = file.read()
    except:
    pass

    # Skip writing file if nothing's changed (avoid unnecessary rebuilds)
    if old_content == file_content:
    return

    with open(out_file, "w", encoding="utf8", newline="\n") as file:
    file.write(file_content)


    if __name__ == "__main__":
    main()