1#!/usr/bin/python3 2# Copyright 2019 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_mtl_internal_shaders.py: 7# Code generation for Metal backend's default shaders. 8# NOTE: don't run this script directly. Run scripts/run_code_generation.py. 9 10import json 11import os 12import subprocess 13import sys 14 15sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 16import angle_format 17import gen_angle_format_table 18 19configs = [{ 20 "sdk": "macosx", 21 "compile_flags": ["--std=macos-metal2.1", "-mmacosx-version-min=10.14"], 22 "variable_name": "gDefaultMetallib", 23 "header": "mtl_internal_shaders_macos_autogen.h" 24}, { 25 "sdk": "iphoneos", 26 "compile_flags": ["--std=ios-metal2.1", "-mios-version-min=12"], 27 "variable_name": "gDefaultMetallib", 28 "header": "mtl_internal_shaders_ios_autogen.h" 29}] 30 31metal_source_output_header = "mtl_internal_shaders_src_autogen.h" 32 33metal_shader_output_file = "mtl_internal_shaders_autogen.metal" 34 35template_header_boilerplate = """// GENERATED FILE - DO NOT EDIT. 36// Generated by {script_name} 37// 38// Copyright 2020 The ANGLE Project Authors. All rights reserved. 39// Use of this source code is governed by a BSD-style license that can be 40// found in the LICENSE file. 41// 42""" 43 44def gen_shader_enums_code(angle_formats): 45 46 code = """// This file is similar to src/libANGLE/renderer/FormatID_autogen.h but is used by Metal default 47// shaders instead of C++ code. 48// 49""" 50 51 code += "namespace rx\n" 52 code += "{\n" 53 code += "namespace mtl_shader\n" 54 code += "{\n" 55 code += "\n" 56 code += "namespace FormatID\n" 57 code += "{\n" 58 code += "enum\n" 59 code += "{\n" 60 code += gen_angle_format_table.gen_enum_string(angle_formats) + '\n' 61 code += "};\n\n" 62 code += "}\n" 63 code += "\n" 64 code += "}\n" 65 code += "}\n" 66 67 return code 68 69 70def find_clang(): 71 if os.name == 'nt': 72 binary = 'clang-cl.exe' 73 else: 74 binary = 'clang++' 75 76 clang = os.path.join('..', '..', '..', '..', '..', 'third_party', 'llvm-build', 77 'Release+Asserts', 'bin', binary) 78 79 if not os.path.isfile(clang): 80 xcrun_clang = subprocess.run(["xcrun", "-f", binary], stdout=subprocess.PIPE, text=True) 81 if xcrun_clang.returncode == 0: 82 clang = xcrun_clang.stdout.strip() 83 if (not os.path.isfile(clang)): 84 raise Exception('Cannot find clang') 85 86 return clang 87 88 89def metal_to_air(metal_src, sdk, compile_flags, dest_air_file): 90 temp_fname = 'temp_metal_src.metal' 91 with open(temp_fname, 'wt') as out_file: 92 out_file.write(metal_src.decode("utf-8")) 93 94 result = subprocess.run( 95 ["xcrun", "-sdk", sdk, "metal"] + compile_flags + ["-c", temp_fname, "-o", dest_air_file], 96 stdout=subprocess.PIPE, 97 text=True) 98 os.remove(temp_fname) 99 100 if result.returncode != 0: 101 raise Exception('Failed to compile metal to air: ' + result.stdout.strip()) 102 103 104def air_to_mtllib(src_air_file, sdk, dest_mtllib_file): 105 result = subprocess.run( 106 ["xcrun", "-sdk", sdk, "metallib", src_air_file, "-o", dest_mtllib_file], 107 stdout=subprocess.PIPE, 108 text=True) 109 if result.returncode != 0: 110 raise Exception('Failed to compile metal to air: ' + result.stdout.strip()) 111 112 113def metal_to_metallib(metal_src, sdk, compile_flags): 114 intermediate_air_file = "temp_metal.air" 115 metal_to_air(metal_src, sdk, compile_flags, intermediate_air_file) 116 117 intermediate_metallib_file = "temp_metal.metallib" 118 air_to_mtllib(intermediate_air_file, sdk, intermediate_metallib_file) 119 os.remove(intermediate_air_file) 120 121 with open(intermediate_metallib_file, 'rb') as f: 122 mtllib = f.read() 123 os.remove(intermediate_metallib_file) 124 125 return mtllib 126 127 128def generate_metallib_header(metal_src, sdk, compile_flags, variable_name, dest_header_file): 129 boilerplate_code = template_header_boilerplate.format( 130 script_name=os.path.basename(sys.argv[0])) 131 132 metallib_data = metal_to_metallib(metal_src, sdk, compile_flags) 133 134 with open(dest_header_file, 'wt') as out_file: 135 out_file.write(boilerplate_code) 136 out_file.write('\n') 137 out_file.write('// C++ string version of default shaders mtllib.\n\n') 138 out_file.write('\n\nstatic constexpr uint8_t ' + variable_name + '[] = {\n') 139 for byte in metallib_data: 140 out_file.write(f"{byte}, ") 141 out_file.write('\n') 142 out_file.write('};\n') 143 out_file.close() 144 145 146def generate_metal_autogen_header(dest_metal_header): 147 angle_to_gl = angle_format.load_inverse_table('../../angle_format_map.json') 148 shader_autogen_header = gen_shader_enums_code(angle_to_gl.keys()) 149 150 with open(dest_metal_header, 'wt') as out_file: 151 out_file.write(shader_autogen_header) 152 out_file.close() 153 154 155def generate_combined_metal_src(metal_src_files): 156 autogen_header_file = "format_autogen.h" 157 generate_metal_autogen_header(autogen_header_file) 158 159 clang = find_clang() 160 161 # Use clang to preprocess the combination source. "@@" token is used to prevent clang from 162 # expanding the preprocessor directive 163 temp_fname = 'temp_master_source.metal' 164 with open(temp_fname, 'wb') as temp_file: 165 for src_file in metal_src_files: 166 include_str = '#include "' + src_file + '" \n' 167 temp_file.write(include_str.encode('utf-8')) 168 169 args = [clang] 170 if not os.name == 'nt': 171 args += ['-xc++'] 172 args += ['-E', temp_fname] 173 174 combined_source = subprocess.check_output(args) 175 os.remove(temp_fname) 176 os.remove(autogen_header_file) 177 178 # Remove '@@' tokens 179 final_combined_src_string = combined_source.replace('@@'.encode('utf-8'), ''.encode('utf-8')) 180 181 return final_combined_src_string 182 183 184def generate_combined_metal_src_header(combined_metal_src, dest_header): 185 boilerplate_code = template_header_boilerplate.format( 186 script_name=os.path.basename(sys.argv[0])) 187 188 with open(dest_header, 'wt') as out_file: 189 out_file.write(boilerplate_code) 190 out_file.write('\n') 191 out_file.write('// C++ string version of combined Metal default shaders.\n\n') 192 out_file.write('\n\nstatic char gDefaultMetallibSrc[] = R"(\n') 193 out_file.write(combined_metal_src.decode("utf-8")) 194 out_file.write('\n') 195 out_file.write(')";\n') 196 out_file.close() 197 198 199def generate_combined_metal_shader_file(combined_metal_src, dest_file): 200 boilerplate_code = template_header_boilerplate.format( 201 script_name=os.path.basename(sys.argv[0])) 202 203 with open(dest_file, 'wt') as out_file: 204 out_file.write(boilerplate_code) 205 out_file.write('\n') 206 out_file.write('// Combined Metal default shaders.\n\n') 207 out_file.write(combined_metal_src.decode("utf-8")) 208 out_file.write('\n') 209 out_file.close() 210 211 212def main(): 213 angle_format_script_files = [ 214 '../../angle_format_map.json', '../../angle_format.py', '../../gen_angle_format_table.py' 215 ] 216 src_files = [ 217 'blit.metal', 'clear.metal', 'gen_indices.metal', 'gen_mipmap.metal', 'copy_buffer.metal', 218 'visibility.metal', 'rewrite_indices.metal' 219 ] 220 221 # auto_script parameters. 222 if len(sys.argv) > 1: 223 inputs = angle_format_script_files + src_files + ['common.h', 'constants.h'] 224 outputs = [config["header"] for config in configs 225 ] + [metal_source_output_header, metal_shader_output_file] 226 227 if sys.argv[1] == 'inputs': 228 print(','.join(inputs)) 229 elif sys.argv[1] == 'outputs': 230 print(','.join(outputs)) 231 else: 232 print('Invalid script parameters') 233 return 1 234 return 0 235 236 os.chdir(sys.path[0]) 237 238 combined_metal_src = generate_combined_metal_src(src_files) 239 240 generate_combined_metal_src_header(combined_metal_src, metal_source_output_header) 241 242 # Write also the shader text. At the time of writing WebKit compilation would use this. 243 # The build system should take `metal_shader_output_file` and produce 244 # libANGLE/renderer/metal/shaders/mtl_internal_shaders_metallib.h in some part 245 # of the include path before the real libANGLE file. 246 generate_combined_metal_shader_file(combined_metal_src, metal_shader_output_file) 247 248 for config in configs: 249 generate_metallib_header(combined_metal_src, config["sdk"], config["compile_flags"], 250 config["variable_name"], config["header"]) 251 252 253if __name__ == '__main__': 254 sys.exit(main()) 255