• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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