• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2# Copyright 2018 The ANGLE Project Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5#
6# gen_vk_internal_shaders.py:
7#  Code generation for internal Vulkan shaders. Should be run when an internal
8#  shader program is changed, added or removed.
9#  Because this script can be slow direct invocation is supported. But before
10#  code upload please run scripts/run_code_generation.py.
11
12from datetime import date
13import io
14import json
15import multiprocessing
16import os
17import platform
18import re
19import subprocess
20import sys
21
22out_file_cpp = 'vk_internal_shaders_autogen.cpp'
23out_file_h = 'vk_internal_shaders_autogen.h'
24out_file_gni = 'vk_internal_shaders_autogen.gni'
25
26is_windows = platform.system() == 'Windows'
27is_linux = platform.system() == 'Linux'
28
29# Templates for the generated files:
30template_shader_library_cpp = u"""// GENERATED FILE - DO NOT EDIT.
31// Generated by {script_name} using data from {input_file_name}
32//
33// Copyright {copyright_year} The ANGLE Project Authors. All rights reserved.
34// Use of this source code is governed by a BSD-style license that can be
35// found in the LICENSE file.
36//
37// {out_file_name}:
38//   Pre-generated shader library for the ANGLE Vulkan back-end.
39
40#include "libANGLE/renderer/vulkan/vk_internal_shaders_autogen.h"
41
42namespace rx
43{{
44namespace vk
45{{
46namespace
47{{
48{internal_shader_includes}
49
50// This is SPIR-V binary blob and the size.
51struct ShaderBlob
52{{
53    const uint32_t *code;
54    size_t codeSize;
55}};
56
57{shader_tables_cpp}
58
59angle::Result GetShader(Context *context,
60                        RefCounted<ShaderAndSerial> *shaders,
61                        const ShaderBlob *shaderBlobs,
62                        size_t shadersCount,
63                        uint32_t shaderFlags,
64                        RefCounted<ShaderAndSerial> **shaderOut)
65{{
66    ASSERT(shaderFlags < shadersCount);
67    RefCounted<ShaderAndSerial> &shader = shaders[shaderFlags];
68    *shaderOut                          = &shader;
69
70    if (shader.get().valid())
71    {{
72        return angle::Result::Continue;
73    }}
74
75    // Create shader lazily. Access will need to be locked for multi-threading.
76    const ShaderBlob &shaderCode = shaderBlobs[shaderFlags];
77    ASSERT(shaderCode.code != nullptr);
78
79    return InitShaderAndSerial(context, &shader.get(), shaderCode.code, shaderCode.codeSize);
80}}
81}}  // anonymous namespace
82
83
84ShaderLibrary::ShaderLibrary()
85{{
86}}
87
88ShaderLibrary::~ShaderLibrary()
89{{
90}}
91
92void ShaderLibrary::destroy(VkDevice device)
93{{
94    {shader_destroy_calls}
95}}
96
97{shader_get_functions_cpp}
98}}  // namespace vk
99}}  // namespace rx
100"""
101
102template_shader_library_h = u"""// GENERATED FILE - DO NOT EDIT.
103// Generated by {script_name} using data from {input_file_name}
104//
105// Copyright {copyright_year} The ANGLE Project Authors. All rights reserved.
106// Use of this source code is governed by a BSD-style license that can be
107// found in the LICENSE file.
108//
109// {out_file_name}:
110//   Pre-generated shader library for the ANGLE Vulkan back-end.
111
112#ifndef LIBANGLE_RENDERER_VULKAN_VK_INTERNAL_SHADERS_AUTOGEN_H_
113#define LIBANGLE_RENDERER_VULKAN_VK_INTERNAL_SHADERS_AUTOGEN_H_
114
115#include "libANGLE/renderer/vulkan/vk_utils.h"
116
117namespace rx
118{{
119namespace vk
120{{
121namespace InternalShader
122{{
123{shader_variation_definitions}
124}}  // namespace InternalShader
125
126class ShaderLibrary final : angle::NonCopyable
127{{
128  public:
129    ShaderLibrary();
130    ~ShaderLibrary();
131
132    void destroy(VkDevice device);
133
134    {shader_get_functions_h}
135
136  private:
137    {shader_tables_h}
138}};
139}}  // namespace vk
140}}  // namespace rx
141
142#endif  // LIBANGLE_RENDERER_VULKAN_VK_INTERNAL_SHADERS_AUTOGEN_H_
143"""
144
145template_shader_includes_gni = u"""# GENERATED FILE - DO NOT EDIT.
146# Generated by {script_name} using data from {input_file_name}
147#
148# Copyright {copyright_year} The ANGLE Project Authors. All rights reserved.
149# Use of this source code is governed by a BSD-style license that can be
150# found in the LICENSE file.
151#
152# {out_file_name}:
153#   List of generated shaders for inclusion in ANGLE's build process.
154
155angle_vulkan_internal_shaders = [
156{shaders_list}
157]
158"""
159
160
161# Gets the constant variable name for a generated shader.
162def get_var_name(output, prefix='k'):
163    return prefix + output.replace(".", "_")
164
165
166# Gets the namespace name given to constants generated from shader_file
167def get_namespace_name(shader_file):
168    return get_var_name(os.path.basename(shader_file), '')
169
170
171# Gets the namespace name given to constants generated from shader_file
172def get_variation_table_name(shader_file, prefix='k'):
173    return get_var_name(os.path.basename(shader_file), prefix) + '_shaders'
174
175
176# Gets the internal ID string for a particular shader.
177def get_shader_id(shader):
178    file = os.path.splitext(os.path.basename(shader))[0]
179    return file.replace(".", "_")
180
181
182# Returns the name of the generated SPIR-V file for a shader.
183def get_output_path(name):
184    return os.path.join('shaders', 'gen', name + ".inc")
185
186
187# Finds a path to GN's out directory
188def get_linux_glslang_exe_path():
189    return '../../../../tools/glslang/glslang_validator'
190
191
192def get_win_glslang_exe_path():
193    return get_linux_glslang_exe_path() + '.exe'
194
195
196def get_glslang_exe_path():
197    glslang_exe = get_win_glslang_exe_path() if is_windows else get_linux_glslang_exe_path()
198    if not os.path.isfile(glslang_exe):
199        raise Exception('Could not find %s' % glslang_exe)
200    return glslang_exe
201
202
203# Generates the code for a shader blob array entry.
204def gen_shader_blob_entry(shader):
205    var_name = get_var_name(os.path.basename(shader))[0:-4]
206    return "{%s, %s}" % (var_name, "sizeof(%s)" % var_name)
207
208
209def slash(s):
210    return s.replace('\\', '/')
211
212
213def gen_shader_include(shader):
214    return '#include "libANGLE/renderer/vulkan/%s"' % slash(shader)
215
216
217def get_shader_variations(shader):
218    variation_file = shader + '.json'
219    if not os.path.exists(variation_file):
220        # If there is no variation file, assume none.
221        return ({}, [])
222
223    with open(variation_file) as fin:
224        variations = json.loads(fin.read())
225        flags = {}
226        enums = []
227
228        for key, value in variations.iteritems():
229            if key == "Description":
230                continue
231            elif key == "Flags":
232                flags = value
233            elif len(value) > 0:
234                enums.append((key, value))
235
236        # sort enums so the ones with the most waste ends up last, reducing the table size
237        enums.sort(key=lambda enum: (1 << (len(enum[1]) - 1).bit_length()) / float(len(enum[1])))
238
239        return (flags, enums)
240
241
242def get_variation_bits(flags, enums):
243    flags_bits = len(flags)
244    enum_bits = [(len(enum[1]) - 1).bit_length() for enum in enums]
245    return (flags_bits, enum_bits)
246
247
248def next_enum_variation(enums, enum_indices):
249    """Loop through indices from [0, 0, ...] to [L0-1, L1-1, ...]
250    where Li is len(enums[i]).  The list can be thought of as a number with many
251    digits, where each digit is in [0, Li), and this function effectively implements
252    the increment operation, with the least-significant digit being the first item."""
253    for i in range(len(enums)):
254        current = enum_indices[i]
255        # if current digit has room, increment it.
256        if current + 1 < len(enums[i][1]):
257            enum_indices[i] = current + 1
258            return True
259        # otherwise reset it to 0 and carry to the next digit.
260        enum_indices[i] = 0
261
262    # if this is reached, the number has overflowed and the loop is finished.
263    return False
264
265
266compact_newlines_regex = re.compile(r"\n\s*\n", re.MULTILINE)
267
268
269def cleanup_preprocessed_shader(shader_text):
270    return compact_newlines_regex.sub('\n\n', shader_text.strip())
271
272
273class CompileQueue:
274
275    class AppendPreprocessorOutput:
276
277        def __init__(self, shader_file, preprocessor_args, output_path):
278            # Asynchronously launch the preprocessor job.
279            self.process = subprocess.Popen(
280                preprocessor_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
281            # Store the file name for output to be appended to.
282            self.output_path = output_path
283            # Store info for error description.
284            self.shader_file = shader_file
285
286        def wait(self, queue):
287            (out, err) = self.process.communicate()
288            if self.process.returncode == 0:
289                # Use unix line endings.
290                out = out.replace('\r\n', '\n')
291                # Clean up excessive empty lines.
292                out = cleanup_preprocessed_shader(out)
293                # Comment it out!
294                out = '\n'.join([('// ' + line).strip() for line in out.splitlines()])
295                # Append preprocessor output to the output file.
296                with open(self.output_path, 'ab') as incfile:
297                    incfile.write('\n\n// Generated from:\n//\n')
298                    incfile.write(out + '\n')
299                out = None
300            return (out, err, self.process.returncode, None,
301                    "Error running preprocessor on " + self.shader_file)
302
303    class CompileToSPIRV:
304
305        def __init__(self, shader_file, shader_basename, variation_string, output_path,
306                     compile_args, preprocessor_args):
307            # Asynchronously launch the compile job.
308            self.process = subprocess.Popen(
309                compile_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
310            # Store info for launching the preprocessor.
311            self.preprocessor_args = preprocessor_args
312            self.output_path = output_path
313            # Store info for job and error description.
314            self.shader_file = shader_file
315            self.shader_basename = shader_basename
316            self.variation_string = variation_string
317
318        def wait(self, queue):
319            (out, err) = self.process.communicate()
320            if self.process.returncode == 0:
321                # Insert the preprocessor job in the queue.
322                queue.append(
323                    CompileQueue.AppendPreprocessorOutput(self.shader_file, self.preprocessor_args,
324                                                          self.output_path))
325            # If all the output says is the source file name, don't bother printing it.
326            if out.strip() == self.shader_file:
327                out = None
328            description = self.output_path + ': ' + self.shader_basename + self.variation_string
329            return (out, err, self.process.returncode, description,
330                    "Error compiling " + self.shader_file)
331
332    def __init__(self):
333        # Compile with as many CPU threads are detected.  Once a shader is compiled, another job is
334        # automatically added to the queue to append the preprocessor output to the generated file.
335        self.queue = []
336        self.thread_count = multiprocessing.cpu_count()
337
338    def _wait_first(self, ignore_output=False):
339        (out, err, returncode, description, exception_description) = self.queue[0].wait(self.queue)
340        self.queue.pop(0)
341        if not ignore_output:
342            if description:
343                print description
344            if out and out.strip():
345                print out.strip()
346            if err and err.strip():
347                print err
348            if returncode != 0:
349                return exception_description
350        return None
351
352    # Wait for all pending tasks.  If called after error is detected, ignore_output can be used to
353    # make sure errors in later jobs are suppressed to avoid cluttering the output.  This is
354    # because the same compile error is likely present in other variations of the same shader and
355    # outputting the same error multiple times is not useful.
356    def _wait_all(self, ignore_output=False):
357        exception_description = None
358        while len(self.queue) > 0:
359            this_job_exception = self._wait_first(ignore_output)
360            # If encountered an error, keep it to be raised, ignoring errors from following jobs.
361            if this_job_exception and not ignore_output:
362                exception_description = this_job_exception
363                ignore_output = True
364
365        return exception_description
366
367    def add_job(self, shader_file, shader_basename, variation_string, output_path, compile_args,
368                preprocessor_args):
369        # If the queue is full, wait until there is at least one slot available.
370        while len(self.queue) >= self.thread_count:
371            exception = self._wait_first(False)
372            # If encountered an exception, cleanup following jobs and raise it.
373            if exception:
374                self._wait_all(True)
375                raise Exception(exception)
376
377        # Add a compile job
378        self.queue.append(
379            CompileQueue.CompileToSPIRV(shader_file, shader_basename, variation_string,
380                                        output_path, compile_args, preprocessor_args))
381
382    def finish(self):
383        exception = self._wait_all(False)
384        # If encountered an exception, cleanup following jobs and raise it.
385        if exception is not None:
386            raise Exception(exception)
387
388
389# If the option is just a string, that's the name.  Otherwise, it could be
390# [ name, arg1, ..., argN ].  In that case, name is option[0] and option[1:] are extra arguments
391# that need to be passed to glslang_validator for this variation.
392def get_variation_name(option):
393    return option if isinstance(option, unicode) else option[0]
394
395
396def get_variation_args(option):
397    return [] if isinstance(option, unicode) else option[1:]
398
399
400def compile_variation(glslang_path, compile_queue, shader_file, shader_basename, flags, enums,
401                      flags_active, enum_indices, flags_bits, enum_bits, output_shaders):
402
403    glslang_args = [glslang_path]
404
405    # generate -D defines and the output file name
406    #
407    # The variations are given a bit pattern to be able to OR different flags into a variation. The
408    # least significant bits are the flags, where there is one bit per flag.  After that, each enum
409    # takes up as few bits as needed to count that many enum values.
410    variation_bits = 0
411    variation_string = ''
412    variation_extra_args = []
413    for f in range(len(flags)):
414        if flags_active & (1 << f):
415            flag = flags[f]
416            flag_name = get_variation_name(flag)
417            variation_extra_args += get_variation_args(flag)
418            glslang_args.append('-D' + flag_name + '=1')
419
420            variation_bits |= 1 << f
421            variation_string += '|' + flag_name
422
423    current_bit_start = flags_bits
424
425    for e in range(len(enums)):
426        enum = enums[e][1][enum_indices[e]]
427        enum_name = get_variation_name(enum)
428        variation_extra_args += get_variation_args(enum)
429        glslang_args.append('-D' + enum_name + '=1')
430
431        variation_bits |= enum_indices[e] << current_bit_start
432        current_bit_start += enum_bits[e]
433        variation_string += '|' + enum_name
434
435    output_name = '%s.%08X' % (shader_basename, variation_bits)
436    output_path = get_output_path(output_name)
437    output_shaders.append(output_path)
438
439    if glslang_path is not None:
440        glslang_preprocessor_output_args = glslang_args + ['-E']
441        glslang_preprocessor_output_args.append(shader_file)  # Input GLSL shader
442
443        glslang_args += variation_extra_args
444
445        glslang_args += ['-V']  # Output mode is Vulkan
446        glslang_args += ['--variable-name', get_var_name(output_name)]  # C-style variable name
447        glslang_args += ['-o', output_path]  # Output file
448        glslang_args.append(shader_file)  # Input GLSL shader
449
450        compile_queue.add_job(shader_file, shader_basename, variation_string, output_path,
451                              glslang_args, glslang_preprocessor_output_args)
452
453
454class ShaderAndVariations:
455
456    def __init__(self, shader_file):
457        self.shader_file = shader_file
458        (self.flags, self.enums) = get_shader_variations(shader_file)
459        get_variation_bits(self.flags, self.enums)
460        (self.flags_bits, self.enum_bits) = get_variation_bits(self.flags, self.enums)
461        # Maximum index value has all flags set and all enums at max value.
462        max_index = (1 << self.flags_bits) - 1
463        current_bit_start = self.flags_bits
464        for (name, values), bits in zip(self.enums, self.enum_bits):
465            max_index |= (len(values) - 1) << current_bit_start
466            current_bit_start += bits
467        # Minimum array size is one more than the maximum value.
468        self.array_len = max_index + 1
469
470
471def get_variation_definition(shader_and_variation):
472    shader_file = shader_and_variation.shader_file
473    flags = shader_and_variation.flags
474    enums = shader_and_variation.enums
475    flags_bits = shader_and_variation.flags_bits
476    enum_bits = shader_and_variation.enum_bits
477    array_len = shader_and_variation.array_len
478
479    namespace_name = get_namespace_name(shader_file)
480
481    definition = 'namespace %s\n{\n' % namespace_name
482    if len(flags) > 0:
483        definition += 'enum flags\n{\n'
484        definition += ''.join([
485            'k%s = 0x%08X,\n' % (get_variation_name(flags[f]), 1 << f) for f in range(len(flags))
486        ])
487        definition += '};\n'
488
489    current_bit_start = flags_bits
490
491    for e in range(len(enums)):
492        enum = enums[e]
493        enum_name = enum[0]
494        definition += 'enum %s\n{\n' % enum_name
495        definition += ''.join([
496            'k%s = 0x%08X,\n' % (get_variation_name(enum[1][v]), v << current_bit_start)
497            for v in range(len(enum[1]))
498        ])
499        definition += '};\n'
500        current_bit_start += enum_bits[e]
501
502    definition += 'constexpr size_t kArrayLen = 0x%08X;\n' % array_len
503
504    definition += '}  // namespace %s\n' % namespace_name
505    return definition
506
507
508def get_shader_table_h(shader_and_variation):
509    shader_file = shader_and_variation.shader_file
510    flags = shader_and_variation.flags
511    enums = shader_and_variation.enums
512
513    table_name = get_variation_table_name(shader_file, 'm')
514
515    table = 'RefCounted<ShaderAndSerial> %s[' % table_name
516
517    namespace_name = "InternalShader::" + get_namespace_name(shader_file)
518
519    table += '%s::kArrayLen' % namespace_name
520
521    table += '];'
522    return table
523
524
525def get_shader_table_cpp(shader_and_variation):
526    shader_file = shader_and_variation.shader_file
527    enums = shader_and_variation.enums
528    flags_bits = shader_and_variation.flags_bits
529    enum_bits = shader_and_variation.enum_bits
530    array_len = shader_and_variation.array_len
531
532    # Cache max and mask value of each enum to quickly know when a possible variation is invalid
533    enum_maxes = []
534    enum_masks = []
535    current_bit_start = flags_bits
536
537    for e in range(len(enums)):
538        enum_values = enums[e][1]
539        enum_maxes.append((len(enum_values) - 1) << current_bit_start)
540        enum_masks.append(((1 << enum_bits[e]) - 1) << current_bit_start)
541        current_bit_start += enum_bits[e]
542
543    table_name = get_variation_table_name(shader_file)
544    var_name = get_var_name(os.path.basename(shader_file))
545
546    table = 'constexpr ShaderBlob %s[] = {\n' % table_name
547
548    for variation in range(array_len):
549        # if any variation is invalid, output an empty entry
550        if any([(variation & enum_masks[e]) > enum_maxes[e] for e in range(len(enums))]):
551            table += '{nullptr, 0}, // 0x%08X\n' % variation
552        else:
553            entry = '%s_%08X' % (var_name, variation)
554            table += '{%s, sizeof(%s)},\n' % (entry, entry)
555
556    table += '};'
557    return table
558
559
560def get_get_function_h(shader_and_variation):
561    shader_file = shader_and_variation.shader_file
562
563    function_name = get_var_name(os.path.basename(shader_file), 'get')
564
565    definition = 'angle::Result %s' % function_name
566    definition += '(Context *context, uint32_t shaderFlags, RefCounted<ShaderAndSerial> **shaderOut);'
567
568    return definition
569
570
571def get_get_function_cpp(shader_and_variation):
572    shader_file = shader_and_variation.shader_file
573    enums = shader_and_variation.enums
574
575    function_name = get_var_name(os.path.basename(shader_file), 'get')
576    namespace_name = "InternalShader::" + get_namespace_name(shader_file)
577    member_table_name = get_variation_table_name(shader_file, 'm')
578    constant_table_name = get_variation_table_name(shader_file)
579
580    definition = 'angle::Result ShaderLibrary::%s' % function_name
581    definition += '(Context *context, uint32_t shaderFlags, RefCounted<ShaderAndSerial> **shaderOut)\n{\n'
582    definition += 'return GetShader(context, %s, %s, ArraySize(%s), shaderFlags, shaderOut);\n}\n' % (
583        member_table_name, constant_table_name, constant_table_name)
584
585    return definition
586
587
588def get_destroy_call(shader_and_variation):
589    shader_file = shader_and_variation.shader_file
590
591    table_name = get_variation_table_name(shader_file, 'm')
592
593    destroy = 'for (RefCounted<ShaderAndSerial> &shader : %s)\n' % table_name
594    destroy += '{\nshader.get().destroy(device);\n}'
595    return destroy
596
597
598def shader_path(shader):
599    return '"%s"' % slash(shader)
600
601
602def main():
603    # STEP 0: Handle inputs/outputs for run_code_generation.py's auto_script
604    shaders_dir = os.path.join('shaders', 'src')
605    if not os.path.isdir(shaders_dir):
606        raise Exception("Could not find shaders directory")
607
608    print_inputs = len(sys.argv) == 2 and sys.argv[1] == 'inputs'
609    print_outputs = len(sys.argv) == 2 and sys.argv[1] == 'outputs'
610    # If an argument X is given that's not inputs or outputs, compile shaders that match *X*.
611    # This is useful in development to build only the shader of interest.
612    shader_files_to_compile = os.listdir(shaders_dir)
613    if not (print_inputs or print_outputs or len(sys.argv) < 2):
614        shader_files_to_compile = [f for f in shader_files_to_compile if f.find(sys.argv[1]) != -1]
615
616    valid_extensions = ['.vert', '.frag', '.comp']
617    input_shaders = sorted([
618        os.path.join(shaders_dir, shader)
619        for shader in os.listdir(shaders_dir)
620        if any([os.path.splitext(shader)[1] == ext for ext in valid_extensions])
621    ])
622    if print_inputs:
623        glslang_binaries = [get_linux_glslang_exe_path(), get_win_glslang_exe_path()]
624        glslang_binary_hashes = [path + '.sha1' for path in glslang_binaries]
625        print(",".join(input_shaders + glslang_binary_hashes))
626        return 0
627
628    # STEP 1: Call glslang to generate the internal shaders into small .inc files.
629    # Iterates over the shaders and call glslang with the right arguments.
630
631    glslang_path = None
632    if not print_outputs:
633        glslang_path = get_glslang_exe_path()
634
635    output_shaders = []
636
637    input_shaders_and_variations = [
638        ShaderAndVariations(shader_file) for shader_file in input_shaders
639    ]
640
641    compile_queue = CompileQueue()
642
643    for shader_and_variation in input_shaders_and_variations:
644        shader_file = shader_and_variation.shader_file
645        flags = shader_and_variation.flags
646        enums = shader_and_variation.enums
647        flags_bits = shader_and_variation.flags_bits
648        enum_bits = shader_and_variation.enum_bits
649
650        # an array where each element i is in [0, len(enums[i])),
651        # telling which enum is currently selected
652        enum_indices = [0] * len(enums)
653
654        output_name = os.path.basename(shader_file)
655
656        while True:
657            do_compile = not print_outputs and output_name in shader_files_to_compile
658            # a number where each bit says whether a flag is active or not,
659            # with values in [0, 2^len(flags))
660            for flags_active in range(1 << len(flags)):
661                compile_variation(glslang_path if do_compile else None, compile_queue, shader_file,
662                                  output_name, flags, enums, flags_active, enum_indices,
663                                  flags_bits, enum_bits, output_shaders)
664
665            if not next_enum_variation(enums, enum_indices):
666                break
667
668    output_shaders = sorted(output_shaders)
669    outputs = output_shaders + [out_file_cpp, out_file_h]
670
671    if print_outputs:
672        print(','.join(outputs))
673        return 0
674
675    compile_queue.finish()
676
677    # STEP 2: Consolidate the .inc files into an auto-generated cpp/h library.
678    with open(out_file_cpp, 'w') as outfile:
679        includes = "\n".join([gen_shader_include(shader) for shader in output_shaders])
680        shader_tables_cpp = '\n'.join(
681            [get_shader_table_cpp(s) for s in input_shaders_and_variations])
682        shader_destroy_calls = '\n'.join(
683            [get_destroy_call(s) for s in input_shaders_and_variations])
684        shader_get_functions_cpp = '\n'.join(
685            [get_get_function_cpp(s) for s in input_shaders_and_variations])
686
687        outcode = template_shader_library_cpp.format(
688            script_name=__file__,
689            copyright_year=date.today().year,
690            out_file_name=out_file_cpp,
691            input_file_name='shaders/src/*',
692            internal_shader_includes=includes,
693            shader_tables_cpp=shader_tables_cpp,
694            shader_destroy_calls=shader_destroy_calls,
695            shader_get_functions_cpp=shader_get_functions_cpp)
696        outfile.write(outcode)
697        outfile.close()
698
699    with open(out_file_h, 'w') as outfile:
700        shader_variation_definitions = '\n'.join(
701            [get_variation_definition(s) for s in input_shaders_and_variations])
702        shader_get_functions_h = '\n'.join(
703            [get_get_function_h(s) for s in input_shaders_and_variations])
704        shader_tables_h = '\n'.join([get_shader_table_h(s) for s in input_shaders_and_variations])
705        outcode = template_shader_library_h.format(
706            script_name=__file__,
707            copyright_year=date.today().year,
708            out_file_name=out_file_h,
709            input_file_name='shaders/src/*',
710            shader_variation_definitions=shader_variation_definitions,
711            shader_get_functions_h=shader_get_functions_h,
712            shader_tables_h=shader_tables_h)
713        outfile.write(outcode)
714        outfile.close()
715
716    # STEP 3: Create a gni file with the generated files.
717    with io.open(out_file_gni, 'w', newline='\n') as outfile:
718        outcode = template_shader_includes_gni.format(
719            script_name=__file__,
720            copyright_year=date.today().year,
721            out_file_name=out_file_gni,
722            input_file_name='shaders/src/*',
723            shaders_list=',\n'.join([shader_path(shader) for shader in output_shaders]))
724        outfile.write(outcode)
725        outfile.close()
726
727    return 0
728
729
730if __name__ == '__main__':
731    sys.exit(main())
732