| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import optparse |
| import os.path |
| import re |
| import subprocess |
| import sys |
| |
| |
| def ConvertToCamelCase(input): |
| """Converts the input string from 'unix_hacker' style to 'CamelCase' style.""" |
| return ''.join(x[:1].upper() + x[1:] for x in input.split('_')) |
| |
| |
| def ExtractShaderTargetNamesFromSource(source_hlsl_file): |
| """Parses '@gyp_compile' and '@gyp_namespace' metadata from an .hlsl file.""" |
| # matches strings like // @gyp_compile(arg_a, arg_b) ... |
| gyp_compile = re.compile( |
| '^//\s*@gyp_compile\(\s*(?P<profile>[a-zA-Z0-9_]+)\s*,' |
| '\s*(?P<function_name>[a-zA-Z0-9_]+)\s*\).*') |
| # matches strings like // @gyp_namespace(arg_a) ... |
| gyp_namespace = re.compile( |
| '^//\s*@gyp_namespace\(\s*(?P<namespace>[a-zA-Z0-9_]+)\s*\).*') |
| |
| shader_targets = [] # tuples like ('vs_2_0', 'vertexMain') |
| namespace = None |
| with open(source_hlsl_file) as hlsl: |
| for line_number, line in enumerate(hlsl.read().splitlines(), 1): |
| m = gyp_compile.match(line) |
| if m: |
| shader_targets.append((m.group('profile'), m.group('function_name'))) |
| continue |
| m = gyp_namespace.match(line) |
| if m: |
| namespace = m.group('namespace') |
| continue |
| if '@gyp' in line: |
| print '%s(%d) : warning: ignoring malformed @gyp directive ' % ( |
| source_hlsl_file, line_number) |
| |
| if not shader_targets: |
| print ( |
| """%s(%d) : error: Reached end of file without finding @gyp_compile directive. |
| |
| By convention, each HLSL source must contain one or more @gyp_compile |
| directives in its comments, as metadata informing the Chrome build tool |
| which entry points should be compiled. For example, to specify compilation |
| of a function named 'vertexMain' as a shader model 2 vertex shader: |
| |
| // @gyp_compile(vs_2_0, vertexMain) |
| |
| Or to compile a pixel shader 2.0 function named 'someOtherShader': |
| |
| // @gyp_compile(ps_2_0, someOtherShader) |
| |
| To wrap everything in a C++ namespace 'foo_bar', add a line somewhere like: |
| |
| // @gyp_namespace(foo_bar) |
| |
| (Namespaces are optional) |
| """ % (source_hlsl_file, line_number)) |
| sys.exit(1) |
| return (shader_targets, namespace) |
| |
| |
| def GetCppVariableName(function_name): |
| return 'k%s' % ConvertToCamelCase(function_name) |
| |
| |
| def CompileMultipleHLSLShadersToOneHeaderFile(fxc_compiler_path, |
| source_hlsl_file, |
| namespace, |
| shader_targets, |
| target_header_file, |
| target_cc_file): |
| """Compiles specified shaders from an .hlsl file into a single C++ header.""" |
| header_output = [] |
| # Invoke the compiler one at a time to write the c++ header file, |
| # then read that header file into |header_output|. |
| for (compiler_profile, hlsl_function_name) in shader_targets: |
| file_name_only = os.path.basename(source_hlsl_file) |
| base_filename, _ = os.path.splitext(file_name_only) |
| cpp_global_var_name = GetCppVariableName(hlsl_function_name) |
| |
| command = [fxc_compiler_path, |
| source_hlsl_file, # From this HLSL file |
| '/E', hlsl_function_name, # Compile one function |
| '/T', compiler_profile, # As a vertex or pixel shader |
| '/Vn', cpp_global_var_name, # Into a C++ constant thus named |
| '/Fh', target_header_file, # Declared in this C++ header file. |
| '/O3'] # Fast is better than slow. |
| (out, err) = subprocess.Popen(command, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| shell=False).communicate() |
| if err: |
| print 'Error while compiling %s in file %s' % ( |
| hlsl_function_name, source_hlsl_file) |
| print err |
| sys.exit(1) |
| with open(target_header_file, 'r') as header: |
| header_output.append(header.read()) |
| |
| # Now, re-write the .h and .cc files with the concatenation of all |
| # the individual passes. |
| classname = '%sHLSL' % (ConvertToCamelCase(base_filename)) |
| preamble = '\n'.join([ |
| '/' * 77, |
| '// This file is auto-generated from %s' % file_name_only, |
| '//', |
| "// To edit it directly would be a fool's errand.", |
| '/' * 77, |
| '', |
| '']) |
| with open(target_header_file, 'wb') as h: |
| h.write(preamble) |
| h.write('#pragma once\n') |
| h.write('#include <windows.h>\n\n') |
| if namespace: |
| h.write('namespace %s {\n\n' % namespace) |
| h.write('namespace %s {\n\n' % classname) |
| for _, function_name in shader_targets: |
| h.write('extern const BYTE %s[];\n' % GetCppVariableName(function_name)) |
| h.write('\n} // namespace %s\n' % classname) |
| if namespace: |
| h.write('\n} // namespace %s\n' % namespace) |
| |
| with open(target_cc_file, 'wb') as cc: |
| cc.write(preamble) |
| cc.write('#include "%s"\n\n' % os.path.basename(target_header_file)) |
| if namespace: |
| cc.write('namespace %s {\n\n' % namespace) |
| cc.write('namespace %s {\n\n' % classname) |
| cc.write(''.join(header_output)) |
| cc.write('\n} // namespace %s\n' % classname) |
| if namespace: |
| cc.write('\n} // namespace %s\n' % namespace) |
| |
| |
| if __name__ == '__main__': |
| parser = optparse.OptionParser() |
| parser.add_option('--shader_compiler_tool', dest='compiler') |
| parser.add_option('--output_h_file', dest='header_file') |
| parser.add_option('--output_cc_file', dest='cc_file') |
| parser.add_option('--input_hlsl_file', dest='hlsl_file') |
| (options, args) = parser.parse_args() |
| |
| hlsl_file = os.path.abspath(options.hlsl_file) |
| shader_targets, namespace = ExtractShaderTargetNamesFromSource(hlsl_file) |
| |
| header_file = os.path.normpath(options.header_file) |
| cc_file = os.path.normpath(options.cc_file) |
| CompileMultipleHLSLShadersToOneHeaderFile(options.compiler, |
| hlsl_file, |
| namespace, |
| shader_targets, |
| header_file, |
| cc_file) |