• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
42
43def get_args() -> 'Arguments':
44    parser = argparse.ArgumentParser()
45    parser.add_argument('input', help="Name of input file.")
46    parser.add_argument('output', help="Name of output file.")
47    parser.add_argument('glslang', help="path to glslangValidator")
48
49    parser.add_argument("--create-entry",
50                        dest="create_entry",
51                        help="Create a new entry point and put to the end of a file.")
52
53    parser.add_argument('--glsl-version',
54                        dest="glsl_ver",
55                        choices=['100', '110', '120', '130', '140', '150', '300es', '310es', '330', '400', '410', '420', '430', '440', '450', '460'],
56                        help="Override GLSL #version declaration in source.")
57
58    parser.add_argument("-Olib",
59                        action='store_true',
60                        help="Any optimizations are disabled and unused functions are saved.")
61
62    parser.add_argument("--extra-flags",
63                        dest="extra",
64                        help="Pass additional flags to glslangValidator.")
65
66    parser.add_argument("--vn",
67                        dest="vn",
68                        required=True,
69                        help="Variable name. Creates a C header file that contains a uint32_t array.")
70
71    parser.add_argument("--stage",
72                        default="vert",
73                        choices=['vert', 'tesc', 'tese', 'geom', 'frag', 'comp'],
74                        help="Uses specified stage rather than parsing the file extension")
75
76    parser.add_argument("-I",
77                        dest="includes",
78                        default=[],
79                        action='append',
80                        help="Include directory")
81
82    parser.add_argument("-D",
83                        dest="defines",
84                        default=[],
85                        action='append',
86                        help="Defines")
87
88    args = parser.parse_args()
89    return args
90
91
92def create_include_guard(lines: T.List[str], filename: str) -> T.List[str]:
93    filename = filename.replace('.', '_')
94    upper_name = filename.upper()
95
96    guard_head = [f"#ifndef {upper_name}\n",
97                  f"#define {upper_name}\n"]
98    guard_tail = [f"\n#endif // {upper_name}\n"]
99
100    # remove '#pragma once'
101    for idx, l in enumerate(lines):
102        if '#pragma once' in l:
103            lines.pop(idx)
104            break
105
106    return guard_head + lines + guard_tail
107
108
109def convert_to_static_variable(lines: T.List[str], varname: str) -> T.List[str]:
110    for idx, l in enumerate(lines):
111        if varname in l:
112            lines[idx] = f"static {l}"
113            return lines
114    raise RuntimeError(f'Did not find {varname}, this is unexpected')
115
116
117def override_version(lines: T.List[str], glsl_version: str) -> T.List[str]:
118    for idx, l in enumerate(lines):
119        if '#version ' in l:
120            lines[idx] = f"#version {glsl_version}\n"
121            return lines
122    raise RuntimeError('Did not find #version directive, this is unexpected')
123
124
125def postprocess_file(args: 'Arguments') -> None:
126    with open(args.output, "r") as r:
127        lines = r.readlines()
128
129    # glslangValidator creates a variable without the static modifier.
130    lines = convert_to_static_variable(lines, args.vn)
131
132    # '#pragma once' is unstandardised.
133    lines = create_include_guard(lines, os.path.basename(r.name))
134
135    with open(args.output, "w") as w:
136        w.writelines(lines)
137
138
139def preprocess_file(args: 'Arguments', origin_file: T.TextIO, directory: os.PathLike) -> str:
140    with open(os.path.join(directory, os.path.basename(origin_file.name)), "w") as copy_file:
141        lines = origin_file.readlines()
142
143        if args.create_entry is not None:
144            lines.append(f"\nvoid {args.create_entry}() {{}}\n")
145
146        if args.glsl_ver is not None:
147            override_version(lines, args.glsl_ver)
148
149        copy_file.writelines(lines)
150
151    return copy_file.name
152
153
154def process_file(args: 'Arguments') -> None:
155    with open(args.input, "r") as infile:
156        copy_file = preprocess_file(args, infile,
157                                    os.path.dirname(args.output))
158
159    cmd_list = [args.glslang]
160
161    if args.Olib:
162        cmd_list.append("--keep-uncalled")
163
164    if args.vn is not None:
165        cmd_list.extend(["--variable-name", args.vn])
166
167    if args.extra is not None:
168        cmd_list.append(args.extra)
169
170    if args.create_entry is not None:
171        cmd_list.extend(["--entry-point", args.create_entry])
172
173    for f in args.includes:
174        cmd_list.append('-I' + f)
175
176    for d in args.defines:
177        cmd_list.append('-D' + d)
178
179    cmd_list.extend([
180        '-V',
181        '-o', args.output,
182        '-S', args.stage,
183        copy_file,
184    ])
185
186    ret = subprocess.run(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30)
187    if ret.returncode != 0:
188        print(ret.stdout)
189        print(ret.stderr, file=sys.stderr)
190        sys.exit(1)
191
192    if args.vn is not None:
193        postprocess_file(args)
194
195    if args.create_entry is not None:
196        os.remove(copy_file)
197
198
199def main() -> None:
200    args = get_args()
201    process_file(args)
202
203
204if __name__ == "__main__":
205    main()
206