1 /*
2 * Copyright © 2022 Imagination Technologies Ltd.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 */
23
24 #include "compiler/shader_enums.h"
25 #include "nir/nir.h"
26 #include "rogue.h"
27 #include "rogue_build_data.h"
28 #include "rogue_compiler.h"
29 #include "rogue_dump.h"
30 #include "util/os_file.h"
31 #include "util/ralloc.h"
32
33 #include <getopt.h>
34 #include <limits.h>
35 #include <stdbool.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39
40 /* Number of hex columns to dump before starting a new line. */
41 #define ARRAY_DUMP_COLS 16
42
43 /**
44 * \file compiler.c
45 *
46 * \brief Rogue offline compiler.
47 */
48
49 static const struct option cmdline_opts[] = {
50 /* Arguments. */
51 { "stage", required_argument, NULL, 's' },
52 { "file", required_argument, NULL, 'f' },
53 { "entry", required_argument, NULL, 'e' },
54
55 /* Options. */
56 { "help", no_argument, NULL, 'h' },
57 { "out", required_argument, NULL, 'o' },
58
59 { "dump-c-array", no_argument, NULL, 'c' },
60 { "dump-rogue", no_argument, NULL, 'r' },
61 { "dump-nir", no_argument, NULL, 'n' },
62
63 { NULL, 0, NULL, 0 },
64 };
65
66 struct compiler_opts {
67 gl_shader_stage stage;
68 char *file;
69 char *entry;
70 char *out_file;
71 bool dump_c_array;
72 bool dump_rogue;
73 bool dump_nir;
74 };
75
usage(const char *argv0)76 static void usage(const char *argv0)
77 {
78 /* clang-format off */
79 printf("Rogue offline compiler.\n");
80 printf("Usage: %s -s <stage> -f <file> [-e <entry>] [-o <file>] [-c] [-r] [-n] [-h]\n", argv0);
81 printf("\n");
82
83 printf("Required arguments:\n");
84 printf("\t-s, --stage <stage> Shader stage (supported options: frag, vert).\n");
85 printf("\t-f, --file <file> Shader SPIR-V filename.\n");
86 printf("\n");
87
88 printf("Options:\n");
89 printf("\t-h, --help Prints this help message.\n");
90 printf("\t-e, --entry <entry> Overrides the shader entry-point name (default: 'main').\n");
91 printf("\t-o, --out <file> Overrides the output filename (default: 'out.bin').\n");
92 printf("\n");
93
94 printf("\t-c, --dump-c-array Print the shader binary as a C byte array.\n");
95 printf("\t-r, --dump-rogue Prints the shader Rogue assembly.\n");
96 printf("\t-n, --dump-nir Prints the shader NIR.\n");
97 printf("\n");
98 /* clang-format on */
99 }
100
parse_cmdline(int argc, char *argv[], struct compiler_opts *opts)101 static bool parse_cmdline(int argc, char *argv[], struct compiler_opts *opts)
102 {
103 int opt;
104 int longindex;
105
106 while (
107 (opt =
108 getopt_long(argc, argv, "crnhs:f:e:o:", cmdline_opts, &longindex)) !=
109 -1) {
110 switch (opt) {
111 case 'c':
112 opts->dump_c_array = true;
113 break;
114
115 case 'e':
116 if (opts->entry)
117 continue;
118
119 opts->entry = optarg;
120 break;
121
122 case 'f':
123 if (opts->file)
124 continue;
125
126 opts->file = optarg;
127 break;
128
129 case 'n':
130 opts->dump_nir = true;
131 break;
132
133 case 'o':
134 if (opts->out_file)
135 continue;
136
137 opts->out_file = optarg;
138 break;
139
140 case 'r':
141 opts->dump_rogue = true;
142 break;
143
144 case 's':
145 if (opts->stage != MESA_SHADER_NONE)
146 continue;
147
148 if (!strcmp(optarg, "frag"))
149 opts->stage = MESA_SHADER_FRAGMENT;
150 else if (!strcmp(optarg, "vert"))
151 opts->stage = MESA_SHADER_VERTEX;
152 else {
153 fprintf(stderr, "Invalid stage \"%s\".\n", optarg);
154 usage(argv[0]);
155 return false;
156 }
157
158 break;
159
160 case 'h':
161 default:
162 usage(argv[0]);
163 return false;
164 }
165 }
166
167 if (opts->stage == MESA_SHADER_NONE || !opts->file) {
168 fprintf(stderr,
169 "%s: --stage and --file are required arguments.\n",
170 argv[0]);
171 usage(argv[0]);
172 return false;
173 }
174
175 if (!opts->out_file)
176 opts->out_file = "out.bin";
177
178 if (!opts->entry)
179 opts->entry = "main";
180
181 return true;
182 }
183
main(int argc, char *argv[])184 int main(int argc, char *argv[])
185 {
186 /* Command-line options. */
187 /* N.B. MESA_SHADER_NONE != 0 */
188 struct compiler_opts opts = { .stage = MESA_SHADER_NONE, 0 };
189
190 /* Input file data. */
191 char *input_data;
192 size_t input_size;
193
194 /* Compiler context. */
195 struct rogue_compiler *compiler;
196
197 /* Multi-stage build context. */
198 struct rogue_build_ctx *ctx;
199
200 /* Output file. */
201 FILE *fp;
202 size_t bytes_written;
203
204 /* Parse command-line options. */
205 if (!parse_cmdline(argc, argv, &opts))
206 return 1;
207
208 /* Load SPIR-V input file. */
209 input_data = os_read_file(opts.file, &input_size);
210 if (!input_data) {
211 fprintf(stderr, "Failed to read file \"%s\".\n", opts.file);
212 return 1;
213 }
214
215 /* Create compiler context. */
216 compiler = rogue_compiler_create(NULL);
217 if (!compiler) {
218 fprintf(stderr, "Failed to set up compiler context.\n");
219 goto err_free_input;
220 }
221
222 ctx = rogue_create_build_context(compiler);
223 if (!ctx) {
224 fprintf(stderr, "Failed to set up build context.\n");
225 goto err_destroy_compiler;
226 }
227
228 /* SPIR-V -> NIR. */
229 ctx->nir[opts.stage] = rogue_spirv_to_nir(ctx,
230 opts.stage,
231 opts.entry,
232 input_size / sizeof(uint32_t),
233 (uint32_t *)input_data,
234 0,
235 NULL);
236 if (!ctx->nir[opts.stage]) {
237 fprintf(stderr, "Failed to translate SPIR-V input to NIR.\n");
238 goto err_free_build_context;
239 }
240
241 /* Dump NIR shader. */
242 if (opts.dump_nir)
243 nir_print_shader(ctx->nir[opts.stage], stdout);
244
245 /* NIR -> Rogue. */
246 ctx->rogue[opts.stage] = rogue_nir_to_rogue(ctx, ctx->nir[opts.stage]);
247 if (!ctx->rogue[opts.stage]) {
248 fprintf(stderr, "Failed to translate NIR input to Rogue.\n");
249 goto err_free_build_context;
250 }
251
252 /* Dump Rogue shader. */
253 if (opts.dump_rogue)
254 rogue_dump_shader(ctx->rogue[opts.stage], stdout);
255
256 /* Rogue -> Binary. */
257 ctx->binary[opts.stage] = rogue_to_binary(ctx, ctx->rogue[opts.stage]);
258 if (!ctx->binary[opts.stage]) {
259 fprintf(stderr, "Failed to translate Rogue to binary.\n");
260 goto err_free_build_context;
261 }
262
263 /* Dump binary as a C array. */
264 if (opts.dump_c_array) {
265 printf("uint8_t shader_bytes[%zu] = {", ctx->binary[opts.stage]->size);
266 for (size_t u = 0U; u < ctx->binary[opts.stage]->size; ++u) {
267 if (!(u % ARRAY_DUMP_COLS))
268 printf("\n\t");
269
270 printf("0x%02x, ", ctx->binary[opts.stage]->data[u]);
271 }
272 printf("\n};\n");
273 }
274
275 /* Write shader binary to disk. */
276 fp = fopen(opts.out_file, "wb");
277 if (!fp) {
278 fprintf(stderr, "Failed to open output file \"%s\".\n", opts.out_file);
279 goto err_free_build_context;
280 }
281
282 bytes_written = fwrite(ctx->binary[opts.stage]->data,
283 1,
284 ctx->binary[opts.stage]->size,
285 fp);
286 if (bytes_written != ctx->binary[opts.stage]->size) {
287 fprintf(
288 stderr,
289 "Failed to write to output file \"%s\" (%zu bytes of %zu written).\n",
290 opts.out_file,
291 bytes_written,
292 ctx->binary[opts.stage]->size);
293 goto err_close_outfile;
294 }
295
296 /* Clean up. */
297 fclose(fp);
298 ralloc_free(ctx);
299 rogue_compiler_destroy(compiler);
300 free(input_data);
301
302 return 0;
303
304 err_close_outfile:
305 fclose(fp);
306 err_free_build_context:
307 ralloc_free(ctx);
308 err_destroy_compiler:
309 rogue_compiler_destroy(compiler);
310 err_free_input:
311 free(input_data);
312
313 return 1;
314 }
315