Home | History | Annotate | Download | only in surface
      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