tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

genshaders.py (5282B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 import argparse
      5 import codecs
      6 import locale
      7 import os
      8 import re
      9 import subprocess
     10 import sys
     11 import tempfile
     12 
     13 import buildconfig
     14 import yaml
     15 
     16 
     17 def shell_main():
     18    parser = argparse.ArgumentParser()
     19    parser.add_argument("-o", "--output", type=str, required=True, help="Output file")
     20    parser.add_argument("manifest", type=str, help="Manifest source file")
     21    args = parser.parse_args()
     22 
     23    with open(args.output, "w") as out_file:
     24        process_manifest(out_file, args.manifest)
     25 
     26 
     27 def main(output_fp, input_filename):
     28    return process_manifest(output_fp, input_filename)
     29 
     30 
     31 HEADER = """// AUTOGENERATED - DO NOT EDIT
     32 namespace mozilla {
     33 namespace layers {
     34 
     35 struct ShaderBytes { const void* mData; size_t mLength; };
     36 """
     37 FOOTER = """
     38 } // namespace layers
     39 } // namespace mozilla"""
     40 
     41 
     42 def process_manifest(output_fp, manifest_filename):
     43    with codecs.open(manifest_filename, "r", "UTF-8") as in_fp:
     44        manifest = yaml.safe_load(in_fp)
     45    shader_folder, _ = os.path.split(manifest_filename)
     46 
     47    output_fp.write(HEADER)
     48 
     49    deps = set()
     50    for block in manifest:
     51        if "type" not in block:
     52            raise Exception("Expected 'type' key with shader mode")
     53        if "file" not in block:
     54            raise Exception("Expected 'file' key with shader file")
     55        if "shaders" not in block:
     56            raise Exception("Expected 'shaders' key with shader name list")
     57 
     58        shader_file = os.path.join(shader_folder, block["file"])
     59        deps.add(shader_file)
     60 
     61        shader_model = block["type"]
     62        for shader_name in block["shaders"]:
     63            new_deps = run_fxc(
     64                shader_model=shader_model,
     65                shader_file=shader_file,
     66                shader_name=shader_name,
     67                output_fp=output_fp,
     68            )
     69            deps |= new_deps
     70 
     71    output_fp.write(FOOTER)
     72    return deps
     73 
     74 
     75 def run_fxc(shader_model, shader_file, shader_name, output_fp):
     76    fxc_location = buildconfig.substs["FXC"]
     77 
     78    argv = [
     79        fxc_location,
     80        "-nologo",
     81        f"-T{shader_model}",
     82        os.path.relpath(shader_file),
     83        f"-E{shader_name}",
     84        f"-Vn{shader_name}",
     85        "-Vi",
     86    ]
     87    if "WINNT" not in buildconfig.substs["HOST_OS_ARCH"]:
     88        argv.insert(0, buildconfig.substs["WINE"])
     89    if shader_model.startswith("vs_"):
     90        argv += ["-DVERTEX_SHADER"]
     91    elif shader_model.startswith("ps_"):
     92        argv += ["-DPIXEL_SHADER"]
     93 
     94    deps = None
     95    with ScopedTempFilename() as temp_filename:
     96        argv += [f"-Fh{os.path.relpath(temp_filename)}"]
     97 
     98        sys.stdout.write("{}\n".format(" ".join(argv)))
     99        sys.stdout.flush()
    100        proc_stdout = subprocess.check_output(argv)
    101        proc_stdout = decode_console_text(sys.stdout, proc_stdout)
    102        deps = find_dependencies(proc_stdout)
    103        assert "fxc2" in fxc_location or len(deps) > 0
    104 
    105        with open(temp_filename) as temp_fp:
    106            output_fp.write(temp_fp.read())
    107 
    108    output_fp.write(
    109        f"ShaderBytes s{shader_name} = {{ {shader_name}, sizeof({shader_name}) }};\n"
    110    )
    111    return deps
    112 
    113 
    114 def find_dependencies(fxc_output):
    115    # Dependencies look like this:
    116    #   Resolved to [<path>]
    117    #
    118    # Microsoft likes to change output strings based on the user's language, so
    119    # instead of pattern matching on that string, we take everything in between
    120    # brackets. We filter out potentially bogus strings later.
    121    deps = set()
    122    for line in fxc_output.split("\n"):
    123        m = re.search(r"\[([^\]]+)\]", line)
    124        if m is None:
    125            continue
    126        dep_path = m.group(1)
    127        dep_path = os.path.normpath(dep_path)
    128        # When run via Wine, FXC's output contains Windows paths on the Z drive.
    129        # We want to normalize them back to unix paths for the build system.
    130        if "WINNT" not in buildconfig.substs[
    131            "HOST_OS_ARCH"
    132        ] and dep_path.lower().startswith("z:"):
    133            dep_path = dep_path[2:].replace("\\", "/")
    134        if os.path.isfile(dep_path):
    135            deps.add(dep_path)
    136    return deps
    137 
    138 
    139 # Python reads the raw bytes from stdout, so we need to try our best to
    140 # capture that as a valid Python string.
    141 
    142 
    143 def decode_console_text(pipe, text):
    144    try:
    145        if pipe.encoding:
    146            return text.decode(pipe.encoding, "replace")
    147    except Exception:
    148        pass
    149    try:
    150        return text.decode(locale.getpreferredencoding(), "replace")
    151    except Exception:
    152        return text.decode("utf8", "replace")
    153 
    154 
    155 # Allocate a temporary file name and delete it when done. We need an extra
    156 # wrapper for this since TemporaryNamedFile holds the file open.
    157 
    158 
    159 class ScopedTempFilename:
    160    def __init__(self):
    161        self.name = None
    162 
    163    def __enter__(self):
    164        with tempfile.NamedTemporaryFile(dir=os.getcwd(), delete=False) as tmp:
    165            self.name = tmp.name
    166            return self.name
    167 
    168    def __exit__(self, type, value, traceback):
    169        if not self.name:
    170            return
    171        try:
    172            os.unlink(self.name)
    173        except Exception:
    174            pass
    175 
    176 
    177 if __name__ == "__main__":
    178    shell_main()