| #!/usr/bin/python |
| # Copyright 2016 Google Inc. All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Parse GLSL shader files to generate C++ classes. |
| |
| This script parses all GLSL shader input files to generate C++ class |
| definitions which inherit from ShaderBase: |
| |
| cobalt/renderer/rasterizer/egl/shader_base.h |
| |
| The classes will have member functions to access any "attribute" or "uniform" |
| member(s) found in the shader file. For example, a shader file with the |
| following lines: |
| |
| uniform mat3 u_view_matrix; |
| uniform sampler2d intexture; |
| attribute vec2 inposition; |
| attribute float a_depth; |
| |
| will have the following member functions in the generated class: |
| |
| GLuint intexture() const; |
| GLenum intexture_texunit() const; |
| GLuint u_view_matrix() const; |
| GLuint inposition() const; |
| GLuint a_depth() const; |
| |
| Additionally, the generated classes will include definitions to bind attribute |
| locations, query uniform locations, and initialize uniform samplers to default |
| texture units. These are done in the class' InitializePreLink, |
| InitializePostLink, and InitializePostUse functions, respectively. |
| """ |
| |
| import datetime |
| import os |
| import re |
| import sys |
| |
| |
| def GetBasename(filename): |
| """Returns the filename without the directory and without its extension. |
| |
| Args: |
| filename: A filename from which a basename is to be extracted. |
| |
| Returns: |
| A string derived from the filename except the directory and extension |
| are removed. |
| """ |
| return os.path.splitext(os.path.basename(filename))[0] |
| |
| |
| def GetShaderClassName(filename): |
| """Returns a C++ class name based on the given file name.""" |
| # Convert underscore-delineated string to camelcase and prepends 'Shader'. |
| class_name = ''.join(x.capitalize() for x in |
| GetBasename(filename).lower().split('_')) |
| return 'Shader' + class_name |
| |
| |
| def GetDataDefinitionStringForFile(filename): |
| """Returns a string containing C++ array definition for file contents.""" |
| with open(filename, 'rb') as f: |
| # Read the file contents. |
| file_contents = f.read() |
| |
| # Strip comments and blank lines. |
| file_contents = re.sub(r'/\*.*?\*/', '', file_contents, flags=re.DOTALL) |
| file_contents = re.sub(r'(\s)*//.*', '', file_contents) |
| file_contents = re.sub('(^|\\n)(\\s)*\\n', '\\1', file_contents) |
| |
| # Remove any carriage returns (apitrace doesn't handle shader sources with |
| # that character very well), and add a null terminator at the end. |
| file_contents = file_contents.replace('\r', '') + '\0' |
| |
| def GetChunk(contents, chunk_size): |
| # Yield successive |chunk_size|-sized chunks from |contents|. |
| for i in xrange(0, len(contents), chunk_size): |
| yield contents[i:i + chunk_size] |
| |
| # Break up the data into chunk sizes such that the produced output lines |
| # representing the data in the .h file are less than 80 characters long. |
| length_of_output_byte_string = 6 |
| max_characters_per_line = 80 |
| chunk_size = max_characters_per_line / length_of_output_byte_string |
| |
| # Convert each byte to ASCII hexadecimal form and output that to the C++ |
| # header file, line-by-line. |
| data_definition_string = '{\n' |
| for output_line_data in GetChunk(file_contents, chunk_size): |
| data_definition_string += ( |
| ' ' + |
| ' '.join(['0x%02x,' % ord(y) for y in output_line_data]) + |
| '\n') |
| data_definition_string += '};\n' |
| return data_definition_string |
| |
| |
| def GetShaderSourceDefinitions(files): |
| """Returns a string containing C++ that represents all data in files.""" |
| source_definition_string = '' |
| for filename in files: |
| class_name = GetShaderClassName(filename) |
| source_definition_string += '\nconst char %s::kSource[] = ' % class_name |
| source_definition_string += GetDataDefinitionStringForFile(filename) |
| return source_definition_string |
| |
| |
| SOURCE_FILE_TEMPLATE = """\ |
| // Copyright {year} Google Inc. All Rights Reserved. |
| // This file is generated by: |
| // cobalt/renderer/rasterizer/egl/generate_shader_impl.py |
| // Do not edit! |
| |
| #include "{header_filename}" |
| |
| namespace cobalt {{ |
| namespace renderer {{ |
| namespace rasterizer {{ |
| namespace egl {{ |
| {source_definitions} |
| }} // namespace egl |
| }} // namespace rasterizer |
| }} // namespace renderer |
| }} // namespace cobalt |
| """ |
| |
| |
| def GenerateSourceFile(source_filename, header_filename, all_shaders): |
| """Generate the actual C++ source file.""" |
| header_filename = os.path.basename(header_filename) |
| current_year = datetime.datetime.now().year |
| with open(source_filename, 'w') as output_file: |
| output_file.write( |
| SOURCE_FILE_TEMPLATE.format( |
| year=current_year, |
| header_filename=header_filename, |
| source_definitions=GetShaderSourceDefinitions(all_shaders))) |
| |
| |
| def IndentLines(lines, indent_count): |
| """Indent all lines by the given amount.""" |
| line_start = '\n' + ' ' * indent_count |
| return lines.replace('\n', line_start) |
| |
| |
| def GetShaderInputs(filename): |
| """Return all attributes, uniforms, and samplers in the given shader file.""" |
| attributes = [] |
| uniforms = [] |
| samplers = [] |
| |
| with open(filename, 'r') as f: |
| # Read file as a single string to facilitate comment removal. |
| file_contents = f.read() |
| |
| # Remove comments and directives. |
| file_contents = re.sub(r'/\*.*?\*/', '', file_contents, flags=re.DOTALL) |
| file_contents = re.sub(r'\#.*', '', file_contents) |
| file_contents = re.sub(r'//.*', '', file_contents) |
| |
| # Remove everything associated with the main program. |
| file_contents = re.sub(r'void main\(\) .*', '', file_contents, |
| flags=re.DOTALL) |
| |
| # Match attributes, uniforms, and samplers (a subset of uniforms). |
| for line in file_contents.split(';'): |
| words = line.strip().split() |
| if len(words) == 3: |
| type_name = words[0].lower() |
| if type_name == 'attribute': |
| attributes.append(words[2]) |
| elif type_name == 'uniform': |
| uniforms.append(words[2]) |
| if words[1].lower().startswith('sampler'): |
| samplers.append(words[2]) |
| |
| return attributes, uniforms, samplers |
| |
| |
| def ParseUniformName(name): |
| """Parse a uniform name into the base string and array count (if array).""" |
| temp = re.match(r'(\w+)(?:\[(\d+)\])?', name) |
| base = temp.group(1) |
| count = temp.group(2) |
| return base, count |
| |
| |
| def GetAttributeMethods(attributes): |
| """Return a string representing C++ methods for the given attributes.""" |
| methods = '' |
| for index, name in enumerate(attributes): |
| methods += '\nGLuint {0}() const {{ return {1}; }}'.format(name, index) |
| return methods |
| |
| |
| def GetUniformMethods(uniforms): |
| """Return a string representing C++ methods for the given uniforms.""" |
| methods = '' |
| for name in uniforms: |
| base, count = ParseUniformName(name) |
| methods += '\nGLuint {0}() const {{ return {0}_; }}'.format(base) |
| if count: |
| methods += ('\nstatic constexpr GLsizei {0}_count() {{ return {1}; }}' |
| .format(base, count)) |
| return methods |
| |
| |
| def GetSamplerMethods(samplers): |
| """Returns a string representing C++ methods for the given samplers.""" |
| methods = '' |
| for index, name in enumerate(samplers): |
| methods += ('\nGLenum {0}_texunit() const {{ return GL_TEXTURE{1}; }}' |
| .format(name, index)) |
| return methods |
| |
| |
| def GetInitializePreLink(attributes): |
| """Returns a string representing C++ statements to process during prelink.""" |
| statements = '' |
| for name in attributes: |
| statements += '\nBindAttribLocation(program, {0}(), "{0}");'.format(name) |
| return statements |
| |
| |
| def GetInitializePostLink(uniforms): |
| """Returns a string representing C++ statements to process during postlink.""" |
| statements = '' |
| for name in uniforms: |
| base, unused_count = ParseUniformName(name) |
| statements += '\n{0}_ = GetUniformLocation(program, "{0}");'.format(base) |
| return statements |
| |
| |
| def GetInitializePostUse(samplers): |
| """Returns a string representing C++ statements to process during postuse.""" |
| statements = '' |
| for name in samplers: |
| statements += ('\nSetTextureUnitForUniformSampler({0}(), {0}_texunit());' |
| .format(name)) |
| return statements |
| |
| |
| def GetVariables(variable_names): |
| """Returns a string representing C++ member variable declarations.""" |
| variables = '' |
| for name in variable_names: |
| base, unused_count = ParseUniformName(name) |
| variables += '\nGLuint {0}_;'.format(base) |
| return variables |
| |
| |
| CLASS_TEMPLATE = """\ |
| class {class_name} : public ShaderBase {{ |
| public: |
| const char* GetSource() const OVERRIDE {{ return kSource; }} |
| {attribute_methods}\ |
| {uniform_methods}\ |
| {sampler_methods} |
| |
| private: |
| void InitializePreLink(GLuint program) OVERRIDE {{\ |
| {initialize_prelink} |
| }} |
| void InitializePostLink(GLuint program) OVERRIDE {{\ |
| {initialize_postlink} |
| }} |
| void InitializePostUse() OVERRIDE {{\ |
| {initialize_postuse} |
| }} |
| {variables} |
| |
| static const char kSource[]; |
| }}; |
| """ |
| |
| |
| def GetClassDefinitionForFile(filename): |
| """Returns a string representing a C++ class definition for a shader.""" |
| attributes, uniforms, samplers = GetShaderInputs(filename) |
| return CLASS_TEMPLATE.format( |
| class_name=GetShaderClassName(filename), |
| # For the following fields, each line in the strings should start with |
| # a newline. |
| attribute_methods=IndentLines(GetAttributeMethods(attributes), 2), |
| uniform_methods=IndentLines(GetUniformMethods(uniforms), 2), |
| sampler_methods=IndentLines(GetSamplerMethods(samplers), 2), |
| initialize_prelink=IndentLines(GetInitializePreLink(attributes), 4), |
| initialize_postlink=IndentLines(GetInitializePostLink(uniforms), 4), |
| initialize_postuse=IndentLines(GetInitializePostUse(samplers), 4), |
| variables=IndentLines(GetVariables(uniforms), 2)) |
| |
| |
| def GetShaderClassDefinitions(files): |
| """Returns a string representing C++ class definitions for all given files.""" |
| class_definition_string = '' |
| for filename in files: |
| class_definition_string += '\n' + GetClassDefinitionForFile(filename) |
| return class_definition_string |
| |
| |
| HEADER_FILE_TEMPLATE = """\ |
| // Copyright {year} Google Inc. All Rights Reserved. |
| // This file is generated by: |
| // cobalt/renderer/rasterizer/egl/generate_shader_impl.py |
| // Do not edit! |
| |
| #ifndef {include_guard} |
| #define {include_guard} |
| |
| #include "base/compiler_specific.h" |
| #include "cobalt/renderer/rasterizer/egl/shader_base.h" |
| |
| namespace cobalt {{ |
| namespace renderer {{ |
| namespace rasterizer {{ |
| namespace egl {{ |
| {class_definitions} |
| }} // namespace egl |
| }} // namespace rasterizer |
| }} // namespace renderer |
| }} // namespace cobalt |
| |
| #endif // {include_guard} |
| """ |
| |
| |
| def GenerateHeaderFile(output_filename, all_shaders): |
| """Generate the actual C++ header file.""" |
| include_guard = 'GENERATED_%s_H_' % GetBasename(output_filename).upper() |
| current_year = datetime.datetime.now().year |
| with open(output_filename, 'w') as output_file: |
| output_file.write( |
| HEADER_FILE_TEMPLATE.format( |
| year=current_year, |
| include_guard=include_guard, |
| class_definitions=GetShaderClassDefinitions(all_shaders))) |
| |
| |
| def main(output_header_filename, output_source_filename, shader_files_file): |
| all_shader_files = [] |
| with open(shader_files_file, 'r') as input_file: |
| shader_files = input_file.readlines() |
| for filename in shader_files: |
| all_shader_files.append(filename.strip()) |
| |
| GenerateSourceFile(output_source_filename, output_header_filename, |
| all_shader_files) |
| GenerateHeaderFile(output_header_filename, all_shader_files) |
| |
| |
| if __name__ == '__main__': |
| main(sys.argv[1], sys.argv[2], sys.argv[3]) |