gecko-dev/gfx/layers/d3d11/genshaders.py
Tom Ritter 3efa760176 Bug 1370007 Generate Shaders on a MinGW Cross Compile on Linux r=dvander,ted
Bug 1365859 introduced a dependency on the Visual Studio binary 'fxc'
to generate Shader bytecode. This was unavailable when compiling for
Windows on Linux as part of a MinGW build.

This commit adds a configure check for fxc, and also searches for
fxc2, which was written (https://github.com/tomrittervg/fxc2) to be
a tiny application that wraps D3DCompileFromFile and can produce
similar (but not exactly the same) output as fxc.

fxc2 is compiled using MinGW for Windows, and runs under wine, so
we need to check for wine also.

Finally, fxc outputs some include information fxc2 doesn't, so
we will just change that assert to not take effect.

MozReview-Commit-ID: 8LVxuODi6cV

--HG--
extra : rebase_source : 9116d266663284d6594e34aa53bd37eae01ba67f
2017-07-24 14:32:08 -05:00

159 lines
4.5 KiB
Python
Executable File

# 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 yaml
import buildconfig
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.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),
shader_file,
'-E{0}'.format(shader_name),
'-Vn{0}'.format(shader_name),
'-Vi',
]
if 'Linux' 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(temp_filename)]
sys.stdout.write('{0}\n'.format(' '.join(argv)))
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)
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:
pass
try:
return text.decode(locale.getpreferredencoding(), 'replace')
except:
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(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:
pass
if __name__ == '__main__':
shell_main()