| #!/usr/bin/env python3 | |
| # Copyright 2024 The Dawn & Tint Authors | |
| # | |
| # Redistribution and use in source and binary forms, with or without | |
| # modification, are permitted provided that the following conditions are met: | |
| # | |
| # 1. Redistributions of source code must retain the above copyright notice, this | |
| # list of conditions and the following disclaimer. | |
| # | |
| # 2. Redistributions in binary form must reproduce the above copyright notice, | |
| # this list of conditions and the following disclaimer in the documentation | |
| # and/or other materials provided with the distribution. | |
| # | |
| # 3. Neither the name of the copyright holder nor the names of its | |
| # contributors may be used to endorse or promote products derived from | |
| # this software without specific prior written permission. | |
| # | |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| """ | |
| Generates a header file that declares all of the Tint benchmark programs as embedded WGSL shaders, | |
| and declares macros that will be used to register them all with Google Benchmark. | |
| This script is also responsible for converting SPIR-V shaders to WGSL using Tint. | |
| Usage: | |
| generate_benchmark_inputs.py header <build_dir> <header_path> | |
| generate_benchmark_inputs.py wgsl <tint> [--check-stale] | |
| """ | |
| import argparse | |
| import filecmp | |
| import subprocess | |
| import sys | |
| import tempfile | |
| from os import path | |
| # The list of benchmark inputs. | |
| kBenchmarkFiles = [ | |
| "test/tint/benchmark/atan2-const-eval.wgsl", | |
| "test/tint/benchmark/cluster-lights.wgsl", | |
| "test/tint/benchmark/metaball-isosurface.wgsl", | |
| "test/tint/benchmark/particles.wgsl", | |
| "test/tint/benchmark/shadow-fragment.wgsl", | |
| "test/tint/benchmark/skinned-shadowed-pbr-fragment.wgsl", | |
| "test/tint/benchmark/skinned-shadowed-pbr-vertex.wgsl", | |
| "third_party/benchmark_shaders/unity_boat_attack/unity_webgpu_000002778DE78280.cs.spv", | |
| "third_party/benchmark_shaders/unity_boat_attack/unity_webgpu_000002778DE78280.cs.wgsl", | |
| "third_party/benchmark_shaders/unity_boat_attack/unity_webgpu_000002778F740030.fs.spv", | |
| "third_party/benchmark_shaders/unity_boat_attack/unity_webgpu_000002778F740030.fs.wgsl", | |
| "third_party/benchmark_shaders/unity_boat_attack/unity_webgpu_0000017E9E2D81A0.vs.spv", | |
| "third_party/benchmark_shaders/unity_boat_attack/unity_webgpu_0000017E9E2D81A0.vs.wgsl", | |
| ] | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| subparsers = parser.add_subparsers(required=True) | |
| parser_gen_header = subparsers.add_parser('header', | |
| help='generate benchmark header') | |
| parser_gen_header.add_argument('build_dir_path') | |
| parser_gen_header.add_argument('header_output_path') | |
| parser_gen_header.set_defaults(func=generate_header) | |
| parser_gen_wgsl = subparsers.add_parser( | |
| 'wgsl', help='generate WGSL for SPIR-V inputs') | |
| parser_gen_wgsl.add_argument('--check-stale', action='store_true') | |
| parser_gen_wgsl.add_argument('tint') | |
| parser_gen_wgsl.set_defaults(func=generate_wgsl) | |
| args = parser.parse_args() | |
| args.func(args) | |
| def generate_wgsl(args): | |
| script_dir = path.dirname(path.realpath(__file__)) | |
| base_dir = script_dir + '/../../../../' | |
| with tempfile.TemporaryDirectory() as tmpdir: | |
| # Convert every SPIR-V benchmark to WGSL. | |
| for f in kBenchmarkFiles: | |
| if not f.endswith('.spv'): | |
| continue | |
| # Generate to a temporary file if we are only checking freshness. | |
| spv_path = base_dir + '/' + f | |
| tmp_wgsl_path = tmpdir + '/tmp.wgsl' | |
| final_wgsl_path = spv_path + '.wgsl' | |
| wgsl_path = tmp_wgsl_path if args.check_stale else final_wgsl_path | |
| tint_args = [ | |
| args.tint, '-o', wgsl_path, '--format', 'wgsl', spv_path, | |
| '--allow-non-uniform-derivatives' | |
| ] | |
| subprocess.run(tint_args, check=True) | |
| # Rewrite the file without CRLF line endings. | |
| with open(wgsl_path, 'r') as file: | |
| wgsl = file.read() | |
| with open(wgsl_path, 'w', newline='\n') as file: | |
| file.write(wgsl) | |
| # Check if the generated content is different to the current file. | |
| if args.check_stale: | |
| if not filecmp.cmp( | |
| tmp_wgsl_path, final_wgsl_path, shallow=False): | |
| print(f'{final_wgsl_path} is stale') | |
| print() | |
| import difflib | |
| with open(final_wgsl_path) as f: | |
| orig_lines = f.readlines() | |
| with open(tmp_wgsl_path) as f: | |
| gen_lines = f.readlines() | |
| for line in difflib.unified_diff(orig_lines, | |
| gen_lines, | |
| fromfile=final_wgsl_path, | |
| tofile=tmp_wgsl_path): | |
| print(line, end='') | |
| print(''' | |
| ================================ | |
| To regenerate, run: | |
| python3 src/tint/cmd/bench/generate_benchmark_inputs.py wgsl /path/to/tint.exe | |
| ================================ | |
| ''') | |
| sys.exit(1) | |
| if args.check_stale: | |
| print('All generated WGSL files are up-to-date.') | |
| def generate_header(args): | |
| script_dir = path.dirname(path.realpath(__file__)) | |
| base_dir = script_dir + '/../../../../' | |
| full_path_to_header = args.build_dir_path + '/' + args.header_output_path | |
| # Generate the header file. | |
| with open(full_path_to_header, 'w', newline='\n') as output: | |
| print('''// AUTOMATICALLY GENERATED, DO NOT MODIFY DIRECTLY. | |
| #ifndef SRC_TINT_CMD_BENCH_INPUTS_BENCH_H_ | |
| #define SRC_TINT_CMD_BENCH_INPUTS_BENCH_H_ | |
| #include <string> | |
| #include <unordered_map> | |
| // clang-format off | |
| namespace tint::bench { | |
| [[clang::no_destroy]] | |
| const std::unordered_map<std::string, std::string> kBenchmarkInputs = {''', | |
| file=output) | |
| # Add an entry to the array for each benchmark. | |
| for f in kBenchmarkFiles: | |
| fullpath = base_dir + '/' + f | |
| if f.endswith('.spv'): | |
| fullpath += '.wgsl' | |
| # Load the WGSL shader and emit it as a char initializer list. | |
| with open(fullpath, 'rb') as input: | |
| print(f' {{"{f}", {{', file=output, end='') | |
| i = 0 | |
| for char in input.read(): | |
| if char == ord('\r'): | |
| # Skip carriage return to make output consistent across platforms. | |
| continue | |
| if (i % 16) == 0: | |
| print('\n ', file=output, end='') | |
| print(' ' + str(char), file=output, end=',') | |
| i += 1 | |
| print(f'}}}},', file=output) | |
| print('};', file=output) | |
| print('', file=output) | |
| # Define the macro that registers each of the inputs with Google Benchmark. | |
| print('#define TINT_BENCHMARK_PROGRAMS(FUNC) \\', file=output) | |
| for f in sorted(kBenchmarkFiles): | |
| name = f.split('/')[-1] | |
| print(f' BENCHMARK_CAPTURE(FUNC, {name}, "{f}"); \\', | |
| file=output) | |
| print(' TINT_REQUIRE_SEMICOLON', file=output) | |
| print('', file=output) | |
| print(''' | |
| } // namespace tint::bench | |
| // clang-format on | |
| #endif // SRC_TINT_CMD_BENCH_INPUTS_BENCH_H_''', | |
| file=output) | |
| # Generate a depfile. | |
| with open(full_path_to_header + '.d', 'w') as depfile: | |
| print(args.header_output_path + ": \\", file=depfile) | |
| for f in kBenchmarkFiles: | |
| fullpath = base_dir + '/' + f | |
| if f.endswith('.spv'): | |
| fullpath += '.wgsl' | |
| print("\t" + fullpath + " \\", file=depfile) | |
| if __name__ == "__main__": | |
| sys.exit(main()) |