diff options
Diffstat (limited to 'gfx/layers/d3d11/genshaders.py')
-rw-r--r-- | gfx/layers/d3d11/genshaders.py | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/gfx/layers/d3d11/genshaders.py b/gfx/layers/d3d11/genshaders.py new file mode 100644 index 0000000000..b9701978d5 --- /dev/null +++ b/gfx/layers/d3d11/genshaders.py @@ -0,0 +1,176 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +import argparse +import codecs +import locale +import os +import re +import subprocess +import sys +import tempfile + +import buildconfig +import yaml + + +def shell_main(): + parser = argparse.ArgumentParser() + parser.add_argument("-o", "--output", type=str, required=True, help="Output file") + parser.add_argument("manifest", type=str, help="Manifest source file") + args = parser.parse_args() + + with open(args.output, "w") as out_file: + process_manifest(out_file, args.manifest) + + +def main(output_fp, input_filename): + return process_manifest(output_fp, input_filename) + + +HEADER = """// AUTOGENERATED - DO NOT EDIT +namespace mozilla { +namespace layers { + +struct ShaderBytes { const void* mData; size_t mLength; }; +""" +FOOTER = """ +} // namespace layers +} // namespace mozilla""" + + +def process_manifest(output_fp, manifest_filename): + with codecs.open(manifest_filename, "r", "UTF-8") as in_fp: + manifest = yaml.safe_load(in_fp) + shader_folder, _ = os.path.split(manifest_filename) + + output_fp.write(HEADER) + + deps = set() + for block in manifest: + if "type" not in block: + raise Exception("Expected 'type' key with shader mode") + if "file" not in block: + raise Exception("Expected 'file' key with shader file") + if "shaders" not in block: + raise Exception("Expected 'shaders' key with shader name list") + + shader_file = os.path.join(shader_folder, block["file"]) + deps.add(shader_file) + + shader_model = block["type"] + for shader_name in block["shaders"]: + new_deps = run_fxc( + shader_model=shader_model, + shader_file=shader_file, + shader_name=shader_name, + output_fp=output_fp, + ) + deps |= new_deps + + output_fp.write(FOOTER) + return deps + + +def run_fxc(shader_model, shader_file, shader_name, output_fp): + fxc_location = buildconfig.substs["FXC"] + + argv = [ + fxc_location, + "-nologo", + "-T{0}".format(shader_model), + os.path.relpath(shader_file), + "-E{0}".format(shader_name), + "-Vn{0}".format(shader_name), + "-Vi", + ] + if "WINNT" not in buildconfig.substs["HOST_OS_ARCH"]: + argv.insert(0, buildconfig.substs["WINE"]) + if shader_model.startswith("vs_"): + argv += ["-DVERTEX_SHADER"] + elif shader_model.startswith("ps_"): + argv += ["-DPIXEL_SHADER"] + + deps = None + with ScopedTempFilename() as temp_filename: + argv += ["-Fh{0}".format(os.path.relpath(temp_filename))] + + sys.stdout.write("{0}\n".format(" ".join(argv))) + sys.stdout.flush() + proc_stdout = subprocess.check_output(argv) + proc_stdout = decode_console_text(sys.stdout, proc_stdout) + deps = find_dependencies(proc_stdout) + assert "fxc2" in fxc_location or len(deps) > 0 + + with open(temp_filename, "r") as temp_fp: + output_fp.write(temp_fp.read()) + + output_fp.write("ShaderBytes s{0} = {{ {0}, sizeof({0}) }};\n".format(shader_name)) + return deps + + +def find_dependencies(fxc_output): + # Dependencies look like this: + # Resolved to [<path>] + # + # Microsoft likes to change output strings based on the user's language, so + # instead of pattern matching on that string, we take everything in between + # brackets. We filter out potentially bogus strings later. + deps = set() + for line in fxc_output.split("\n"): + m = re.search(r"\[([^\]]+)\]", line) + if m is None: + continue + dep_path = m.group(1) + dep_path = os.path.normpath(dep_path) + # When run via Wine, FXC's output contains Windows paths on the Z drive. + # We want to normalize them back to unix paths for the build system. + if "WINNT" not in buildconfig.substs[ + "HOST_OS_ARCH" + ] and dep_path.lower().startswith("z:"): + dep_path = dep_path[2:].replace("\\", "/") + if os.path.isfile(dep_path): + deps.add(dep_path) + return deps + + +# Python reads the raw bytes from stdout, so we need to try our best to +# capture that as a valid Python string. + + +def decode_console_text(pipe, text): + try: + if pipe.encoding: + return text.decode(pipe.encoding, "replace") + except Exception: + pass + try: + return text.decode(locale.getpreferredencoding(), "replace") + except Exception: + return text.decode("utf8", "replace") + + +# Allocate a temporary file name and delete it when done. We need an extra +# wrapper for this since TemporaryNamedFile holds the file open. + + +class ScopedTempFilename(object): + def __init__(self): + self.name = None + + def __enter__(self): + with tempfile.NamedTemporaryFile(dir=os.getcwd(), delete=False) as tmp: + self.name = tmp.name + return self.name + + def __exit__(self, type, value, traceback): + if not self.name: + return + try: + os.unlink(self.name) + except Exception: + pass + + +if __name__ == "__main__": + shell_main() |