• 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
389def compile_variation(glslang_path, compile_queue, shader_file, shader_basename, flags, enums,
390                      flags_active, enum_indices, flags_bits, enum_bits, output_shaders):
391
392    glslang_args = [glslang_path]
393
394    # generate -D defines and the output file name
395    #
396    # The variations are given a bit pattern to be able to OR different flags into a variation. The
397    # least significant bits are the flags, where there is one bit per flag.  After that, each enum
398    # takes up as few bits as needed to count that many enum values.
399    variation_bits = 0
400    variation_string = ''
401    for f in range(len(flags)):
402        if flags_active & (1 << f):
403            flag_name = flags[f]
404            glslang_args.append('-D' + flag_name + '=1')
405
406            variation_bits |= 1 << f
407            variation_string += '|' + flag_name
408
409    current_bit_start = flags_bits
410
411    for e in range(len(enums)):
412        enum_name = enums[e][1][enum_indices[e]]
413        glslang_args.append('-D' + enum_name + '=1')
414
415        variation_bits |= enum_indices[e] << current_bit_start
416        current_bit_start += enum_bits[e]
417        variation_string += '|' + enum_name
418
419    output_name = '%s.%08X' % (shader_basename, variation_bits)
420    output_path = get_output_path(output_name)
421    output_shaders.append(output_path)
422
423    if glslang_path is not None:
424        glslang_preprocessor_output_args = glslang_args + ['-E']
425        glslang_preprocessor_output_args.append(shader_file)  # Input GLSL shader
426
427        glslang_args += ['-V']  # Output mode is Vulkan
428        glslang_args += ['--variable-name', get_var_name(output_name)]  # C-style variable name
429        glslang_args += ['-o', output_path]  # Output file
430        glslang_args.append(shader_file)  # Input GLSL shader
431
432        compile_queue.add_job(shader_file, shader_basename, variation_string, output_path,
433                              glslang_args, glslang_preprocessor_output_args)
434
435
436class ShaderAndVariations:
437
438    def __init__(self, shader_file):
439        self.shader_file = shader_file
440        (self.flags, self.enums) = get_shader_variations(shader_file)
441        get_variation_bits(self.flags, self.enums)
442        (self.flags_bits, self.enum_bits) = get_variation_bits(self.flags, self.enums)
443        # Maximum index value has all flags set and all enums at max value.
444        max_index = (1 << self.flags_bits) - 1
445        current_bit_start = self.flags_bits
446        for (name, values), bits in zip(self.enums, self.enum_bits):
447            max_index |= (len(values) - 1) << current_bit_start
448            current_bit_start += bits
449        # Minimum array size is one more than the maximum value.
450        self.array_len = max_index + 1
451
452
453def get_variation_definition(shader_and_variation):
454    shader_file = shader_and_variation.shader_file
455    flags = shader_and_variation.flags
456    enums = shader_and_variation.enums
457    flags_bits = shader_and_variation.flags_bits
458    enum_bits = shader_and_variation.enum_bits
459    array_len = shader_and_variation.array_len
460
461    namespace_name = get_namespace_name(shader_file)
462
463    definition = 'namespace %s\n{\n' % namespace_name
464    if len(flags) > 0:
465        definition += 'enum flags\n{\n'
466        definition += ''.join(['k%s = 0x%08X,\n' % (flags[f], 1 << f) for f in range(len(flags))])
467        definition += '};\n'
468
469    current_bit_start = flags_bits
470
471    for e in range(len(enums)):
472        enum = enums[e]
473        enum_name = enum[0]
474        definition += 'enum %s\n{\n' % enum_name
475        definition += ''.join([
476            'k%s = 0x%08X,\n' % (enum[1][v], v << current_bit_start) for v in range(len(enum[1]))
477        ])
478        definition += '};\n'
479        current_bit_start += enum_bits[e]
480
481    definition += 'constexpr size_t kArrayLen = 0x%08X;\n' % array_len
482
483    definition += '}  // namespace %s\n' % namespace_name
484    return definition
485
486
487def get_shader_table_h(shader_and_variation):
488    shader_file = shader_and_variation.shader_file
489    flags = shader_and_variation.flags
490    enums = shader_and_variation.enums
491
492    table_name = get_variation_table_name(shader_file, 'm')
493
494    table = 'RefCounted<ShaderAndSerial> %s[' % table_name
495
496    namespace_name = "InternalShader::" + get_namespace_name(shader_file)
497
498    table += '%s::kArrayLen' % namespace_name
499
500    table += '];'
501    return table
502
503
504def get_shader_table_cpp(shader_and_variation):
505    shader_file = shader_and_variation.shader_file
506    enums = shader_and_variation.enums
507    flags_bits = shader_and_variation.flags_bits
508    enum_bits = shader_and_variation.enum_bits
509    array_len = shader_and_variation.array_len
510
511    # Cache max and mask value of each enum to quickly know when a possible variation is invalid
512    enum_maxes = []
513    enum_masks = []
514    current_bit_start = flags_bits
515
516    for e in range(len(enums)):
517        enum_values = enums[e][1]
518        enum_maxes.append((len(enum_values) - 1) << current_bit_start)
519        enum_masks.append(((1 << enum_bits[e]) - 1) << current_bit_start)
520        current_bit_start += enum_bits[e]
521
522    table_name = get_variation_table_name(shader_file)
523    var_name = get_var_name(os.path.basename(shader_file))
524
525    table = 'constexpr ShaderBlob %s[] = {\n' % table_name
526
527    for variation in range(array_len):
528        # if any variation is invalid, output an empty entry
529        if any([(variation & enum_masks[e]) > enum_maxes[e] for e in range(len(enums))]):
530            table += '{nullptr, 0}, // 0x%08X\n' % variation
531        else:
532            entry = '%s_%08X' % (var_name, variation)
533            table += '{%s, sizeof(%s)},\n' % (entry, entry)
534
535    table += '};'
536    return table
537
538
539def get_get_function_h(shader_and_variation):
540    shader_file = shader_and_variation.shader_file
541
542    function_name = get_var_name(os.path.basename(shader_file), 'get')
543
544    definition = 'angle::Result %s' % function_name
545    definition += '(Context *context, uint32_t shaderFlags, RefCounted<ShaderAndSerial> **shaderOut);'
546
547    return definition
548
549
550def get_get_function_cpp(shader_and_variation):
551    shader_file = shader_and_variation.shader_file
552    enums = shader_and_variation.enums
553
554    function_name = get_var_name(os.path.basename(shader_file), 'get')
555    namespace_name = "InternalShader::" + get_namespace_name(shader_file)
556    member_table_name = get_variation_table_name(shader_file, 'm')
557    constant_table_name = get_variation_table_name(shader_file)
558
559    definition = 'angle::Result ShaderLibrary::%s' % function_name
560    definition += '(Context *context, uint32_t shaderFlags, RefCounted<ShaderAndSerial> **shaderOut)\n{\n'
561    definition += 'return GetShader(context, %s, %s, ArraySize(%s), shaderFlags, shaderOut);\n}\n' % (
562        member_table_name, constant_table_name, constant_table_name)
563
564    return definition
565
566
567def get_destroy_call(shader_and_variation):
568    shader_file = shader_and_variation.shader_file
569
570    table_name = get_variation_table_name(shader_file, 'm')
571
572    destroy = 'for (RefCounted<ShaderAndSerial> &shader : %s)\n' % table_name
573    destroy += '{\nshader.get().destroy(device);\n}'
574    return destroy
575
576
577def shader_path(shader):
578    return '"src/libANGLE/renderer/vulkan/%s"' % slash(shader)
579
580
581def main():
582    # STEP 0: Handle inputs/outputs for run_code_generation.py's auto_script
583    shaders_dir = os.path.join('shaders', 'src')
584    if not os.path.isdir(shaders_dir):
585        raise Exception("Could not find shaders directory")
586
587    print_inputs = len(sys.argv) == 2 and sys.argv[1] == 'inputs'
588    print_outputs = len(sys.argv) == 2 and sys.argv[1] == 'outputs'
589    # If an argument X is given that's not inputs or outputs, compile shaders that match *X*.
590    # This is useful in development to build only the shader of interest.
591    shader_files_to_compile = os.listdir(shaders_dir)
592    if not (print_inputs or print_outputs or len(sys.argv) < 2):
593        shader_files_to_compile = [f for f in shader_files_to_compile if f.find(sys.argv[1]) != -1]
594
595    valid_extensions = ['.vert', '.frag', '.comp']
596    input_shaders = sorted([
597        os.path.join(shaders_dir, shader)
598        for shader in os.listdir(shaders_dir)
599        if any([os.path.splitext(shader)[1] == ext for ext in valid_extensions])
600    ])
601    if print_inputs:
602        glslang_binaries = [get_linux_glslang_exe_path(), get_win_glslang_exe_path()]
603        glslang_binary_hashes = [path + '.sha1' for path in glslang_binaries]
604        print(",".join(input_shaders + glslang_binary_hashes))
605        return 0
606
607    # STEP 1: Call glslang to generate the internal shaders into small .inc files.
608    # Iterates over the shaders and call glslang with the right arguments.
609
610    glslang_path = None
611    if not print_outputs:
612        glslang_path = get_glslang_exe_path()
613
614    output_shaders = []
615
616    input_shaders_and_variations = [
617        ShaderAndVariations(shader_file) for shader_file in input_shaders
618    ]
619
620    compile_queue = CompileQueue()
621
622    for shader_and_variation in input_shaders_and_variations:
623        shader_file = shader_and_variation.shader_file
624        flags = shader_and_variation.flags
625        enums = shader_and_variation.enums
626        flags_bits = shader_and_variation.flags_bits
627        enum_bits = shader_and_variation.enum_bits
628
629        # an array where each element i is in [0, len(enums[i])),
630        # telling which enum is currently selected
631        enum_indices = [0] * len(enums)
632
633        output_name = os.path.basename(shader_file)
634
635        while True:
636            do_compile = not print_outputs and output_name in shader_files_to_compile
637            # a number where each bit says whether a flag is active or not,
638            # with values in [0, 2^len(flags))
639            for flags_active in range(1 << len(flags)):
640                compile_variation(glslang_path if do_compile else None, compile_queue, shader_file,
641                                  output_name, flags, enums, flags_active, enum_indices,
642                                  flags_bits, enum_bits, output_shaders)
643
644            if not next_enum_variation(enums, enum_indices):
645                break
646
647    output_shaders = sorted(output_shaders)
648    outputs = output_shaders + [out_file_cpp, out_file_h]
649
650    if print_outputs:
651        print(','.join(outputs))
652        return 0
653
654    compile_queue.finish()
655
656    # STEP 2: Consolidate the .inc files into an auto-generated cpp/h library.
657    with open(out_file_cpp, 'w') as outfile:
658        includes = "\n".join([gen_shader_include(shader) for shader in output_shaders])
659        shader_tables_cpp = '\n'.join(
660            [get_shader_table_cpp(s) for s in input_shaders_and_variations])
661        shader_destroy_calls = '\n'.join(
662            [get_destroy_call(s) for s in input_shaders_and_variations])
663        shader_get_functions_cpp = '\n'.join(
664            [get_get_function_cpp(s) for s in input_shaders_and_variations])
665
666        outcode = template_shader_library_cpp.format(
667            script_name=__file__,
668            copyright_year=date.today().year,
669            out_file_name=out_file_cpp,
670            input_file_name='shaders/src/*',
671            internal_shader_includes=includes,
672            shader_tables_cpp=shader_tables_cpp,
673            shader_destroy_calls=shader_destroy_calls,
674            shader_get_functions_cpp=shader_get_functions_cpp)
675        outfile.write(outcode)
676        outfile.close()
677
678    with open(out_file_h, 'w') as outfile:
679        shader_variation_definitions = '\n'.join(
680            [get_variation_definition(s) for s in input_shaders_and_variations])
681        shader_get_functions_h = '\n'.join(
682            [get_get_function_h(s) for s in input_shaders_and_variations])
683        shader_tables_h = '\n'.join([get_shader_table_h(s) for s in input_shaders_and_variations])
684        outcode = template_shader_library_h.format(
685            script_name=__file__,
686            copyright_year=date.today().year,
687            out_file_name=out_file_h,
688            input_file_name='shaders/src/*',
689            shader_variation_definitions=shader_variation_definitions,
690            shader_get_functions_h=shader_get_functions_h,
691            shader_tables_h=shader_tables_h)
692        outfile.write(outcode)
693        outfile.close()
694
695    # STEP 3: Create a gni file with the generated files.
696    with io.open(out_file_gni, 'w', newline='\n') as outfile:
697        outcode = template_shader_includes_gni.format(
698            script_name=__file__,
699            copyright_year=date.today().year,
700            out_file_name=out_file_gni,
701            input_file_name='shaders/src/*',
702            shaders_list=',\n'.join([shader_path(shader) for shader in output_shaders]))
703        outfile.write(outcode)
704        outfile.close()
705
706    return 0
707
708
709if __name__ == '__main__':
710    sys.exit(main())
711