xref: /aosp_15_r20/external/mesa3d/src/util/glsl2spirv.py (revision 6104692788411f58d303aa86923a9ff6ecaded22)
1# Copyright © 2022 Intel Corporation
2#
3# Permission is hereby granted, free of charge, to any person obtaining a
4# copy of this software and associated documentation files (the "Software"),
5# to deal in the Software without restriction, including without limitation
6# the rights to use, copy, modify, merge, publish, distribute, sublicense,
7# and/or sell copies of the Software, and to permit persons to whom the
8# Software is furnished to do so, subject to the following conditions:
9#
10# The above copyright notice and this permission notice (including the next
11# paragraph) shall be included in all copies or substantial portions of the
12# Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20# IN THE SOFTWARE.
21
22# Converts GLSL shader to SPIR-V library
23
24import argparse
25import subprocess
26import sys
27import os
28import typing as T
29
30if T.TYPE_CHECKING:
31    class Arguments(T.Protocol):
32        input: str
33        output: str
34        glslang: str
35        create_entry: T.Optional[str]
36        glsl_ver: T.Optional[str]
37        Olib: bool
38        extra: T.Optional[str]
39        vn: str
40        stage: str
41        includes: T.List[str]
42        defines: T.List[str]
43        depfile: T.Optional[str]
44
45
46def get_args() -> 'Arguments':
47    parser = argparse.ArgumentParser()
48    parser.add_argument('input', help="Name of input file.")
49    parser.add_argument('output', help="Name of output file.")
50    parser.add_argument('glslang', help="path to glslangValidator")
51
52    parser.add_argument("--create-entry",
53                        dest="create_entry",
54                        help="Create a new entry point and put to the end of a file.")
55
56    parser.add_argument('--glsl-version',
57                        dest="glsl_ver",
58                        choices=['100', '110', '120', '130', '140', '150', '300es', '310es', '330', '400', '410', '420', '430', '440', '450', '460'],
59                        help="Override GLSL #version declaration in source.")
60
61    parser.add_argument("-Olib",
62                        action='store_true',
63                        help="Any optimizations are disabled and unused functions are saved.")
64
65    parser.add_argument("--extra-flags",
66                        dest="extra",
67                        help="Pass additional flags to glslangValidator.")
68
69    parser.add_argument("--vn",
70                        dest="vn",
71                        required=True,
72                        help="Variable name. Creates a C header file that contains a uint32_t array.")
73
74    parser.add_argument("--stage",
75                        default="vert",
76                        choices=['vert', 'tesc', 'tese', 'geom', 'frag', 'comp'],
77                        help="Uses specified stage rather than parsing the file extension")
78
79    parser.add_argument("-I",
80                        dest="includes",
81                        default=[],
82                        action='append',
83                        help="Include directory")
84
85    parser.add_argument("-D",
86                        dest="defines",
87                        default=[],
88                        action='append',
89                        help="Defines")
90
91    parser.add_argument('--depfile',
92                        dest="depfile",
93                        default=None,
94                        action='store',
95                        help='Where glslangValidator should write its depfile, if unset no depfile will be written.')
96
97    args = parser.parse_args()
98    return args
99
100
101def create_include_guard(lines: T.List[str], filename: str) -> T.List[str]:
102    filename = filename.replace('.', '_')
103    upper_name = filename.upper()
104
105    guard_head = [f"#ifndef {upper_name}\n",
106                  f"#define {upper_name}\n"]
107    guard_tail = [f"\n#endif // {upper_name}\n"]
108
109    # remove '#pragma once'
110    for idx, l in enumerate(lines):
111        if '#pragma once' in l:
112            lines.pop(idx)
113            break
114
115    return guard_head + lines + guard_tail
116
117
118def convert_to_static_variable(lines: T.List[str], varname: str) -> T.List[str]:
119    for idx, l in enumerate(lines):
120        if varname in l:
121            lines[idx] = f"static {l}"
122            return lines
123    raise RuntimeError(f'Did not find {varname}, this is unexpected')
124
125
126def override_version(lines: T.List[str], glsl_version: str) -> T.List[str]:
127    for idx, l in enumerate(lines):
128        if '#version ' in l:
129            lines[idx] = f"#version {glsl_version}\n"
130            return lines
131    raise RuntimeError('Did not find #version directive, this is unexpected')
132
133
134def postprocess_file(args: 'Arguments') -> None:
135    with open(args.output, "r") as r:
136        lines = r.readlines()
137
138    # glslangValidator creates a variable without the static modifier.
139    lines = convert_to_static_variable(lines, args.vn)
140
141    # '#pragma once' is unstandardised.
142    lines = create_include_guard(lines, os.path.basename(r.name))
143
144    with open(args.output, "w") as w:
145        w.writelines(lines)
146
147
148def preprocess_file(args: 'Arguments', origin_file: T.TextIO, directory: os.PathLike, filemap: T.Dict[str, str]) -> str:
149    if args.create_entry is None and args.glsl_ver is None:
150        return origin_file.name
151
152    with open(os.path.join(directory, os.path.basename(origin_file.name)), "w") as copy_file:
153        lines = origin_file.readlines()
154
155        if args.create_entry is not None:
156            lines.append(f"\nvoid {args.create_entry}() {{}}\n")
157
158        if args.glsl_ver is not None:
159            override_version(lines, args.glsl_ver)
160
161        copy_file.writelines(lines)
162
163    filemap[copy_file.name] = origin_file.name
164    return copy_file.name
165
166
167def fixup_depfile(depfile: str, filemap: T.Mapping[str, str]) -> None:
168    """Replace generated file references in the depfile with their source.
169    """
170    depends: T.List[str] = []
171    out: T.Optional[str] = None
172    with open(depfile, 'r') as f:
173        for line in f.readlines():
174            if ':' in line:
175                out, line = line.split(':', 1)
176            depends.extend(l.strip() for l in line.split())
177
178    with open(depfile, 'w') as f:
179        f.write(out)
180        f.write(': ')
181        for dep in depends:
182            f.write(filemap.get(dep, dep))
183            f.write(' ')
184        f.write('\n')
185
186
187def process_file(args: 'Arguments') -> None:
188    filemap: T.Dict[str, str] = {}
189
190    with open(args.input, "r") as infile:
191        copy_file = preprocess_file(
192            args, infile, os.path.dirname(args.output), filemap)
193
194    cmd_list = [args.glslang]
195
196    if args.Olib:
197        cmd_list.append("--keep-uncalled")
198
199    if args.vn is not None:
200        cmd_list.extend(["--variable-name", args.vn])
201
202    if args.extra is not None:
203        cmd_list.append(args.extra)
204
205    if args.create_entry is not None:
206        cmd_list.extend(["--entry-point", args.create_entry])
207
208    if args.depfile is not None:
209        cmd_list.extend(['--depfile', args.depfile])
210
211    for f in args.includes:
212        cmd_list.append('-I' + f)
213    for d in args.defines:
214        cmd_list.append('-D' + d)
215
216    cmd_list.extend([
217        '-V',
218        '-o', args.output,
219        '-S', args.stage,
220        copy_file,
221    ])
222
223    ret = subprocess.run(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30)
224    if ret.returncode != 0:
225        print(ret.stdout)
226        print(ret.stderr, file=sys.stderr)
227        sys.exit(1)
228
229    if args.vn is not None:
230        postprocess_file(args)
231
232    if args.create_entry is not None:
233        os.remove(copy_file)
234
235    if args.depfile is not None:
236        fixup_depfile(args.depfile, filemap)
237
238
239def main() -> None:
240    args = get_args()
241    process_file(args)
242
243
244if __name__ == "__main__":
245    main()
246