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