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