1 // Copyright 2019 The Shaderc Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <fstream>
16 #include <iomanip>
17 #include <iostream>
18 #include <sstream>
19 #include <vector>
20
21 #include "libshaderc_util/args.h"
22 #include "libshaderc_util/string_piece.h"
23 #include "shaderc/env.h"
24 #include "spvc/spvc.hpp"
25 #include "shaderc/status.h"
26 #include "spirv-tools/libspirv.h"
27
28 using shaderc_util::string_piece;
29
30 namespace {
31
32 // Prints the help message.
PrintHelp(std::ostream * out)33 void PrintHelp(std::ostream* out) {
34 *out << R"(spvc - Compile SPIR-V into GLSL/HLSL/MSL.
35
36 Usage: spvc [options] file
37
38 An input file of - represents standard input.
39
40 Options:
41 --help Display available options.
42 -v Display compiler version information.
43 -o <output file> '-' means standard output.
44 --no-validate Disable validating input and intermediate source.
45 Validation is by default enabled.
46 --no-optimize Disable optimizing input and intermediate source.
47 Optimization is by default enabled.
48 --source-env=<env> Execution environment of the input source.
49 <env> is vulkan1.0 (the default), vulkan1.1,
50 or webgpu0
51 --entry=<name> Specify entry point.
52 --language=<lang> Specify output language.
53 <lang> is glsl (the default), msl or hlsl.
54 --glsl-version=<ver> Specify GLSL output language version, e.g. 100
55 Default is 450 if not detected from input.
56 --msl-version=<ver> Specify MSL output language version, e.g. 100
57 Default is 10200.
58 --target-env=<env> Target intermediate execution environment to
59 transform the source to before cross-compiling.
60 Defaults to the value set for source-env.
61 <env> must be one of the legal values for source-env.
62
63 If target-env and source-env are the same, then no
64 transformation is performed.
65 If there is no defined transformation between source
66 and target the operation will fail.
67 Defined transforms:
68 webgpu0 -> vulkan1.1
69 vulkan1.1 -> webgpu0
70 --use-spvc-parser Use the built in parser for generating spirv-cross IR
71 instead of spirv-cross.
72
73
74 The following flags behave as in spirv-cross:
75
76 --remove-unused-variables
77 --vulkan-semantics
78 --separate-shader-objects
79 --flatten-ubo
80 --flatten-multidimensional-arrays
81 --es
82 --no-es
83 --glsl-emit-push-constant-as-ubo
84 --msl-swizzle-texture-samples
85 --msl-platform=ios|macos
86 --msl-pad-fragment-output
87 --msl-capture-output
88 --msl-domain-lower-left
89 --msl-argument-buffers
90 --msl-discrete-descriptor-set=<number>
91 --emit-line-directives
92 --hlsl-enable-compat
93 --shader-model=<model>
94 )";
95 }
96
97 const char kBuildVersion[] = ""
98 // TODO(bug 653): #include "build-version.inc"
99 ;
100
ReadFile(const std::string & path,std::vector<uint32_t> * out)101 bool ReadFile(const std::string& path, std::vector<uint32_t>* out) {
102 FILE* file =
103 path == "-" ? freopen(nullptr, "rb", stdin) : fopen(path.c_str(), "rb");
104 if (!file) {
105 std::cerr << "Failed to open SPIR-V file: " << path << std::endl;
106 return false;
107 }
108
109 fseek(file, 0, SEEK_END);
110 out->resize(ftell(file) / sizeof((*out)[0]));
111 rewind(file);
112
113 if (fread(out->data(), sizeof((*out)[0]), out->size(), file) != out->size()) {
114 std::cerr << "Failed to read SPIR-V file: " << path << std::endl;
115 out->clear();
116 return false;
117 }
118
119 fclose(file);
120 return true;
121 }
122
StringPieceToEnvEnum(const string_piece & str,shaderc_spvc_spv_env * env)123 bool StringPieceToEnvEnum(const string_piece& str, shaderc_spvc_spv_env* env) {
124 if (!env) {
125 std::cerr << "spvc: error: internal error calling StringPieceToEnvEnum"
126 << std::endl;
127 return false;
128 }
129
130 if (str == "vulkan1.0") {
131 *env = shaderc_spvc_spv_env_vulkan_1_0;
132 } else if (str == "vulkan1.1") {
133 *env = shaderc_spvc_spv_env_vulkan_1_1;
134 } else if (str == "webgpu0") {
135 *env = shaderc_spvc_spv_env_webgpu_0;
136 } else {
137 std::cerr << "spvc: error: invalid value '" << str
138 << "' in --source-env= or --target-env=" << std::endl;
139 return false;
140 }
141 return true;
142 }
143
144 } // anonymous namespace
145
main(int argc,char ** argv)146 int main(int argc, char** argv) {
147 shaderc_spvc::Context context;
148 std::vector<uint32_t> input;
149 std::vector<uint32_t> msl_discrete_descriptor;
150 string_piece output_path;
151 string_piece output_language;
152 string_piece source_str = "vulkan1.0";
153 string_piece target_str = "";
154
155 // Scan and find any source and target env flags, since we need to know those
156 // to create options.
157 for (int i = 1; i < argc; ++i) {
158 const string_piece arg = argv[i];
159 if (arg.starts_with("--source-env=")) {
160 string_piece env;
161 shaderc_util::GetOptionArgument(argc, argv, &i, "--source-env=", &env);
162 source_str = env;
163 } else if (arg.starts_with("--target-env=")) {
164 string_piece env;
165 shaderc_util::GetOptionArgument(argc, argv, &i, "--target-env=", &env);
166 target_str = env;
167 }
168 }
169
170 if (target_str == "") {
171 target_str = source_str;
172 }
173
174 shaderc_spvc_spv_env source_env;
175 if (!StringPieceToEnvEnum(source_str, &source_env)) {
176 return 1;
177 }
178
179 shaderc_spvc_spv_env target_env;
180 if (!StringPieceToEnvEnum(target_str, &target_env)) {
181 return 1;
182 }
183
184 shaderc_spvc::CompileOptions options(source_env, target_env);
185 for (int i = 1; i < argc; ++i) {
186 const string_piece arg = argv[i];
187 if (arg == "--help") {
188 ::PrintHelp(&std::cout);
189 return 0;
190 } else if (arg == "-v") {
191 std::cout << kBuildVersion << std::endl;
192 std::cout << "Target: " << spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_0)
193 << std::endl;
194 return 0;
195 } else if (arg.starts_with("-o")) {
196 if (!shaderc_util::GetOptionArgument(argc, argv, &i, "-o",
197 &output_path)) {
198 std::cerr
199 << "spvc: error: argument to '-o' is missing (expected 1 value)"
200 << std::endl;
201 return 1;
202 }
203 } else if (arg.starts_with("--entry=")) {
204 string_piece entry_point;
205 shaderc_util::GetOptionArgument(argc, argv, &i, "--entry=", &entry_point);
206 options.SetEntryPoint(entry_point.data());
207 } else if (arg.starts_with("--glsl-version=")) {
208 string_piece version_str;
209 shaderc_util::GetOptionArgument(argc, argv, &i,
210 "--glsl-version=", &version_str);
211 uint32_t version_num;
212 if (!shaderc_util::ParseUint32(version_str.str(), &version_num)) {
213 std::cerr << "spvc: error: invalid value '" << version_str
214 << "' in --glsl-version=" << std::endl;
215 return 1;
216 }
217 options.SetGLSLLanguageVersion(version_num);
218 } else if (arg.starts_with("--msl-version=")) {
219 string_piece version_str;
220 shaderc_util::GetOptionArgument(argc, argv, &i,
221 "--msl-version=", &version_str);
222 uint32_t version_num;
223 if (!shaderc_util::ParseUint32(version_str.str(), &version_num)) {
224 std::cerr << "spvc: error: invalid value '" << version_str
225 << "' in --msl-version=" << std::endl;
226 return 1;
227 }
228 options.SetMSLLanguageVersion(version_num);
229 } else if (arg.starts_with("--language=")) {
230 shaderc_util::GetOptionArgument(argc, argv, &i,
231 "--language=", &output_language);
232 if (!(output_language == "glsl" || output_language == "msl" ||
233 output_language == "hlsl" || output_language == "vulkan")) {
234 std::cerr << "spvc: error: invalid value '" << output_language
235 << "' in --language=" << std::endl;
236 return 1;
237 }
238 } else if (arg == "--remove-unused-variables") {
239 options.SetRemoveUnusedVariables(true);
240 } else if (arg == "--no-validate"){
241 options.SetValidate(false);
242 } else if (arg == "--no-optimize"){
243 options.SetOptimize(false);
244 } else if (arg == "--robust-buffer-access-pass"){
245 options.SetRobustBufferAccessPass(true);
246 } else if (arg == "--vulkan-semantics") {
247 options.SetVulkanSemantics(true);
248 } else if (arg == "--separate-shader-objects") {
249 options.SetSeparateShaderObjects(true);
250 } else if (arg == "--flatten-ubo") {
251 options.SetFlattenUbo(true);
252 } else if (arg == "--flatten-multidimensional-arrays") {
253 options.SetFlattenMultidimensionalArrays(true);
254 } else if (arg == "--es") {
255 options.SetES(true);
256 } else if (arg == "--no-es") {
257 options.SetES(false);
258 } else if (arg == "--hlsl-enable-compat") {
259 options.SetHLSLPointSizeCompat(true);
260 options.SetHLSLPointCoordCompat(true);
261 } else if (arg == "--glsl-emit-push-constant-as-ubo") {
262 options.SetGLSLEmitPushConstantAsUBO(true);
263 } else if (arg == "--msl-swizzle-texture-samples") {
264 options.SetMSLSwizzleTextureSamples(true);
265 } else if (arg.starts_with("--msl-platform=")) {
266 string_piece platform;
267 GetOptionArgument(argc, argv, &i, "--msl-platform=", &platform);
268 if (platform == "ios") {
269 options.SetMSLPlatform(shaderc_spvc_msl_platform_ios);
270 } else if (platform == "macos") {
271 options.SetMSLPlatform(shaderc_spvc_msl_platform_macos);
272 } else {
273 std::cerr << "spvc: error: invalid value '" << platform
274 << "' in --msl-platform=" << std::endl;
275 return 1;
276 }
277 } else if (arg == "--msl-pad-fragment-output") {
278 options.SetMSLPadFragmentOutput(true);
279 } else if (arg == "--msl-capture-output") {
280 options.SetMSLCapture(true);
281 } else if (arg == "--msl-domain-lower-left") {
282 options.SetMSLDomainLowerLeft(true);
283 } else if (arg == "--msl-argument-buffers") {
284 options.SetMSLArgumentBuffers(true);
285 } else if (arg.starts_with("--msl-discrete-descriptor-set=")) {
286 string_piece descriptor_str;
287 GetOptionArgument(argc, argv, &i,
288 "--msl-discrete-descriptor-set=", &descriptor_str);
289 uint32_t descriptor_num;
290 if (!shaderc_util::ParseUint32(descriptor_str.str(), &descriptor_num)) {
291 std::cerr << "spvc: error: invalid value '" << descriptor_str
292 << "' in --msl-discrete-descriptor-set=" << std::endl;
293 return 1;
294 }
295 msl_discrete_descriptor.push_back(descriptor_num);
296 } else if (arg == "--emit-line-directives") {
297 options.SetEmitLineDirectives(true);
298 } else if (arg.starts_with("--shader-model=")) {
299 string_piece shader_model_str;
300 shaderc_util::GetOptionArgument(argc, argv, &i,
301 "--shader-model=", &shader_model_str);
302 uint32_t shader_model_num;
303 if (!shaderc_util::ParseUint32(shader_model_str.str(),
304 &shader_model_num)) {
305 std::cerr << "spvc: error: invalid value '" << shader_model_str
306 << "' in --shader-model=" << std::endl;
307 return 1;
308 }
309 options.SetHLSLShaderModel(shader_model_num);
310 } else if (arg.starts_with("--source-env=")) {
311 // Parsed in the previous iteration
312 } else if (arg.starts_with("--target-env=")) {
313 // Parsed in the previous iteration
314 } else if (arg == "--use-spvc-parser") {
315 context.SetUseSpvcParser(true);
316 } else {
317 if (!ReadFile(arg.str(), &input)) {
318 std::cerr << "spvc: error: could not read file" << std::endl;
319 return 1;
320 }
321 }
322 }
323
324 options.SetMSLDiscreteDescriptorSets(msl_discrete_descriptor);
325
326 shaderc_spvc::CompilationResult result;
327 shaderc_spvc_status status = shaderc_spvc_status_configuration_error;
328
329 if (output_language == "glsl") {
330 status = context.InitializeForGlsl((const uint32_t*)input.data(),
331 input.size(), options);
332 } else if (output_language == "msl") {
333 status = context.InitializeForMsl((const uint32_t*)input.data(),
334 input.size(), options);
335 } else if (output_language == "hlsl") {
336 status = context.InitializeForHlsl((const uint32_t*)input.data(),
337 input.size(), options);
338 } else if (output_language == "vulkan") {
339 status = context.InitializeForVulkan((const uint32_t*)input.data(),
340 input.size(), options);
341 } else {
342 std::cerr << "Attempted to output to unknown language: " << output_language
343 << std::endl;
344 return 1;
345 }
346
347 if (status == shaderc_spvc_status_success)
348 status = context.CompileShader(&result);
349
350 switch (status) {
351 case shaderc_spvc_status_validation_error: {
352 std::cerr << "validation failed:\n" << context.GetMessages() << std::endl;
353 return 1;
354 }
355
356 case shaderc_spvc_status_success: {
357 const char* path = output_path.data();
358 if (output_language != "vulkan") {
359 std::string string_output;
360 result.GetStringOutput(&string_output);
361 if (path && strcmp(path, "-")) {
362 std::basic_ofstream<char>(path) << string_output;
363 } else {
364 std::cout << string_output;
365 }
366 } else {
367 if (path && strcmp(path, "-")) {
368 std::vector<uint32_t> binary_output;
369 result.GetBinaryOutput(&binary_output);
370 std::ofstream out(path, std::ios::binary);
371 out.write((char*)binary_output.data(),
372 (sizeof(uint32_t) / sizeof(char)) *
373 binary_output.size());
374 } else {
375 std::cerr << "Cowardly refusing to output binary result to screen"
376 << std::endl;
377 return 1;
378 }
379 }
380 return 0;
381 }
382
383 case shaderc_spvc_status_compilation_error: {
384 std::cerr << "compilation failed:\n"
385 << context.GetMessages() << std::endl;
386 return 1;
387 }
388
389 default:
390 std::cerr << "unexpected error " << status << std::endl;
391 return 1;
392 }
393 }
394