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<ShaderModule> *shaders, 64 const CompressedShaderBlob *compressedShaderBlobs, 65 size_t shadersCount, 66 uint32_t shaderFlags, 67 RefCounted<ShaderModule> **shaderOut) 68{{ 69 ASSERT(shaderFlags < shadersCount); 70 RefCounted<ShaderModule> &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 InitShaderModule(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<ShaderModule> %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<ShaderModule> **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<ShaderModule> **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<ShaderModule> &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