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