/* * Copyright 2015-2019 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "spirv_cpp.hpp" #include "spirv_cross_util.hpp" #include "spirv_glsl.hpp" #include "spirv_hlsl.hpp" #include "spirv_msl.hpp" #include "spirv_parser.hpp" #include "spirv_reflect.hpp" #include #include #include #include #include #include #include #include #include #ifdef HAVE_SPIRV_CROSS_GIT_VERSION #include "gitversion.h" #endif #ifdef _MSC_VER #pragma warning(disable : 4996) #endif using namespace spv; using namespace SPIRV_CROSS_NAMESPACE; using namespace std; #ifdef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS static inline void THROW(const char *str) { fprintf(stderr, "SPIRV-Cross will abort: %s\n", str); fflush(stderr); abort(); } #else #define THROW(x) throw runtime_error(x) #endif struct CLIParser; struct CLICallbacks { void add(const char *cli, const function &func) { callbacks[cli] = func; } unordered_map> callbacks; function error_handler; function default_handler; }; struct CLIParser { CLIParser(CLICallbacks cbs_, int argc_, char *argv_[]) : cbs(move(cbs_)) , argc(argc_) , argv(argv_) { } bool parse() { #ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS try #endif { while (argc && !ended_state) { const char *next = *argv++; argc--; if (*next != '-' && cbs.default_handler) { cbs.default_handler(next); } else { auto itr = cbs.callbacks.find(next); if (itr == ::end(cbs.callbacks)) { THROW("Invalid argument"); } itr->second(*this); } } return true; } #ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS catch (...) { if (cbs.error_handler) { cbs.error_handler(); } return false; } #endif } void end() { ended_state = true; } uint32_t next_uint() { if (!argc) { THROW("Tried to parse uint, but nothing left in arguments"); } uint64_t val = stoul(*argv); if (val > numeric_limits::max()) { THROW("next_uint() out of range"); } argc--; argv++; return uint32_t(val); } double next_double() { if (!argc) { THROW("Tried to parse double, but nothing left in arguments"); } double val = stod(*argv); argc--; argv++; return val; } // Return a string only if it's not prefixed with `--`, otherwise return the default value const char *next_value_string(const char *default_value) { if (!argc) { return default_value; } if (0 == strncmp("--", *argv, 2)) { return default_value; } return next_string(); } const char *next_string() { if (!argc) { THROW("Tried to parse string, but nothing left in arguments"); } const char *ret = *argv; argc--; argv++; return ret; } CLICallbacks cbs; int argc; char **argv; bool ended_state = false; }; static vector read_spirv_file(const char *path) { FILE *file = fopen(path, "rb"); if (!file) { fprintf(stderr, "Failed to open SPIR-V file: %s\n", path); return {}; } fseek(file, 0, SEEK_END); long len = ftell(file) / sizeof(uint32_t); rewind(file); vector spirv(len); if (fread(spirv.data(), sizeof(uint32_t), len, file) != size_t(len)) spirv.clear(); fclose(file); return spirv; } static bool write_string_to_file(const char *path, const char *string) { FILE *file = fopen(path, "w"); if (!file) { fprintf(stderr, "Failed to write file: %s\n", path); return false; } fprintf(file, "%s", string); fclose(file); return true; } static void print_resources(const Compiler &compiler, const char *tag, const SmallVector &resources) { fprintf(stderr, "%s\n", tag); fprintf(stderr, "=============\n\n"); bool print_ssbo = !strcmp(tag, "ssbos"); for (auto &res : resources) { auto &type = compiler.get_type(res.type_id); if (print_ssbo && compiler.buffer_is_hlsl_counter_buffer(res.id)) continue; // If we don't have a name, use the fallback for the type instead of the variable // for SSBOs and UBOs since those are the only meaningful names to use externally. // Push constant blocks are still accessed by name and not block name, even though they are technically Blocks. bool is_push_constant = compiler.get_storage_class(res.id) == StorageClassPushConstant; bool is_block = compiler.get_decoration_bitset(type.self).get(DecorationBlock) || compiler.get_decoration_bitset(type.self).get(DecorationBufferBlock); bool is_sized_block = is_block && (compiler.get_storage_class(res.id) == StorageClassUniform || compiler.get_storage_class(res.id) == StorageClassUniformConstant); ID fallback_id = !is_push_constant && is_block ? ID(res.base_type_id) : ID(res.id); uint32_t block_size = 0; uint32_t runtime_array_stride = 0; if (is_sized_block) { auto &base_type = compiler.get_type(res.base_type_id); block_size = uint32_t(compiler.get_declared_struct_size(base_type)); runtime_array_stride = uint32_t(compiler.get_declared_struct_size_runtime_array(base_type, 1) - compiler.get_declared_struct_size_runtime_array(base_type, 0)); } Bitset mask; if (print_ssbo) mask = compiler.get_buffer_block_flags(res.id); else mask = compiler.get_decoration_bitset(res.id); string array; for (auto arr : type.array) array = join("[", arr ? convert_to_string(arr) : "", "]") + array; fprintf(stderr, " ID %03u : %s%s", uint32_t(res.id), !res.name.empty() ? res.name.c_str() : compiler.get_fallback_name(fallback_id).c_str(), array.c_str()); if (mask.get(DecorationLocation)) fprintf(stderr, " (Location : %u)", compiler.get_decoration(res.id, DecorationLocation)); if (mask.get(DecorationDescriptorSet)) fprintf(stderr, " (Set : %u)", compiler.get_decoration(res.id, DecorationDescriptorSet)); if (mask.get(DecorationBinding)) fprintf(stderr, " (Binding : %u)", compiler.get_decoration(res.id, DecorationBinding)); if (mask.get(DecorationInputAttachmentIndex)) fprintf(stderr, " (Attachment : %u)", compiler.get_decoration(res.id, DecorationInputAttachmentIndex)); if (mask.get(DecorationNonReadable)) fprintf(stderr, " writeonly"); if (mask.get(DecorationNonWritable)) fprintf(stderr, " readonly"); if (is_sized_block) { fprintf(stderr, " (BlockSize : %u bytes)", block_size); if (runtime_array_stride) fprintf(stderr, " (Unsized array stride: %u bytes)", runtime_array_stride); } uint32_t counter_id = 0; if (print_ssbo && compiler.buffer_get_hlsl_counter_buffer(res.id, counter_id)) fprintf(stderr, " (HLSL counter buffer ID: %u)", counter_id); fprintf(stderr, "\n"); } fprintf(stderr, "=============\n\n"); } static const char *execution_model_to_str(spv::ExecutionModel model) { switch (model) { case spv::ExecutionModelVertex: return "vertex"; case spv::ExecutionModelTessellationControl: return "tessellation control"; case ExecutionModelTessellationEvaluation: return "tessellation evaluation"; case ExecutionModelGeometry: return "geometry"; case ExecutionModelFragment: return "fragment"; case ExecutionModelGLCompute: return "compute"; case ExecutionModelRayGenerationNV: return "raygenNV"; case ExecutionModelIntersectionNV: return "intersectionNV"; case ExecutionModelCallableNV: return "callableNV"; case ExecutionModelAnyHitNV: return "anyhitNV"; case ExecutionModelClosestHitNV: return "closesthitNV"; case ExecutionModelMissNV: return "missNV"; default: return "???"; } } static void print_resources(const Compiler &compiler, const ShaderResources &res) { auto &modes = compiler.get_execution_mode_bitset(); fprintf(stderr, "Entry points:\n"); auto entry_points = compiler.get_entry_points_and_stages(); for (auto &e : entry_points) fprintf(stderr, " %s (%s)\n", e.name.c_str(), execution_model_to_str(e.execution_model)); fprintf(stderr, "\n"); fprintf(stderr, "Execution modes:\n"); modes.for_each_bit([&](uint32_t i) { auto mode = static_cast(i); uint32_t arg0 = compiler.get_execution_mode_argument(mode, 0); uint32_t arg1 = compiler.get_execution_mode_argument(mode, 1); uint32_t arg2 = compiler.get_execution_mode_argument(mode, 2); switch (static_cast(i)) { case ExecutionModeInvocations: fprintf(stderr, " Invocations: %u\n", arg0); break; case ExecutionModeLocalSize: fprintf(stderr, " LocalSize: (%u, %u, %u)\n", arg0, arg1, arg2); break; case ExecutionModeOutputVertices: fprintf(stderr, " OutputVertices: %u\n", arg0); break; #define CHECK_MODE(m) \ case ExecutionMode##m: \ fprintf(stderr, " %s\n", #m); \ break CHECK_MODE(SpacingEqual); CHECK_MODE(SpacingFractionalEven); CHECK_MODE(SpacingFractionalOdd); CHECK_MODE(VertexOrderCw); CHECK_MODE(VertexOrderCcw); CHECK_MODE(PixelCenterInteger); CHECK_MODE(OriginUpperLeft); CHECK_MODE(OriginLowerLeft); CHECK_MODE(EarlyFragmentTests); CHECK_MODE(PointMode); CHECK_MODE(Xfb); CHECK_MODE(DepthReplacing); CHECK_MODE(DepthGreater); CHECK_MODE(DepthLess); CHECK_MODE(DepthUnchanged); CHECK_MODE(LocalSizeHint); CHECK_MODE(InputPoints); CHECK_MODE(InputLines); CHECK_MODE(InputLinesAdjacency); CHECK_MODE(Triangles); CHECK_MODE(InputTrianglesAdjacency); CHECK_MODE(Quads); CHECK_MODE(Isolines); CHECK_MODE(OutputPoints); CHECK_MODE(OutputLineStrip); CHECK_MODE(OutputTriangleStrip); CHECK_MODE(VecTypeHint); CHECK_MODE(ContractionOff); default: break; } }); fprintf(stderr, "\n"); print_resources(compiler, "subpass inputs", res.subpass_inputs); print_resources(compiler, "inputs", res.stage_inputs); print_resources(compiler, "outputs", res.stage_outputs); print_resources(compiler, "textures", res.sampled_images); print_resources(compiler, "separate images", res.separate_images); print_resources(compiler, "separate samplers", res.separate_samplers); print_resources(compiler, "images", res.storage_images); print_resources(compiler, "ssbos", res.storage_buffers); print_resources(compiler, "ubos", res.uniform_buffers); print_resources(compiler, "push", res.push_constant_buffers); print_resources(compiler, "counters", res.atomic_counters); print_resources(compiler, "acceleration structures", res.acceleration_structures); } static void print_push_constant_resources(const Compiler &compiler, const SmallVector &res) { for (auto &block : res) { auto ranges = compiler.get_active_buffer_ranges(block.id); fprintf(stderr, "Active members in buffer: %s\n", !block.name.empty() ? block.name.c_str() : compiler.get_fallback_name(block.id).c_str()); fprintf(stderr, "==================\n\n"); for (auto &range : ranges) { const auto &name = compiler.get_member_name(block.base_type_id, range.index); fprintf(stderr, "Member #%3u (%s): Offset: %4u, Range: %4u\n", range.index, !name.empty() ? name.c_str() : compiler.get_fallback_member_name(range.index).c_str(), unsigned(range.offset), unsigned(range.range)); } fprintf(stderr, "==================\n\n"); } } static void print_spec_constants(const Compiler &compiler) { auto spec_constants = compiler.get_specialization_constants(); fprintf(stderr, "Specialization constants\n"); fprintf(stderr, "==================\n\n"); for (auto &c : spec_constants) fprintf(stderr, "ID: %u, Spec ID: %u\n", uint32_t(c.id), c.constant_id); fprintf(stderr, "==================\n\n"); } static void print_capabilities_and_extensions(const Compiler &compiler) { fprintf(stderr, "Capabilities\n"); fprintf(stderr, "============\n"); for (auto &capability : compiler.get_declared_capabilities()) fprintf(stderr, "Capability: %u\n", static_cast(capability)); fprintf(stderr, "============\n\n"); fprintf(stderr, "Extensions\n"); fprintf(stderr, "============\n"); for (auto &ext : compiler.get_declared_extensions()) fprintf(stderr, "Extension: %s\n", ext.c_str()); fprintf(stderr, "============\n\n"); } struct PLSArg { PlsFormat format; string name; }; struct Remap { string src_name; string dst_name; unsigned components; }; struct VariableTypeRemap { string variable_name; string new_variable_type; }; struct InterfaceVariableRename { StorageClass storageClass; uint32_t location; string variable_name; }; struct CLIArguments { const char *input = nullptr; const char *output = nullptr; const char *cpp_interface_name = nullptr; uint32_t version = 0; uint32_t shader_model = 0; uint32_t msl_version = 0; bool es = false; bool set_version = false; bool set_shader_model = false; bool set_msl_version = false; bool set_es = false; bool dump_resources = false; bool force_temporary = false; bool flatten_ubo = false; bool fixup = false; bool yflip = false; bool sso = false; bool support_nonzero_baseinstance = true; bool msl_capture_output_to_buffer = false; bool msl_swizzle_texture_samples = false; bool msl_ios = false; bool msl_pad_fragment_output = false; bool msl_domain_lower_left = false; bool msl_argument_buffers = false; bool msl_texture_buffer_native = false; bool msl_framebuffer_fetch = false; bool msl_invariant_float_math = false; bool msl_emulate_cube_array = false; bool msl_multiview = false; bool msl_view_index_from_device_index = false; bool msl_dispatch_base = false; bool glsl_emit_push_constant_as_ubo = false; bool glsl_emit_ubo_as_plain_uniforms = false; bool vulkan_glsl_disable_ext_samplerless_texture_functions = false; bool emit_line_directives = false; SmallVector msl_discrete_descriptor_sets; SmallVector msl_device_argument_buffers; SmallVector> msl_dynamic_buffers; SmallVector pls_in; SmallVector pls_out; SmallVector remaps; SmallVector extensions; SmallVector variable_type_remaps; SmallVector interface_variable_renames; SmallVector hlsl_attr_remap; string entry; string entry_stage; struct Rename { string old_name; string new_name; ExecutionModel execution_model; }; SmallVector entry_point_rename; uint32_t iterations = 1; bool cpp = false; string reflect; bool msl = false; bool hlsl = false; bool hlsl_compat = false; bool hlsl_support_nonzero_base = false; HLSLBindingFlags hlsl_binding_flags = 0; bool vulkan_semantics = false; bool flatten_multidimensional_arrays = false; bool use_420pack_extension = true; bool remove_unused = false; bool combined_samplers_inherit_bindings = false; }; static void print_version() { #ifdef HAVE_SPIRV_CROSS_GIT_VERSION fprintf(stderr, "%s\n", SPIRV_CROSS_GIT_REVISION); #else fprintf(stderr, "Git revision unknown. Build with CMake to create timestamp and revision info.\n"); #endif } static void print_help() { print_version(); fprintf(stderr, "Usage: spirv-cross\n" "\t[--output ]\n" "\t[SPIR-V file]\n" "\t[--es]\n" "\t[--no-es]\n" "\t[--version ]\n" "\t[--dump-resources]\n" "\t[--help]\n" "\t[--revision]\n" "\t[--force-temporary]\n" "\t[--vulkan-semantics]\n" "\t[--flatten-ubo]\n" "\t[--fixup-clipspace]\n" "\t[--flip-vert-y]\n" "\t[--iterations iter]\n" "\t[--cpp]\n" "\t[--cpp-interface-name ]\n" "\t[--glsl-emit-push-constant-as-ubo]\n" "\t[--glsl-emit-ubo-as-plain-uniforms]\n" "\t[--vulkan-glsl-disable-ext-samplerless-texture-functions]\n" "\t[--msl]\n" "\t[--msl-version ]\n" "\t[--msl-capture-output]\n" "\t[--msl-swizzle-texture-samples]\n" "\t[--msl-ios]\n" "\t[--msl-pad-fragment-output]\n" "\t[--msl-domain-lower-left]\n" "\t[--msl-argument-buffers]\n" "\t[--msl-texture-buffer-native]\n" "\t[--msl-framebuffer-fetch]\n" "\t[--msl-emulate-cube-array]\n" "\t[--msl-discrete-descriptor-set ]\n" "\t[--msl-device-argument-buffer ]\n" "\t[--msl-multiview]\n" "\t[--msl-view-index-from-device-index]\n" "\t[--msl-dispatch-base]\n" "\t[--msl-dynamic-buffer ]\n" "\t[--hlsl]\n" "\t[--reflect]\n" "\t[--shader-model]\n" "\t[--hlsl-enable-compat]\n" "\t[--hlsl-support-nonzero-basevertex-baseinstance]\n" "\t[--hlsl-auto-binding (push, cbv, srv, uav, sampler, all)]\n" "\t[--separate-shader-objects]\n" "\t[--pls-in format input-name]\n" "\t[--pls-out format output-name]\n" "\t[--remap source_name target_name components]\n" "\t[--extension ext]\n" "\t[--entry name]\n" "\t[--stage ]\n" "\t[--remove-unused-variables]\n" "\t[--flatten-multidimensional-arrays]\n" "\t[--no-420pack-extension]\n" "\t[--remap-variable-type ]\n" "\t[--rename-interface-variable ]\n" "\t[--set-hlsl-vertex-input-semantic ]\n" "\t[--rename-entry-point ]\n" "\t[--combined-samplers-inherit-bindings]\n" "\t[--no-support-nonzero-baseinstance]\n" "\t[--emit-line-directives]\n" "\n"); } static bool remap_generic(Compiler &compiler, const SmallVector &resources, const Remap &remap) { auto itr = find_if(begin(resources), end(resources), [&remap](const Resource &res) { return res.name == remap.src_name; }); if (itr != end(resources)) { compiler.set_remapped_variable_state(itr->id, true); compiler.set_name(itr->id, remap.dst_name); compiler.set_subpass_input_remapped_components(itr->id, remap.components); return true; } else return false; } static vector remap_pls(const SmallVector &pls_variables, const SmallVector &resources, const SmallVector *secondary_resources) { vector ret; for (auto &pls : pls_variables) { bool found = false; for (auto &res : resources) { if (res.name == pls.name) { ret.push_back({ res.id, pls.format }); found = true; break; } } if (!found && secondary_resources) { for (auto &res : *secondary_resources) { if (res.name == pls.name) { ret.push_back({ res.id, pls.format }); found = true; break; } } } if (!found) fprintf(stderr, "Did not find stage input/output/target with name \"%s\".\n", pls.name.c_str()); } return ret; } static PlsFormat pls_format(const char *str) { if (!strcmp(str, "r11f_g11f_b10f")) return PlsR11FG11FB10F; else if (!strcmp(str, "r32f")) return PlsR32F; else if (!strcmp(str, "rg16f")) return PlsRG16F; else if (!strcmp(str, "rg16")) return PlsRG16; else if (!strcmp(str, "rgb10_a2")) return PlsRGB10A2; else if (!strcmp(str, "rgba8")) return PlsRGBA8; else if (!strcmp(str, "rgba8i")) return PlsRGBA8I; else if (!strcmp(str, "rgba8ui")) return PlsRGBA8UI; else if (!strcmp(str, "rg16i")) return PlsRG16I; else if (!strcmp(str, "rgb10_a2ui")) return PlsRGB10A2UI; else if (!strcmp(str, "rg16ui")) return PlsRG16UI; else if (!strcmp(str, "r32ui")) return PlsR32UI; else return PlsNone; } static ExecutionModel stage_to_execution_model(const std::string &stage) { if (stage == "vert") return ExecutionModelVertex; else if (stage == "frag") return ExecutionModelFragment; else if (stage == "comp") return ExecutionModelGLCompute; else if (stage == "tesc") return ExecutionModelTessellationControl; else if (stage == "tese") return ExecutionModelTessellationEvaluation; else if (stage == "geom") return ExecutionModelGeometry; else SPIRV_CROSS_THROW("Invalid stage."); } static HLSLBindingFlags hlsl_resource_type_to_flag(const std::string &arg) { if (arg == "push") return HLSL_BINDING_AUTO_PUSH_CONSTANT_BIT; else if (arg == "cbv") return HLSL_BINDING_AUTO_CBV_BIT; else if (arg == "srv") return HLSL_BINDING_AUTO_SRV_BIT; else if (arg == "uav") return HLSL_BINDING_AUTO_UAV_BIT; else if (arg == "sampler") return HLSL_BINDING_AUTO_SAMPLER_BIT; else if (arg == "all") return HLSL_BINDING_AUTO_ALL; else { fprintf(stderr, "Invalid resource type for --hlsl-auto-binding: %s\n", arg.c_str()); return 0; } } static string compile_iteration(const CLIArguments &args, std::vector spirv_file) { Parser spirv_parser(move(spirv_file)); spirv_parser.parse(); unique_ptr compiler; bool combined_image_samplers = false; bool build_dummy_sampler = false; if (args.cpp) { compiler.reset(new CompilerCPP(move(spirv_parser.get_parsed_ir()))); if (args.cpp_interface_name) static_cast(compiler.get())->set_interface_name(args.cpp_interface_name); } else if (args.msl) { compiler.reset(new CompilerMSL(move(spirv_parser.get_parsed_ir()))); auto *msl_comp = static_cast(compiler.get()); auto msl_opts = msl_comp->get_msl_options(); if (args.set_msl_version) msl_opts.msl_version = args.msl_version; msl_opts.capture_output_to_buffer = args.msl_capture_output_to_buffer; msl_opts.swizzle_texture_samples = args.msl_swizzle_texture_samples; msl_opts.invariant_float_math = args.msl_invariant_float_math; if (args.msl_ios) { msl_opts.platform = CompilerMSL::Options::iOS; msl_opts.ios_use_framebuffer_fetch_subpasses = args.msl_framebuffer_fetch; msl_opts.emulate_cube_array = args.msl_emulate_cube_array; } msl_opts.pad_fragment_output_components = args.msl_pad_fragment_output; msl_opts.tess_domain_origin_lower_left = args.msl_domain_lower_left; msl_opts.argument_buffers = args.msl_argument_buffers; msl_opts.texture_buffer_native = args.msl_texture_buffer_native; msl_opts.multiview = args.msl_multiview; msl_opts.view_index_from_device_index = args.msl_view_index_from_device_index; msl_opts.dispatch_base = args.msl_dispatch_base; msl_comp->set_msl_options(msl_opts); for (auto &v : args.msl_discrete_descriptor_sets) msl_comp->add_discrete_descriptor_set(v); for (auto &v : args.msl_device_argument_buffers) msl_comp->set_argument_buffer_device_address_space(v, true); uint32_t i = 0; for (auto &v : args.msl_dynamic_buffers) msl_comp->add_dynamic_buffer(v.first, v.second, i++); } else if (args.hlsl) compiler.reset(new CompilerHLSL(move(spirv_parser.get_parsed_ir()))); else { combined_image_samplers = !args.vulkan_semantics; if (!args.vulkan_semantics || args.vulkan_glsl_disable_ext_samplerless_texture_functions) build_dummy_sampler = true; compiler.reset(new CompilerGLSL(move(spirv_parser.get_parsed_ir()))); } if (!args.variable_type_remaps.empty()) { auto remap_cb = [&](const SPIRType &, const string &name, string &out) -> void { for (const VariableTypeRemap &remap : args.variable_type_remaps) if (name == remap.variable_name) out = remap.new_variable_type; }; compiler->set_variable_type_remap_callback(move(remap_cb)); } for (auto &rename : args.entry_point_rename) compiler->rename_entry_point(rename.old_name, rename.new_name, rename.execution_model); auto entry_points = compiler->get_entry_points_and_stages(); auto entry_point = args.entry; ExecutionModel model = ExecutionModelMax; if (!args.entry_stage.empty()) { model = stage_to_execution_model(args.entry_stage); if (entry_point.empty()) { // Just use the first entry point with this stage. for (auto &e : entry_points) { if (e.execution_model == model) { entry_point = e.name; break; } } if (entry_point.empty()) { fprintf(stderr, "Could not find an entry point with stage: %s\n", args.entry_stage.c_str()); exit(EXIT_FAILURE); } } else { // Make sure both stage and name exists. bool exists = false; for (auto &e : entry_points) { if (e.execution_model == model && e.name == entry_point) { exists = true; break; } } if (!exists) { fprintf(stderr, "Could not find an entry point %s with stage: %s\n", entry_point.c_str(), args.entry_stage.c_str()); exit(EXIT_FAILURE); } } } else if (!entry_point.empty()) { // Make sure there is just one entry point with this name, or the stage // is ambiguous. uint32_t stage_count = 0; for (auto &e : entry_points) { if (e.name == entry_point) { stage_count++; model = e.execution_model; } } if (stage_count == 0) { fprintf(stderr, "There is no entry point with name: %s\n", entry_point.c_str()); exit(EXIT_FAILURE); } else if (stage_count > 1) { fprintf(stderr, "There is more than one entry point with name: %s. Use --stage.\n", entry_point.c_str()); exit(EXIT_FAILURE); } } if (!entry_point.empty()) compiler->set_entry_point(entry_point, model); if (!args.set_version && !compiler->get_common_options().version) { fprintf(stderr, "Didn't specify GLSL version and SPIR-V did not specify language.\n"); print_help(); exit(EXIT_FAILURE); } CompilerGLSL::Options opts = compiler->get_common_options(); if (args.set_version) opts.version = args.version; if (args.set_es) opts.es = args.es; opts.force_temporary = args.force_temporary; opts.separate_shader_objects = args.sso; opts.flatten_multidimensional_arrays = args.flatten_multidimensional_arrays; opts.enable_420pack_extension = args.use_420pack_extension; opts.vulkan_semantics = args.vulkan_semantics; opts.vertex.fixup_clipspace = args.fixup; opts.vertex.flip_vert_y = args.yflip; opts.vertex.support_nonzero_base_instance = args.support_nonzero_baseinstance; opts.emit_push_constant_as_uniform_buffer = args.glsl_emit_push_constant_as_ubo; opts.emit_uniform_buffer_as_plain_uniforms = args.glsl_emit_ubo_as_plain_uniforms; opts.emit_line_directives = args.emit_line_directives; compiler->set_common_options(opts); // Set HLSL specific options. if (args.hlsl) { auto *hlsl = static_cast(compiler.get()); auto hlsl_opts = hlsl->get_hlsl_options(); if (args.set_shader_model) { if (args.shader_model < 30) { fprintf(stderr, "Shader model earlier than 30 (3.0) not supported.\n"); exit(EXIT_FAILURE); } hlsl_opts.shader_model = args.shader_model; } if (args.hlsl_compat) { // Enable all compat options. hlsl_opts.point_size_compat = true; hlsl_opts.point_coord_compat = true; } if (hlsl_opts.shader_model <= 30) { combined_image_samplers = true; build_dummy_sampler = true; } hlsl_opts.support_nonzero_base_vertex_base_instance = args.hlsl_support_nonzero_base; hlsl->set_hlsl_options(hlsl_opts); hlsl->set_resource_binding_flags(args.hlsl_binding_flags); } if (build_dummy_sampler) { uint32_t sampler = compiler->build_dummy_sampler_for_combined_images(); if (sampler != 0) { // Set some defaults to make validation happy. compiler->set_decoration(sampler, DecorationDescriptorSet, 0); compiler->set_decoration(sampler, DecorationBinding, 0); } } ShaderResources res; if (args.remove_unused) { auto active = compiler->get_active_interface_variables(); res = compiler->get_shader_resources(active); compiler->set_enabled_interface_variables(move(active)); } else res = compiler->get_shader_resources(); if (args.flatten_ubo) { for (auto &ubo : res.uniform_buffers) compiler->flatten_buffer_block(ubo.id); for (auto &ubo : res.push_constant_buffers) compiler->flatten_buffer_block(ubo.id); } auto pls_inputs = remap_pls(args.pls_in, res.stage_inputs, &res.subpass_inputs); auto pls_outputs = remap_pls(args.pls_out, res.stage_outputs, nullptr); compiler->remap_pixel_local_storage(move(pls_inputs), move(pls_outputs)); for (auto &ext : args.extensions) compiler->require_extension(ext); for (auto &remap : args.remaps) { if (remap_generic(*compiler, res.stage_inputs, remap)) continue; if (remap_generic(*compiler, res.stage_outputs, remap)) continue; if (remap_generic(*compiler, res.subpass_inputs, remap)) continue; } for (auto &rename : args.interface_variable_renames) { if (rename.storageClass == StorageClassInput) spirv_cross_util::rename_interface_variable(*compiler, res.stage_inputs, rename.location, rename.variable_name); else if (rename.storageClass == StorageClassOutput) spirv_cross_util::rename_interface_variable(*compiler, res.stage_outputs, rename.location, rename.variable_name); else { fprintf(stderr, "error at --rename-interface-variable ...\n"); exit(EXIT_FAILURE); } } if (args.dump_resources) { print_resources(*compiler, res); print_push_constant_resources(*compiler, res.push_constant_buffers); print_spec_constants(*compiler); print_capabilities_and_extensions(*compiler); } if (combined_image_samplers) { compiler->build_combined_image_samplers(); if (args.combined_samplers_inherit_bindings) spirv_cross_util::inherit_combined_sampler_bindings(*compiler); // Give the remapped combined samplers new names. for (auto &remap : compiler->get_combined_image_samplers()) { compiler->set_name(remap.combined_id, join("SPIRV_Cross_Combined", compiler->get_name(remap.image_id), compiler->get_name(remap.sampler_id))); } } if (args.hlsl) { auto *hlsl_compiler = static_cast(compiler.get()); uint32_t new_builtin = hlsl_compiler->remap_num_workgroups_builtin(); if (new_builtin) { hlsl_compiler->set_decoration(new_builtin, DecorationDescriptorSet, 0); hlsl_compiler->set_decoration(new_builtin, DecorationBinding, 0); } } if (args.hlsl) { for (auto &remap : args.hlsl_attr_remap) static_cast(compiler.get())->add_vertex_attribute_remap(remap); } return compiler->compile(); } static int main_inner(int argc, char *argv[]) { CLIArguments args; CLICallbacks cbs; cbs.add("--help", [](CLIParser &parser) { print_help(); parser.end(); }); cbs.add("--revision", [](CLIParser &parser) { print_version(); parser.end(); }); cbs.add("--output", [&args](CLIParser &parser) { args.output = parser.next_string(); }); cbs.add("--es", [&args](CLIParser &) { args.es = true; args.set_es = true; }); cbs.add("--no-es", [&args](CLIParser &) { args.es = false; args.set_es = true; }); cbs.add("--version", [&args](CLIParser &parser) { args.version = parser.next_uint(); args.set_version = true; }); cbs.add("--dump-resources", [&args](CLIParser &) { args.dump_resources = true; }); cbs.add("--force-temporary", [&args](CLIParser &) { args.force_temporary = true; }); cbs.add("--flatten-ubo", [&args](CLIParser &) { args.flatten_ubo = true; }); cbs.add("--fixup-clipspace", [&args](CLIParser &) { args.fixup = true; }); cbs.add("--flip-vert-y", [&args](CLIParser &) { args.yflip = true; }); cbs.add("--iterations", [&args](CLIParser &parser) { args.iterations = parser.next_uint(); }); cbs.add("--cpp", [&args](CLIParser &) { args.cpp = true; }); cbs.add("--reflect", [&args](CLIParser &parser) { args.reflect = parser.next_value_string("json"); }); cbs.add("--cpp-interface-name", [&args](CLIParser &parser) { args.cpp_interface_name = parser.next_string(); }); cbs.add("--metal", [&args](CLIParser &) { args.msl = true; }); // Legacy compatibility cbs.add("--glsl-emit-push-constant-as-ubo", [&args](CLIParser &) { args.glsl_emit_push_constant_as_ubo = true; }); cbs.add("--glsl-emit-ubo-as-plain-uniforms", [&args](CLIParser &) { args.glsl_emit_ubo_as_plain_uniforms = true; }); cbs.add("--vulkan-glsl-disable-ext-samplerless-texture-functions", [&args](CLIParser &) { args.vulkan_glsl_disable_ext_samplerless_texture_functions = true; }); cbs.add("--msl", [&args](CLIParser &) { args.msl = true; }); cbs.add("--hlsl", [&args](CLIParser &) { args.hlsl = true; }); cbs.add("--hlsl-enable-compat", [&args](CLIParser &) { args.hlsl_compat = true; }); cbs.add("--hlsl-support-nonzero-basevertex-baseinstance", [&args](CLIParser &) { args.hlsl_support_nonzero_base = true; }); cbs.add("--hlsl-auto-binding", [&args](CLIParser &parser) { args.hlsl_binding_flags |= hlsl_resource_type_to_flag(parser.next_string()); }); cbs.add("--vulkan-semantics", [&args](CLIParser &) { args.vulkan_semantics = true; }); cbs.add("--flatten-multidimensional-arrays", [&args](CLIParser &) { args.flatten_multidimensional_arrays = true; }); cbs.add("--no-420pack-extension", [&args](CLIParser &) { args.use_420pack_extension = false; }); cbs.add("--msl-capture-output", [&args](CLIParser &) { args.msl_capture_output_to_buffer = true; }); cbs.add("--msl-swizzle-texture-samples", [&args](CLIParser &) { args.msl_swizzle_texture_samples = true; }); cbs.add("--msl-ios", [&args](CLIParser &) { args.msl_ios = true; }); cbs.add("--msl-pad-fragment-output", [&args](CLIParser &) { args.msl_pad_fragment_output = true; }); cbs.add("--msl-domain-lower-left", [&args](CLIParser &) { args.msl_domain_lower_left = true; }); cbs.add("--msl-argument-buffers", [&args](CLIParser &) { args.msl_argument_buffers = true; }); cbs.add("--msl-discrete-descriptor-set", [&args](CLIParser &parser) { args.msl_discrete_descriptor_sets.push_back(parser.next_uint()); }); cbs.add("--msl-device-argument-buffer", [&args](CLIParser &parser) { args.msl_device_argument_buffers.push_back(parser.next_uint()); }); cbs.add("--msl-texture-buffer-native", [&args](CLIParser &) { args.msl_texture_buffer_native = true; }); cbs.add("--msl-framebuffer-fetch", [&args](CLIParser &) { args.msl_framebuffer_fetch = true; }); cbs.add("--msl-invariant-float-math", [&args](CLIParser &) { args.msl_invariant_float_math = true; }); cbs.add("--msl-emulate-cube-array", [&args](CLIParser &) { args.msl_emulate_cube_array = true; }); cbs.add("--msl-multiview", [&args](CLIParser &) { args.msl_multiview = true; }); cbs.add("--msl-view-index-from-device-index", [&args](CLIParser &) { args.msl_view_index_from_device_index = true; }); cbs.add("--msl-dispatch-base", [&args](CLIParser &) { args.msl_dispatch_base = true; }); cbs.add("--msl-dynamic-buffer", [&args](CLIParser &parser) { args.msl_argument_buffers = true; // Make sure next_uint() is called in-order. uint32_t desc_set = parser.next_uint(); uint32_t binding = parser.next_uint(); args.msl_dynamic_buffers.push_back(make_pair(desc_set, binding)); }); cbs.add("--extension", [&args](CLIParser &parser) { args.extensions.push_back(parser.next_string()); }); cbs.add("--rename-entry-point", [&args](CLIParser &parser) { auto old_name = parser.next_string(); auto new_name = parser.next_string(); auto model = stage_to_execution_model(parser.next_string()); args.entry_point_rename.push_back({ old_name, new_name, move(model) }); }); cbs.add("--entry", [&args](CLIParser &parser) { args.entry = parser.next_string(); }); cbs.add("--stage", [&args](CLIParser &parser) { args.entry_stage = parser.next_string(); }); cbs.add("--separate-shader-objects", [&args](CLIParser &) { args.sso = true; }); cbs.add("--set-hlsl-vertex-input-semantic", [&args](CLIParser &parser) { HLSLVertexAttributeRemap remap; remap.location = parser.next_uint(); remap.semantic = parser.next_string(); args.hlsl_attr_remap.push_back(move(remap)); }); cbs.add("--remap", [&args](CLIParser &parser) { string src = parser.next_string(); string dst = parser.next_string(); uint32_t components = parser.next_uint(); args.remaps.push_back({ move(src), move(dst), components }); }); cbs.add("--remap-variable-type", [&args](CLIParser &parser) { string var_name = parser.next_string(); string new_type = parser.next_string(); args.variable_type_remaps.push_back({ move(var_name), move(new_type) }); }); cbs.add("--rename-interface-variable", [&args](CLIParser &parser) { StorageClass cls = StorageClassMax; string clsStr = parser.next_string(); if (clsStr == "in") cls = StorageClassInput; else if (clsStr == "out") cls = StorageClassOutput; uint32_t loc = parser.next_uint(); string var_name = parser.next_string(); args.interface_variable_renames.push_back({ cls, loc, move(var_name) }); }); cbs.add("--pls-in", [&args](CLIParser &parser) { auto fmt = pls_format(parser.next_string()); auto name = parser.next_string(); args.pls_in.push_back({ move(fmt), move(name) }); }); cbs.add("--pls-out", [&args](CLIParser &parser) { auto fmt = pls_format(parser.next_string()); auto name = parser.next_string(); args.pls_out.push_back({ move(fmt), move(name) }); }); cbs.add("--shader-model", [&args](CLIParser &parser) { args.shader_model = parser.next_uint(); args.set_shader_model = true; }); cbs.add("--msl-version", [&args](CLIParser &parser) { args.msl_version = parser.next_uint(); args.set_msl_version = true; }); cbs.add("--remove-unused-variables", [&args](CLIParser &) { args.remove_unused = true; }); cbs.add("--combined-samplers-inherit-bindings", [&args](CLIParser &) { args.combined_samplers_inherit_bindings = true; }); cbs.add("--no-support-nonzero-baseinstance", [&](CLIParser &) { args.support_nonzero_baseinstance = false; }); cbs.add("--emit-line-directives", [&args](CLIParser &) { args.emit_line_directives = true; }); cbs.default_handler = [&args](const char *value) { args.input = value; }; cbs.error_handler = [] { print_help(); }; CLIParser parser{ move(cbs), argc - 1, argv + 1 }; if (!parser.parse()) return EXIT_FAILURE; else if (parser.ended_state) return EXIT_SUCCESS; if (!args.input) { fprintf(stderr, "Didn't specify input file.\n"); print_help(); return EXIT_FAILURE; } auto spirv_file = read_spirv_file(args.input); if (spirv_file.empty()) return EXIT_FAILURE; // Special case reflection because it has little to do with the path followed by code-outputting compilers if (!args.reflect.empty()) { Parser spirv_parser(move(spirv_file)); spirv_parser.parse(); CompilerReflection compiler(move(spirv_parser.get_parsed_ir())); compiler.set_format(args.reflect); auto json = compiler.compile(); if (args.output) write_string_to_file(args.output, json.c_str()); else printf("%s", json.c_str()); return EXIT_SUCCESS; } string compiled_output; if (args.iterations == 1) compiled_output = compile_iteration(args, move(spirv_file)); else { for (unsigned i = 0; i < args.iterations; i++) compiled_output = compile_iteration(args, spirv_file); } if (args.output) write_string_to_file(args.output, compiled_output.c_str()); else printf("%s", compiled_output.c_str()); return EXIT_SUCCESS; } int main(int argc, char *argv[]) { #ifdef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS return main_inner(argc, argv); #else // Make sure we catch the exception or it just disappears into the aether on Windows. try { return main_inner(argc, argv); } catch (const std::exception &e) { fprintf(stderr, "SPIRV-Cross threw an exception: %s\n", e.what()); return EXIT_FAILURE; } #endif }