1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
# 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)
|