1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import optparse 6 import os.path 7 import re 8 import subprocess 9 import sys 10 11 12 def ConvertToCamelCase(input): 13 """Converts the input string from 'unix_hacker' style to 'CamelCase' style.""" 14 return ''.join(x[:1].upper() + x[1:] for x in input.split('_')) 15 16 17 def ExtractShaderTargetNamesFromSource(source_hlsl_file): 18 """Parses '@gyp_compile' and '@gyp_namespace' metadata from an .hlsl file.""" 19 # matches strings like // @gyp_compile(arg_a, arg_b) ... 20 gyp_compile = re.compile( 21 '^//\s*@gyp_compile\(\s*(?P<profile>[a-zA-Z0-9_]+)\s*,' 22 '\s*(?P<function_name>[a-zA-Z0-9_]+)\s*\).*') 23 # matches strings like // @gyp_namespace(arg_a) ... 24 gyp_namespace = re.compile( 25 '^//\s*@gyp_namespace\(\s*(?P<namespace>[a-zA-Z0-9_]+)\s*\).*') 26 27 shader_targets = [] # tuples like ('vs_2_0', 'vertexMain') 28 namespace = None 29 with open(source_hlsl_file) as hlsl: 30 for line_number, line in enumerate(hlsl.read().splitlines(), 1): 31 m = gyp_compile.match(line) 32 if m: 33 shader_targets.append((m.group('profile'), m.group('function_name'))) 34 continue 35 m = gyp_namespace.match(line) 36 if m: 37 namespace = m.group('namespace') 38 continue 39 if '@gyp' in line: 40 print '%s(%d) : warning: ignoring malformed @gyp directive ' % ( 41 source_hlsl_file, line_number) 42 43 if not shader_targets: 44 print ( 45 """%s(%d) : error: Reached end of file without finding @gyp_compile directive. 46 47 By convention, each HLSL source must contain one or more @gyp_compile 48 directives in its comments, as metadata informing the Chrome build tool 49 which entry points should be compiled. For example, to specify compilation 50 of a function named 'vertexMain' as a shader model 2 vertex shader: 51 52 // @gyp_compile(vs_2_0, vertexMain) 53 54 Or to compile a pixel shader 2.0 function named 'someOtherShader': 55 56 // @gyp_compile(ps_2_0, someOtherShader) 57 58 To wrap everything in a C++ namespace 'foo_bar', add a line somewhere like: 59 60 // @gyp_namespace(foo_bar) 61 62 (Namespaces are optional) 63 """ % (source_hlsl_file, line_number)) 64 sys.exit(1) 65 return (shader_targets, namespace) 66 67 68 def GetCppVariableName(function_name): 69 return 'k%s' % ConvertToCamelCase(function_name) 70 71 72 def CompileMultipleHLSLShadersToOneHeaderFile(fxc_compiler_path, 73 source_hlsl_file, 74 namespace, 75 shader_targets, 76 target_header_file, 77 target_cc_file): 78 """Compiles specified shaders from an .hlsl file into a single C++ header.""" 79 header_output = [] 80 # Invoke the compiler one at a time to write the c++ header file, 81 # then read that header file into |header_output|. 82 for (compiler_profile, hlsl_function_name) in shader_targets: 83 file_name_only = os.path.basename(source_hlsl_file) 84 base_filename, _ = os.path.splitext(file_name_only) 85 cpp_global_var_name = GetCppVariableName(hlsl_function_name) 86 87 command = [fxc_compiler_path, 88 source_hlsl_file, # From this HLSL file 89 '/E', hlsl_function_name, # Compile one function 90 '/T', compiler_profile, # As a vertex or pixel shader 91 '/Vn', cpp_global_var_name, # Into a C++ constant thus named 92 '/Fh', target_header_file, # Declared in this C++ header file. 93 '/O3'] # Fast is better than slow. 94 (out, err) = subprocess.Popen(command, 95 stdout=subprocess.PIPE, 96 stderr=subprocess.PIPE, 97 shell=False).communicate() 98 if err: 99 print 'Error while compiling %s in file %s' % ( 100 hlsl_function_name, source_hlsl_file) 101 print err 102 sys.exit(1) 103 with open(target_header_file, 'r') as header: 104 header_output.append(header.read()) 105 106 # Now, re-write the .h and .cc files with the concatenation of all 107 # the individual passes. 108 classname = '%sHLSL' % (ConvertToCamelCase(base_filename)) 109 preamble = '\n'.join([ 110 '/' * 77, 111 '// This file is auto-generated from %s' % file_name_only, 112 '//', 113 "// To edit it directly would be a fool's errand.", 114 '/' * 77, 115 '', 116 '']) 117 with open(target_header_file, 'wb') as h: 118 h.write(preamble) 119 h.write('#pragma once\n') 120 h.write('#include <windows.h>\n\n') 121 if namespace: 122 h.write('namespace %s {\n\n' % namespace) 123 h.write('namespace %s {\n\n' % classname) 124 for _, function_name in shader_targets: 125 h.write('extern const BYTE %s[];\n' % GetCppVariableName(function_name)) 126 h.write('\n} // namespace %s\n' % classname) 127 if namespace: 128 h.write('\n} // namespace %s\n' % namespace) 129 130 with open(target_cc_file, 'wb') as cc: 131 cc.write(preamble) 132 cc.write('#include "%s"\n\n' % os.path.basename(target_header_file)) 133 if namespace: 134 cc.write('namespace %s {\n\n' % namespace) 135 cc.write('namespace %s {\n\n' % classname) 136 cc.write(''.join(header_output)) 137 cc.write('\n} // namespace %s\n' % classname) 138 if namespace: 139 cc.write('\n} // namespace %s\n' % namespace) 140 141 142 if __name__ == '__main__': 143 parser = optparse.OptionParser() 144 parser.add_option('--shader_compiler_tool', dest='compiler') 145 parser.add_option('--output_h_file', dest='header_file') 146 parser.add_option('--output_cc_file', dest='cc_file') 147 parser.add_option('--input_hlsl_file', dest='hlsl_file') 148 (options, args) = parser.parse_args() 149 150 hlsl_file = os.path.abspath(options.hlsl_file) 151 shader_targets, namespace = ExtractShaderTargetNamesFromSource(hlsl_file) 152 153 header_file = os.path.normpath(options.header_file) 154 cc_file = os.path.normpath(options.cc_file) 155 CompileMultipleHLSLShadersToOneHeaderFile(options.compiler, 156 hlsl_file, 157 namespace, 158 shader_targets, 159 header_file, 160 cc_file) 161