| /* |
| * Copyright 2015-2018 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_glsl.hpp" |
| #include "GLSL.std.450.h" |
| #include "spirv_common.hpp" |
| #include <algorithm> |
| #include <assert.h> |
| #include <cmath> |
| #include <limits> |
| #include <utility> |
| |
| using namespace spv; |
| using namespace spirv_cross; |
| using namespace std; |
| |
| static bool is_unsigned_opcode(Op op) |
| { |
| // Don't have to be exhaustive, only relevant for legacy target checking ... |
| switch (op) |
| { |
| case OpShiftRightLogical: |
| case OpUGreaterThan: |
| case OpUGreaterThanEqual: |
| case OpULessThan: |
| case OpULessThanEqual: |
| case OpUConvert: |
| case OpUDiv: |
| case OpUMod: |
| case OpUMulExtended: |
| case OpConvertUToF: |
| case OpConvertFToU: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| static bool is_unsigned_glsl_opcode(GLSLstd450 op) |
| { |
| // Don't have to be exhaustive, only relevant for legacy target checking ... |
| switch (op) |
| { |
| case GLSLstd450UClamp: |
| case GLSLstd450UMin: |
| case GLSLstd450UMax: |
| case GLSLstd450FindUMsb: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| static bool packing_is_vec4_padded(BufferPackingStandard packing) |
| { |
| switch (packing) |
| { |
| case BufferPackingHLSLCbuffer: |
| case BufferPackingHLSLCbufferPackOffset: |
| case BufferPackingStd140: |
| case BufferPackingStd140EnhancedLayout: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| static bool packing_is_hlsl(BufferPackingStandard packing) |
| { |
| switch (packing) |
| { |
| case BufferPackingHLSLCbuffer: |
| case BufferPackingHLSLCbufferPackOffset: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| static bool packing_has_flexible_offset(BufferPackingStandard packing) |
| { |
| switch (packing) |
| { |
| case BufferPackingStd140: |
| case BufferPackingStd430: |
| case BufferPackingHLSLCbuffer: |
| return false; |
| |
| default: |
| return true; |
| } |
| } |
| |
| static BufferPackingStandard packing_to_substruct_packing(BufferPackingStandard packing) |
| { |
| switch (packing) |
| { |
| case BufferPackingStd140EnhancedLayout: |
| return BufferPackingStd140; |
| case BufferPackingStd430EnhancedLayout: |
| return BufferPackingStd430; |
| case BufferPackingHLSLCbufferPackOffset: |
| return BufferPackingHLSLCbuffer; |
| default: |
| return packing; |
| } |
| } |
| |
| // Sanitizes underscores for GLSL where multiple underscores in a row are not allowed. |
| string CompilerGLSL::sanitize_underscores(const string &str) |
| { |
| string res; |
| res.reserve(str.size()); |
| |
| bool last_underscore = false; |
| for (auto c : str) |
| { |
| if (c == '_') |
| { |
| if (last_underscore) |
| continue; |
| |
| res += c; |
| last_underscore = true; |
| } |
| else |
| { |
| res += c; |
| last_underscore = false; |
| } |
| } |
| return res; |
| } |
| |
| // Returns true if an arithmetic operation does not change behavior depending on signedness. |
| static bool glsl_opcode_is_sign_invariant(Op opcode) |
| { |
| switch (opcode) |
| { |
| case OpIEqual: |
| case OpINotEqual: |
| case OpISub: |
| case OpIAdd: |
| case OpIMul: |
| case OpShiftLeftLogical: |
| case OpBitwiseOr: |
| case OpBitwiseXor: |
| case OpBitwiseAnd: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| static const char *to_pls_layout(PlsFormat format) |
| { |
| switch (format) |
| { |
| case PlsR11FG11FB10F: |
| return "layout(r11f_g11f_b10f) "; |
| case PlsR32F: |
| return "layout(r32f) "; |
| case PlsRG16F: |
| return "layout(rg16f) "; |
| case PlsRGB10A2: |
| return "layout(rgb10_a2) "; |
| case PlsRGBA8: |
| return "layout(rgba8) "; |
| case PlsRG16: |
| return "layout(rg16) "; |
| case PlsRGBA8I: |
| return "layout(rgba8i)"; |
| case PlsRG16I: |
| return "layout(rg16i) "; |
| case PlsRGB10A2UI: |
| return "layout(rgb10_a2ui) "; |
| case PlsRGBA8UI: |
| return "layout(rgba8ui) "; |
| case PlsRG16UI: |
| return "layout(rg16ui) "; |
| case PlsR32UI: |
| return "layout(r32ui) "; |
| default: |
| return ""; |
| } |
| } |
| |
| static SPIRType::BaseType pls_format_to_basetype(PlsFormat format) |
| { |
| switch (format) |
| { |
| default: |
| case PlsR11FG11FB10F: |
| case PlsR32F: |
| case PlsRG16F: |
| case PlsRGB10A2: |
| case PlsRGBA8: |
| case PlsRG16: |
| return SPIRType::Float; |
| |
| case PlsRGBA8I: |
| case PlsRG16I: |
| return SPIRType::Int; |
| |
| case PlsRGB10A2UI: |
| case PlsRGBA8UI: |
| case PlsRG16UI: |
| case PlsR32UI: |
| return SPIRType::UInt; |
| } |
| } |
| |
| static uint32_t pls_format_to_components(PlsFormat format) |
| { |
| switch (format) |
| { |
| default: |
| case PlsR32F: |
| case PlsR32UI: |
| return 1; |
| |
| case PlsRG16F: |
| case PlsRG16: |
| case PlsRG16UI: |
| case PlsRG16I: |
| return 2; |
| |
| case PlsR11FG11FB10F: |
| return 3; |
| |
| case PlsRGB10A2: |
| case PlsRGBA8: |
| case PlsRGBA8I: |
| case PlsRGB10A2UI: |
| case PlsRGBA8UI: |
| return 4; |
| } |
| } |
| |
| static const char *vector_swizzle(int vecsize, int index) |
| { |
| static const char *swizzle[4][4] = { |
| { ".x", ".y", ".z", ".w" }, { ".xy", ".yz", ".zw" }, { ".xyz", ".yzw" }, { "" } |
| }; |
| |
| assert(vecsize >= 1 && vecsize <= 4); |
| assert(index >= 0 && index < 4); |
| assert(swizzle[vecsize - 1][index]); |
| |
| return swizzle[vecsize - 1][index]; |
| } |
| |
| void CompilerGLSL::reset() |
| { |
| // We do some speculative optimizations which should pretty much always work out, |
| // but just in case the SPIR-V is rather weird, recompile until it's happy. |
| // This typically only means one extra pass. |
| force_recompile = false; |
| |
| // Clear invalid expression tracking. |
| invalid_expressions.clear(); |
| current_function = nullptr; |
| |
| // Clear temporary usage tracking. |
| expression_usage_counts.clear(); |
| forwarded_temporaries.clear(); |
| |
| resource_names.clear(); |
| function_overloads.clear(); |
| |
| for (auto &id : ids) |
| { |
| if (id.get_type() == TypeVariable) |
| { |
| // Clear unflushed dependees. |
| id.get<SPIRVariable>().dependees.clear(); |
| } |
| else if (id.get_type() == TypeExpression) |
| { |
| // And remove all expressions. |
| id.reset(); |
| } |
| else if (id.get_type() == TypeFunction) |
| { |
| // Reset active state for all functions. |
| id.get<SPIRFunction>().active = false; |
| id.get<SPIRFunction>().flush_undeclared = true; |
| } |
| } |
| |
| statement_count = 0; |
| indent = 0; |
| } |
| |
| void CompilerGLSL::remap_pls_variables() |
| { |
| for (auto &input : pls_inputs) |
| { |
| auto &var = get<SPIRVariable>(input.id); |
| |
| bool input_is_target = false; |
| if (var.storage == StorageClassUniformConstant) |
| { |
| auto &type = get<SPIRType>(var.basetype); |
| input_is_target = type.image.dim == DimSubpassData; |
| } |
| |
| if (var.storage != StorageClassInput && !input_is_target) |
| SPIRV_CROSS_THROW("Can only use in and target variables for PLS inputs."); |
| var.remapped_variable = true; |
| } |
| |
| for (auto &output : pls_outputs) |
| { |
| auto &var = get<SPIRVariable>(output.id); |
| if (var.storage != StorageClassOutput) |
| SPIRV_CROSS_THROW("Can only use out variables for PLS outputs."); |
| var.remapped_variable = true; |
| } |
| } |
| |
| void CompilerGLSL::find_static_extensions() |
| { |
| for (auto &id : ids) |
| { |
| if (id.get_type() == TypeType) |
| { |
| auto &type = id.get<SPIRType>(); |
| if (type.basetype == SPIRType::Double) |
| { |
| if (options.es) |
| SPIRV_CROSS_THROW("FP64 not supported in ES profile."); |
| if (!options.es && options.version < 400) |
| require_extension_internal("GL_ARB_gpu_shader_fp64"); |
| } |
| |
| if (type.basetype == SPIRType::Int64 || type.basetype == SPIRType::UInt64) |
| { |
| if (options.es) |
| SPIRV_CROSS_THROW("64-bit integers not supported in ES profile."); |
| if (!options.es) |
| require_extension_internal("GL_ARB_gpu_shader_int64"); |
| } |
| |
| if (type.basetype == SPIRType::Half) |
| require_extension_internal("GL_AMD_gpu_shader_half_float"); |
| } |
| } |
| |
| auto &execution = get_entry_point(); |
| switch (execution.model) |
| { |
| case ExecutionModelGLCompute: |
| if (!options.es && options.version < 430) |
| require_extension_internal("GL_ARB_compute_shader"); |
| if (options.es && options.version < 310) |
| SPIRV_CROSS_THROW("At least ESSL 3.10 required for compute shaders."); |
| break; |
| |
| case ExecutionModelGeometry: |
| if (options.es && options.version < 320) |
| require_extension_internal("GL_EXT_geometry_shader"); |
| if (!options.es && options.version < 150) |
| require_extension_internal("GL_ARB_geometry_shader4"); |
| |
| if (execution.flags.get(ExecutionModeInvocations) && execution.invocations != 1) |
| { |
| // Instanced GS is part of 400 core or this extension. |
| if (!options.es && options.version < 400) |
| require_extension_internal("GL_ARB_gpu_shader5"); |
| } |
| break; |
| |
| case ExecutionModelTessellationEvaluation: |
| case ExecutionModelTessellationControl: |
| if (options.es && options.version < 320) |
| require_extension_internal("GL_EXT_tessellation_shader"); |
| if (!options.es && options.version < 400) |
| require_extension_internal("GL_ARB_tessellation_shader"); |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (!pls_inputs.empty() || !pls_outputs.empty()) |
| require_extension_internal("GL_EXT_shader_pixel_local_storage"); |
| |
| if (options.separate_shader_objects && !options.es && options.version < 410) |
| require_extension_internal("GL_ARB_separate_shader_objects"); |
| } |
| |
| string CompilerGLSL::compile() |
| { |
| // Force a classic "C" locale, reverts when function returns |
| ClassicLocale classic_locale; |
| |
| if (options.vulkan_semantics) |
| backend.allow_precision_qualifiers = true; |
| backend.force_gl_in_out_block = true; |
| backend.supports_extensions = true; |
| |
| // Scan the SPIR-V to find trivial uses of extensions. |
| build_function_control_flow_graphs_and_analyze(); |
| find_static_extensions(); |
| fixup_image_load_store_access(); |
| update_active_builtins(); |
| analyze_image_and_sampler_usage(); |
| |
| uint32_t pass_count = 0; |
| do |
| { |
| if (pass_count >= 3) |
| SPIRV_CROSS_THROW("Over 3 compilation loops detected. Must be a bug!"); |
| |
| reset(); |
| |
| // Move constructor for this type is broken on GCC 4.9 ... |
| buffer = unique_ptr<ostringstream>(new ostringstream()); |
| |
| emit_header(); |
| emit_resources(); |
| |
| emit_function(get<SPIRFunction>(entry_point), Bitset()); |
| |
| pass_count++; |
| } while (force_recompile); |
| |
| // Entry point in GLSL is always main(). |
| get_entry_point().name = "main"; |
| |
| return buffer->str(); |
| } |
| |
| std::string CompilerGLSL::get_partial_source() |
| { |
| return buffer ? buffer->str() : "No compiled source available yet."; |
| } |
| |
| void CompilerGLSL::emit_header() |
| { |
| auto &execution = get_entry_point(); |
| statement("#version ", options.version, options.es && options.version > 100 ? " es" : ""); |
| |
| if (!options.es && options.version < 420) |
| { |
| // Needed for binding = # on UBOs, etc. |
| if (options.enable_420pack_extension) |
| { |
| statement("#ifdef GL_ARB_shading_language_420pack"); |
| statement("#extension GL_ARB_shading_language_420pack : require"); |
| statement("#endif"); |
| } |
| // Needed for: layout(early_fragment_tests) in; |
| if (execution.flags.get(ExecutionModeEarlyFragmentTests)) |
| require_extension_internal("GL_ARB_shader_image_load_store"); |
| } |
| |
| for (auto &ext : forced_extensions) |
| statement("#extension ", ext, " : require"); |
| |
| for (auto &header : header_lines) |
| statement(header); |
| |
| vector<string> inputs; |
| vector<string> outputs; |
| |
| switch (execution.model) |
| { |
| case ExecutionModelGeometry: |
| outputs.push_back(join("max_vertices = ", execution.output_vertices)); |
| if ((execution.flags.get(ExecutionModeInvocations)) && execution.invocations != 1) |
| inputs.push_back(join("invocations = ", execution.invocations)); |
| if (execution.flags.get(ExecutionModeInputPoints)) |
| inputs.push_back("points"); |
| if (execution.flags.get(ExecutionModeInputLines)) |
| inputs.push_back("lines"); |
| if (execution.flags.get(ExecutionModeInputLinesAdjacency)) |
| inputs.push_back("lines_adjacency"); |
| if (execution.flags.get(ExecutionModeTriangles)) |
| inputs.push_back("triangles"); |
| if (execution.flags.get(ExecutionModeInputTrianglesAdjacency)) |
| inputs.push_back("triangles_adjacency"); |
| if (execution.flags.get(ExecutionModeOutputTriangleStrip)) |
| outputs.push_back("triangle_strip"); |
| if (execution.flags.get(ExecutionModeOutputPoints)) |
| outputs.push_back("points"); |
| if (execution.flags.get(ExecutionModeOutputLineStrip)) |
| outputs.push_back("line_strip"); |
| break; |
| |
| case ExecutionModelTessellationControl: |
| if (execution.flags.get(ExecutionModeOutputVertices)) |
| outputs.push_back(join("vertices = ", execution.output_vertices)); |
| break; |
| |
| case ExecutionModelTessellationEvaluation: |
| if (execution.flags.get(ExecutionModeQuads)) |
| inputs.push_back("quads"); |
| if (execution.flags.get(ExecutionModeTriangles)) |
| inputs.push_back("triangles"); |
| if (execution.flags.get(ExecutionModeIsolines)) |
| inputs.push_back("isolines"); |
| if (execution.flags.get(ExecutionModePointMode)) |
| inputs.push_back("point_mode"); |
| |
| if (!execution.flags.get(ExecutionModeIsolines)) |
| { |
| if (execution.flags.get(ExecutionModeVertexOrderCw)) |
| inputs.push_back("cw"); |
| if (execution.flags.get(ExecutionModeVertexOrderCcw)) |
| inputs.push_back("ccw"); |
| } |
| |
| if (execution.flags.get(ExecutionModeSpacingFractionalEven)) |
| inputs.push_back("fractional_even_spacing"); |
| if (execution.flags.get(ExecutionModeSpacingFractionalOdd)) |
| inputs.push_back("fractional_odd_spacing"); |
| if (execution.flags.get(ExecutionModeSpacingEqual)) |
| inputs.push_back("equal_spacing"); |
| break; |
| |
| case ExecutionModelGLCompute: |
| { |
| if (execution.workgroup_size.constant != 0) |
| { |
| SpecializationConstant wg_x, wg_y, wg_z; |
| get_work_group_size_specialization_constants(wg_x, wg_y, wg_z); |
| |
| if (wg_x.id) |
| { |
| if (options.vulkan_semantics) |
| inputs.push_back(join("local_size_x_id = ", wg_x.constant_id)); |
| else |
| inputs.push_back(join("local_size_x = ", get<SPIRConstant>(wg_x.id).scalar())); |
| } |
| else |
| inputs.push_back(join("local_size_x = ", execution.workgroup_size.x)); |
| |
| if (wg_y.id) |
| { |
| if (options.vulkan_semantics) |
| inputs.push_back(join("local_size_y_id = ", wg_y.constant_id)); |
| else |
| inputs.push_back(join("local_size_y = ", get<SPIRConstant>(wg_y.id).scalar())); |
| } |
| else |
| inputs.push_back(join("local_size_y = ", execution.workgroup_size.y)); |
| |
| if (wg_z.id) |
| { |
| if (options.vulkan_semantics) |
| inputs.push_back(join("local_size_z_id = ", wg_z.constant_id)); |
| else |
| inputs.push_back(join("local_size_z = ", get<SPIRConstant>(wg_z.id).scalar())); |
| } |
| else |
| inputs.push_back(join("local_size_z = ", execution.workgroup_size.z)); |
| } |
| else |
| { |
| inputs.push_back(join("local_size_x = ", execution.workgroup_size.x)); |
| inputs.push_back(join("local_size_y = ", execution.workgroup_size.y)); |
| inputs.push_back(join("local_size_z = ", execution.workgroup_size.z)); |
| } |
| break; |
| } |
| |
| case ExecutionModelFragment: |
| if (options.es) |
| { |
| switch (options.fragment.default_float_precision) |
| { |
| case Options::Lowp: |
| statement("precision lowp float;"); |
| break; |
| |
| case Options::Mediump: |
| statement("precision mediump float;"); |
| break; |
| |
| case Options::Highp: |
| statement("precision highp float;"); |
| break; |
| |
| default: |
| break; |
| } |
| |
| switch (options.fragment.default_int_precision) |
| { |
| case Options::Lowp: |
| statement("precision lowp int;"); |
| break; |
| |
| case Options::Mediump: |
| statement("precision mediump int;"); |
| break; |
| |
| case Options::Highp: |
| statement("precision highp int;"); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| if (execution.flags.get(ExecutionModeEarlyFragmentTests)) |
| inputs.push_back("early_fragment_tests"); |
| if (execution.flags.get(ExecutionModeDepthGreater)) |
| inputs.push_back("depth_greater"); |
| if (execution.flags.get(ExecutionModeDepthLess)) |
| inputs.push_back("depth_less"); |
| |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (!inputs.empty()) |
| statement("layout(", merge(inputs), ") in;"); |
| if (!outputs.empty()) |
| statement("layout(", merge(outputs), ") out;"); |
| |
| statement(""); |
| } |
| |
| bool CompilerGLSL::type_is_empty(const SPIRType &type) |
| { |
| return type.basetype == SPIRType::Struct && type.member_types.empty(); |
| } |
| |
| void CompilerGLSL::emit_struct(SPIRType &type) |
| { |
| // Struct types can be stamped out multiple times |
| // with just different offsets, matrix layouts, etc ... |
| // Type-punning with these types is legal, which complicates things |
| // when we are storing struct and array types in an SSBO for example. |
| // If the type master is packed however, we can no longer assume that the struct declaration will be redundant. |
| if (type.type_alias != 0 && !has_decoration(type.type_alias, DecorationCPacked)) |
| return; |
| |
| add_resource_name(type.self); |
| auto name = type_to_glsl(type); |
| |
| statement(!backend.explicit_struct_type ? "struct " : "", name); |
| begin_scope(); |
| |
| type.member_name_cache.clear(); |
| |
| uint32_t i = 0; |
| bool emitted = false; |
| for (auto &member : type.member_types) |
| { |
| add_member_name(type, i); |
| emit_struct_member(type, member, i); |
| i++; |
| emitted = true; |
| } |
| |
| // Don't declare empty structs in GLSL, this is not allowed. |
| if (type_is_empty(type) && !backend.supports_empty_struct) |
| { |
| statement("int empty_struct_member;"); |
| emitted = true; |
| } |
| |
| end_scope_decl(); |
| |
| if (emitted) |
| statement(""); |
| } |
| |
| string CompilerGLSL::to_interpolation_qualifiers(const Bitset &flags) |
| { |
| string res; |
| //if (flags & (1ull << DecorationSmooth)) |
| // res += "smooth "; |
| if (flags.get(DecorationFlat)) |
| res += "flat "; |
| if (flags.get(DecorationNoPerspective)) |
| res += "noperspective "; |
| if (flags.get(DecorationCentroid)) |
| res += "centroid "; |
| if (flags.get(DecorationPatch)) |
| res += "patch "; |
| if (flags.get(DecorationSample)) |
| res += "sample "; |
| if (flags.get(DecorationInvariant)) |
| res += "invariant "; |
| if (flags.get(DecorationExplicitInterpAMD)) |
| res += "__explicitInterpAMD "; |
| |
| return res; |
| } |
| |
| string CompilerGLSL::layout_for_member(const SPIRType &type, uint32_t index) |
| { |
| if (is_legacy()) |
| return ""; |
| |
| bool is_block = meta[type.self].decoration.decoration_flags.get(DecorationBlock) || |
| meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock); |
| if (!is_block) |
| return ""; |
| |
| auto &memb = meta[type.self].members; |
| if (index >= memb.size()) |
| return ""; |
| auto &dec = memb[index]; |
| |
| vector<string> attr; |
| |
| // We can only apply layouts on members in block interfaces. |
| // This is a bit problematic because in SPIR-V decorations are applied on the struct types directly. |
| // This is not supported on GLSL, so we have to make the assumption that if a struct within our buffer block struct |
| // has a decoration, it was originally caused by a top-level layout() qualifier in GLSL. |
| // |
| // We would like to go from (SPIR-V style): |
| // |
| // struct Foo { layout(row_major) mat4 matrix; }; |
| // buffer UBO { Foo foo; }; |
| // |
| // to |
| // |
| // struct Foo { mat4 matrix; }; // GLSL doesn't support any layout shenanigans in raw struct declarations. |
| // buffer UBO { layout(row_major) Foo foo; }; // Apply the layout on top-level. |
| auto flags = combined_decoration_for_member(type, index); |
| |
| if (flags.get(DecorationRowMajor)) |
| attr.push_back("row_major"); |
| // We don't emit any global layouts, so column_major is default. |
| //if (flags & (1ull << DecorationColMajor)) |
| // attr.push_back("column_major"); |
| |
| if (dec.decoration_flags.get(DecorationLocation) && can_use_io_location(type.storage, true)) |
| attr.push_back(join("location = ", dec.location)); |
| |
| // DecorationCPacked is set by layout_for_variable earlier to mark that we need to emit offset qualifiers. |
| // This is only done selectively in GLSL as needed. |
| if (has_decoration(type.self, DecorationCPacked) && dec.decoration_flags.get(DecorationOffset)) |
| attr.push_back(join("offset = ", dec.offset)); |
| |
| if (attr.empty()) |
| return ""; |
| |
| string res = "layout("; |
| res += merge(attr); |
| res += ") "; |
| return res; |
| } |
| |
| const char *CompilerGLSL::format_to_glsl(spv::ImageFormat format) |
| { |
| if (options.es && is_desktop_only_format(format)) |
| SPIRV_CROSS_THROW("Attempting to use image format not supported in ES profile."); |
| |
| switch (format) |
| { |
| case ImageFormatRgba32f: |
| return "rgba32f"; |
| case ImageFormatRgba16f: |
| return "rgba16f"; |
| case ImageFormatR32f: |
| return "r32f"; |
| case ImageFormatRgba8: |
| return "rgba8"; |
| case ImageFormatRgba8Snorm: |
| return "rgba8_snorm"; |
| case ImageFormatRg32f: |
| return "rg32f"; |
| case ImageFormatRg16f: |
| return "rg16f"; |
| case ImageFormatRgba32i: |
| return "rgba32i"; |
| case ImageFormatRgba16i: |
| return "rgba16i"; |
| case ImageFormatR32i: |
| return "r32i"; |
| case ImageFormatRgba8i: |
| return "rgba8i"; |
| case ImageFormatRg32i: |
| return "rg32i"; |
| case ImageFormatRg16i: |
| return "rg16i"; |
| case ImageFormatRgba32ui: |
| return "rgba32ui"; |
| case ImageFormatRgba16ui: |
| return "rgba16ui"; |
| case ImageFormatR32ui: |
| return "r32ui"; |
| case ImageFormatRgba8ui: |
| return "rgba8ui"; |
| case ImageFormatRg32ui: |
| return "rg32ui"; |
| case ImageFormatRg16ui: |
| return "rg16ui"; |
| case ImageFormatR11fG11fB10f: |
| return "r11f_g11f_b10f"; |
| case ImageFormatR16f: |
| return "r16f"; |
| case ImageFormatRgb10A2: |
| return "rgb10_a2"; |
| case ImageFormatR8: |
| return "r8"; |
| case ImageFormatRg8: |
| return "rg8"; |
| case ImageFormatR16: |
| return "r16"; |
| case ImageFormatRg16: |
| return "rg16"; |
| case ImageFormatRgba16: |
| return "rgba16"; |
| case ImageFormatR16Snorm: |
| return "r16_snorm"; |
| case ImageFormatRg16Snorm: |
| return "rg16_snorm"; |
| case ImageFormatRgba16Snorm: |
| return "rgba16_snorm"; |
| case ImageFormatR8Snorm: |
| return "r8_snorm"; |
| case ImageFormatRg8Snorm: |
| return "rg8_snorm"; |
| case ImageFormatR8ui: |
| return "r8ui"; |
| case ImageFormatRg8ui: |
| return "rg8ui"; |
| case ImageFormatR16ui: |
| return "r16ui"; |
| case ImageFormatRgb10a2ui: |
| return "rgb10_a2ui"; |
| case ImageFormatR8i: |
| return "r8i"; |
| case ImageFormatRg8i: |
| return "rg8i"; |
| case ImageFormatR16i: |
| return "r16i"; |
| default: |
| case ImageFormatUnknown: |
| return nullptr; |
| } |
| } |
| |
| uint32_t CompilerGLSL::type_to_packed_base_size(const SPIRType &type, BufferPackingStandard) |
| { |
| switch (type.basetype) |
| { |
| case SPIRType::Double: |
| case SPIRType::Int64: |
| case SPIRType::UInt64: |
| return 8; |
| case SPIRType::Float: |
| case SPIRType::Int: |
| case SPIRType::UInt: |
| return 4; |
| case SPIRType::Half: |
| return 2; |
| |
| default: |
| SPIRV_CROSS_THROW("Unrecognized type in type_to_packed_base_size."); |
| } |
| } |
| |
| uint32_t CompilerGLSL::type_to_packed_alignment(const SPIRType &type, const Bitset &flags, |
| BufferPackingStandard packing) |
| { |
| if (!type.array.empty()) |
| { |
| uint32_t minimum_alignment = 1; |
| if (packing_is_vec4_padded(packing)) |
| minimum_alignment = 16; |
| |
| auto *tmp = &get<SPIRType>(type.parent_type); |
| while (!tmp->array.empty()) |
| tmp = &get<SPIRType>(tmp->parent_type); |
| |
| // Get the alignment of the base type, then maybe round up. |
| return max(minimum_alignment, type_to_packed_alignment(*tmp, flags, packing)); |
| } |
| |
| if (type.basetype == SPIRType::Struct) |
| { |
| // Rule 9. Structs alignments are maximum alignment of its members. |
| uint32_t alignment = 0; |
| for (uint32_t i = 0; i < type.member_types.size(); i++) |
| { |
| auto member_flags = meta[type.self].members.at(i).decoration_flags; |
| alignment = |
| max(alignment, type_to_packed_alignment(get<SPIRType>(type.member_types[i]), member_flags, packing)); |
| } |
| |
| // In std140, struct alignment is rounded up to 16. |
| if (packing_is_vec4_padded(packing)) |
| alignment = max(alignment, 16u); |
| |
| return alignment; |
| } |
| else |
| { |
| const uint32_t base_alignment = type_to_packed_base_size(type, packing); |
| |
| // Vectors are *not* aligned in HLSL, but there's an extra rule where vectors cannot straddle |
| // a vec4, this is handled outside since that part knows our current offset. |
| if (type.columns == 1 && packing_is_hlsl(packing)) |
| return base_alignment; |
| |
| // From 7.6.2.2 in GL 4.5 core spec. |
| // Rule 1 |
| if (type.vecsize == 1 && type.columns == 1) |
| return base_alignment; |
| |
| // Rule 2 |
| if ((type.vecsize == 2 || type.vecsize == 4) && type.columns == 1) |
| return type.vecsize * base_alignment; |
| |
| // Rule 3 |
| if (type.vecsize == 3 && type.columns == 1) |
| return 4 * base_alignment; |
| |
| // Rule 4 implied. Alignment does not change in std430. |
| |
| // Rule 5. Column-major matrices are stored as arrays of |
| // vectors. |
| if (flags.get(DecorationColMajor) && type.columns > 1) |
| { |
| if (packing_is_vec4_padded(packing)) |
| return 4 * base_alignment; |
| else if (type.vecsize == 3) |
| return 4 * base_alignment; |
| else |
| return type.vecsize * base_alignment; |
| } |
| |
| // Rule 6 implied. |
| |
| // Rule 7. |
| if (flags.get(DecorationRowMajor) && type.vecsize > 1) |
| { |
| if (packing_is_vec4_padded(packing)) |
| return 4 * base_alignment; |
| else if (type.columns == 3) |
| return 4 * base_alignment; |
| else |
| return type.columns * base_alignment; |
| } |
| |
| // Rule 8 implied. |
| } |
| |
| SPIRV_CROSS_THROW("Did not find suitable rule for type. Bogus decorations?"); |
| } |
| |
| uint32_t CompilerGLSL::type_to_packed_array_stride(const SPIRType &type, const Bitset &flags, |
| BufferPackingStandard packing) |
| { |
| // Array stride is equal to aligned size of the underlying type. |
| uint32_t parent = type.parent_type; |
| assert(parent); |
| |
| auto &tmp = get<SPIRType>(parent); |
| |
| uint32_t size = type_to_packed_size(tmp, flags, packing); |
| if (tmp.array.empty()) |
| { |
| uint32_t alignment = type_to_packed_alignment(type, flags, packing); |
| return (size + alignment - 1) & ~(alignment - 1); |
| } |
| else |
| { |
| // For multidimensional arrays, array stride always matches size of subtype. |
| // The alignment cannot change because multidimensional arrays are basically N * M array elements. |
| return size; |
| } |
| } |
| |
| uint32_t CompilerGLSL::type_to_packed_size(const SPIRType &type, const Bitset &flags, BufferPackingStandard packing) |
| { |
| if (!type.array.empty()) |
| { |
| return to_array_size_literal(type, uint32_t(type.array.size()) - 1) * |
| type_to_packed_array_stride(type, flags, packing); |
| } |
| |
| uint32_t size = 0; |
| |
| if (type.basetype == SPIRType::Struct) |
| { |
| uint32_t pad_alignment = 1; |
| |
| for (uint32_t i = 0; i < type.member_types.size(); i++) |
| { |
| auto member_flags = meta[type.self].members.at(i).decoration_flags; |
| auto &member_type = get<SPIRType>(type.member_types[i]); |
| |
| uint32_t packed_alignment = type_to_packed_alignment(member_type, member_flags, packing); |
| uint32_t alignment = max(packed_alignment, pad_alignment); |
| |
| // The next member following a struct member is aligned to the base alignment of the struct that came before. |
| // GL 4.5 spec, 7.6.2.2. |
| if (member_type.basetype == SPIRType::Struct) |
| pad_alignment = packed_alignment; |
| else |
| pad_alignment = 1; |
| |
| size = (size + alignment - 1) & ~(alignment - 1); |
| size += type_to_packed_size(member_type, member_flags, packing); |
| } |
| } |
| else |
| { |
| const uint32_t base_alignment = type_to_packed_base_size(type, packing); |
| |
| if (type.columns == 1) |
| size = type.vecsize * base_alignment; |
| |
| if (flags.get(DecorationColMajor) && type.columns > 1) |
| { |
| if (packing_is_vec4_padded(packing)) |
| size = type.columns * 4 * base_alignment; |
| else if (type.vecsize == 3) |
| size = type.columns * 4 * base_alignment; |
| else |
| size = type.columns * type.vecsize * base_alignment; |
| } |
| |
| if (flags.get(DecorationRowMajor) && type.vecsize > 1) |
| { |
| if (packing_is_vec4_padded(packing)) |
| size = type.vecsize * 4 * base_alignment; |
| else if (type.columns == 3) |
| size = type.vecsize * 4 * base_alignment; |
| else |
| size = type.vecsize * type.columns * base_alignment; |
| } |
| } |
| |
| return size; |
| } |
| |
| bool CompilerGLSL::buffer_is_packing_standard(const SPIRType &type, BufferPackingStandard packing, |
| uint32_t start_offset, uint32_t end_offset) |
| { |
| // This is very tricky and error prone, but try to be exhaustive and correct here. |
| // SPIR-V doesn't directly say if we're using std430 or std140. |
| // SPIR-V communicates this using Offset and ArrayStride decorations (which is what really matters), |
| // so we have to try to infer whether or not the original GLSL source was std140 or std430 based on this information. |
| // We do not have to consider shared or packed since these layouts are not allowed in Vulkan SPIR-V (they are useless anyways, and custom offsets would do the same thing). |
| // |
| // It is almost certain that we're using std430, but it gets tricky with arrays in particular. |
| // We will assume std430, but infer std140 if we can prove the struct is not compliant with std430. |
| // |
| // The only two differences between std140 and std430 are related to padding alignment/array stride |
| // in arrays and structs. In std140 they take minimum vec4 alignment. |
| // std430 only removes the vec4 requirement. |
| |
| uint32_t offset = 0; |
| uint32_t pad_alignment = 1; |
| |
| for (uint32_t i = 0; i < type.member_types.size(); i++) |
| { |
| auto &memb_type = get<SPIRType>(type.member_types[i]); |
| auto member_flags = meta[type.self].members.at(i).decoration_flags; |
| |
| // Verify alignment rules. |
| uint32_t packed_alignment = type_to_packed_alignment(memb_type, member_flags, packing); |
| uint32_t packed_size = type_to_packed_size(memb_type, member_flags, packing); |
| |
| if (packing_is_hlsl(packing)) |
| { |
| // If a member straddles across a vec4 boundary, alignment is actually vec4. |
| uint32_t begin_word = offset / 16; |
| uint32_t end_word = (offset + packed_size - 1) / 16; |
| if (begin_word != end_word) |
| packed_alignment = max(packed_alignment, 16u); |
| } |
| |
| uint32_t alignment = max(packed_alignment, pad_alignment); |
| offset = (offset + alignment - 1) & ~(alignment - 1); |
| |
| // Field is not in the specified range anymore and we can ignore any further fields. |
| if (offset >= end_offset) |
| break; |
| |
| // The next member following a struct member is aligned to the base alignment of the struct that came before. |
| // GL 4.5 spec, 7.6.2.2. |
| if (memb_type.basetype == SPIRType::Struct) |
| pad_alignment = packed_alignment; |
| else |
| pad_alignment = 1; |
| |
| // Only care about packing if we are in the given range |
| if (offset >= start_offset) |
| { |
| // We only care about offsets in std140, std430, etc ... |
| // For EnhancedLayout variants, we have the flexibility to choose our own offsets. |
| if (!packing_has_flexible_offset(packing)) |
| { |
| uint32_t actual_offset = type_struct_member_offset(type, i); |
| if (actual_offset != offset) // This cannot be the packing we're looking for. |
| return false; |
| } |
| |
| // Verify array stride rules. |
| if (!memb_type.array.empty() && type_to_packed_array_stride(memb_type, member_flags, packing) != |
| type_struct_member_array_stride(type, i)) |
| return false; |
| |
| // Verify that sub-structs also follow packing rules. |
| // We cannot use enhanced layouts on substructs, so they better be up to spec. |
| auto substruct_packing = packing_to_substruct_packing(packing); |
| |
| if (!memb_type.member_types.empty() && !buffer_is_packing_standard(memb_type, substruct_packing)) |
| return false; |
| } |
| |
| // Bump size. |
| offset += packed_size; |
| } |
| |
| return true; |
| } |
| |
| bool CompilerGLSL::can_use_io_location(StorageClass storage, bool block) |
| { |
| // Location specifiers are must have in SPIR-V, but they aren't really supported in earlier versions of GLSL. |
| // Be very explicit here about how to solve the issue. |
| if ((get_execution_model() != ExecutionModelVertex && storage == StorageClassInput) || |
| (get_execution_model() != ExecutionModelFragment && storage == StorageClassOutput)) |
| { |
| uint32_t minimum_desktop_version = block ? 440 : 410; |
| // ARB_enhanced_layouts vs ARB_separate_shader_objects ... |
| |
| if (!options.es && options.version < minimum_desktop_version && !options.separate_shader_objects) |
| return false; |
| else if (options.es && options.version < 310) |
| return false; |
| } |
| |
| if ((get_execution_model() == ExecutionModelVertex && storage == StorageClassInput) || |
| (get_execution_model() == ExecutionModelFragment && storage == StorageClassOutput)) |
| { |
| if (options.es && options.version < 300) |
| return false; |
| else if (!options.es && options.version < 330) |
| return false; |
| } |
| |
| if (storage == StorageClassUniform || storage == StorageClassUniformConstant) |
| { |
| if (options.es && options.version < 310) |
| return false; |
| else if (!options.es && options.version < 430) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| string CompilerGLSL::layout_for_variable(const SPIRVariable &var) |
| { |
| // FIXME: Come up with a better solution for when to disable layouts. |
| // Having layouts depend on extensions as well as which types |
| // of layouts are used. For now, the simple solution is to just disable |
| // layouts for legacy versions. |
| if (is_legacy()) |
| return ""; |
| |
| vector<string> attr; |
| |
| auto &dec = meta[var.self].decoration; |
| auto &type = get<SPIRType>(var.basetype); |
| auto flags = dec.decoration_flags; |
| auto typeflags = meta[type.self].decoration.decoration_flags; |
| |
| if (options.vulkan_semantics && var.storage == StorageClassPushConstant) |
| attr.push_back("push_constant"); |
| |
| if (flags.get(DecorationRowMajor)) |
| attr.push_back("row_major"); |
| if (flags.get(DecorationColMajor)) |
| attr.push_back("column_major"); |
| |
| if (options.vulkan_semantics) |
| { |
| if (flags.get(DecorationInputAttachmentIndex)) |
| attr.push_back(join("input_attachment_index = ", dec.input_attachment)); |
| } |
| |
| bool is_block = has_decoration(type.self, DecorationBlock); |
| if (flags.get(DecorationLocation) && can_use_io_location(var.storage, is_block)) |
| { |
| Bitset combined_decoration; |
| for (uint32_t i = 0; i < meta[type.self].members.size(); i++) |
| combined_decoration.merge_or(combined_decoration_for_member(type, i)); |
| |
| // If our members have location decorations, we don't need to |
| // emit location decorations at the top as well (looks weird). |
| if (!combined_decoration.get(DecorationLocation)) |
| attr.push_back(join("location = ", dec.location)); |
| } |
| |
| if (flags.get(DecorationIndex)) |
| attr.push_back(join("index = ", dec.index)); |
| |
| // Do not emit set = decoration in regular GLSL output, but |
| // we need to preserve it in Vulkan GLSL mode. |
| if (var.storage != StorageClassPushConstant) |
| { |
| if (flags.get(DecorationDescriptorSet) && options.vulkan_semantics) |
| attr.push_back(join("set = ", dec.set)); |
| } |
| |
| // GL 3.0/GLSL 1.30 is not considered legacy, but it doesn't have UBOs ... |
| bool can_use_buffer_blocks = (options.es && options.version >= 300) || (!options.es && options.version >= 140); |
| |
| bool can_use_binding; |
| if (options.es) |
| can_use_binding = options.version >= 310; |
| else |
| can_use_binding = options.enable_420pack_extension || (options.version >= 420); |
| |
| // Make sure we don't emit binding layout for a classic uniform on GLSL 1.30. |
| if (!can_use_buffer_blocks && var.storage == StorageClassUniform) |
| can_use_binding = false; |
| |
| if (can_use_binding && flags.get(DecorationBinding)) |
| attr.push_back(join("binding = ", dec.binding)); |
| |
| if (flags.get(DecorationOffset)) |
| attr.push_back(join("offset = ", dec.offset)); |
| |
| bool push_constant_block = options.vulkan_semantics && var.storage == StorageClassPushConstant; |
| bool ssbo_block = var.storage == StorageClassStorageBuffer || |
| (var.storage == StorageClassUniform && typeflags.get(DecorationBufferBlock)); |
| |
| // Instead of adding explicit offsets for every element here, just assume we're using std140 or std430. |
| // If SPIR-V does not comply with either layout, we cannot really work around it. |
| if (can_use_buffer_blocks && var.storage == StorageClassUniform && typeflags.get(DecorationBlock)) |
| { |
| if (buffer_is_packing_standard(type, BufferPackingStd140)) |
| attr.push_back("std140"); |
| else if (buffer_is_packing_standard(type, BufferPackingStd140EnhancedLayout)) |
| { |
| attr.push_back("std140"); |
| // Fallback time. We might be able to use the ARB_enhanced_layouts to deal with this difference, |
| // however, we can only use layout(offset) on the block itself, not any substructs, so the substructs better be the appropriate layout. |
| // Enhanced layouts seem to always work in Vulkan GLSL, so no need for extensions there. |
| if (options.es && !options.vulkan_semantics) |
| SPIRV_CROSS_THROW("Push constant block cannot be expressed as neither std430 nor std140. ES-targets do " |
| "not support GL_ARB_enhanced_layouts."); |
| if (!options.es && !options.vulkan_semantics && options.version < 440) |
| require_extension_internal("GL_ARB_enhanced_layouts"); |
| |
| // This is a very last minute to check for this, but use this unused decoration to mark that we should emit |
| // explicit offsets for this block type. |
| // layout_for_variable() will be called before the actual buffer emit. |
| // The alternative is a full pass before codegen where we deduce this decoration, |
| // but then we are just doing the exact same work twice, and more complexity. |
| set_decoration(type.self, DecorationCPacked); |
| } |
| else |
| { |
| SPIRV_CROSS_THROW("Uniform buffer cannot be expressed as std140, even with enhanced layouts. You can try " |
| "flattening this block to " |
| "support a more flexible layout."); |
| } |
| } |
| else if (can_use_buffer_blocks && (push_constant_block || ssbo_block)) |
| { |
| if (buffer_is_packing_standard(type, BufferPackingStd430)) |
| attr.push_back("std430"); |
| else if (buffer_is_packing_standard(type, BufferPackingStd140)) |
| attr.push_back("std140"); |
| else if (buffer_is_packing_standard(type, BufferPackingStd140EnhancedLayout)) |
| { |
| attr.push_back("std140"); |
| |
| // Fallback time. We might be able to use the ARB_enhanced_layouts to deal with this difference, |
| // however, we can only use layout(offset) on the block itself, not any substructs, so the substructs better be the appropriate layout. |
| // Enhanced layouts seem to always work in Vulkan GLSL, so no need for extensions there. |
| if (options.es && !options.vulkan_semantics) |
| SPIRV_CROSS_THROW("Push constant block cannot be expressed as neither std430 nor std140. ES-targets do " |
| "not support GL_ARB_enhanced_layouts."); |
| if (!options.es && !options.vulkan_semantics && options.version < 440) |
| require_extension_internal("GL_ARB_enhanced_layouts"); |
| |
| set_decoration(type.self, DecorationCPacked); |
| } |
| else if (buffer_is_packing_standard(type, BufferPackingStd430EnhancedLayout)) |
| { |
| attr.push_back("std430"); |
| if (options.es && !options.vulkan_semantics) |
| SPIRV_CROSS_THROW("Push constant block cannot be expressed as neither std430 nor std140. ES-targets do " |
| "not support GL_ARB_enhanced_layouts."); |
| if (!options.es && !options.vulkan_semantics && options.version < 440) |
| require_extension_internal("GL_ARB_enhanced_layouts"); |
| |
| set_decoration(type.self, DecorationCPacked); |
| } |
| else |
| { |
| SPIRV_CROSS_THROW("Buffer block cannot be expressed as neither std430 nor std140, even with enhanced " |
| "layouts. You can try flattening this block to support a more flexible layout."); |
| } |
| } |
| |
| // For images, the type itself adds a layout qualifer. |
| // Only emit the format for storage images. |
| if (type.basetype == SPIRType::Image && type.image.sampled == 2) |
| { |
| const char *fmt = format_to_glsl(type.image.format); |
| if (fmt) |
| attr.push_back(fmt); |
| } |
| |
| if (attr.empty()) |
| return ""; |
| |
| string res = "layout("; |
| res += merge(attr); |
| res += ") "; |
| return res; |
| } |
| |
| void CompilerGLSL::emit_push_constant_block(const SPIRVariable &var) |
| { |
| if (flattened_buffer_blocks.count(var.self)) |
| emit_buffer_block_flattened(var); |
| else if (options.vulkan_semantics) |
| emit_push_constant_block_vulkan(var); |
| else |
| emit_push_constant_block_glsl(var); |
| } |
| |
| void CompilerGLSL::emit_push_constant_block_vulkan(const SPIRVariable &var) |
| { |
| emit_buffer_block(var); |
| } |
| |
| void CompilerGLSL::emit_push_constant_block_glsl(const SPIRVariable &var) |
| { |
| // OpenGL has no concept of push constant blocks, implement it as a uniform struct. |
| auto &type = get<SPIRType>(var.basetype); |
| |
| auto &flags = meta[var.self].decoration.decoration_flags; |
| flags.clear(DecorationBinding); |
| flags.clear(DecorationDescriptorSet); |
| |
| #if 0 |
| if (flags & ((1ull << DecorationBinding) | (1ull << DecorationDescriptorSet))) |
| SPIRV_CROSS_THROW("Push constant blocks cannot be compiled to GLSL with Binding or Set syntax. " |
| "Remap to location with reflection API first or disable these decorations."); |
| #endif |
| |
| // We're emitting the push constant block as a regular struct, so disable the block qualifier temporarily. |
| // Otherwise, we will end up emitting layout() qualifiers on naked structs which is not allowed. |
| auto &block_flags = meta[type.self].decoration.decoration_flags; |
| bool block_flag = block_flags.get(DecorationBlock); |
| block_flags.clear(DecorationBlock); |
| |
| emit_struct(type); |
| |
| if (block_flag) |
| block_flags.set(DecorationBlock); |
| |
| emit_uniform(var); |
| statement(""); |
| } |
| |
| void CompilerGLSL::emit_buffer_block(const SPIRVariable &var) |
| { |
| if (flattened_buffer_blocks.count(var.self)) |
| emit_buffer_block_flattened(var); |
| else if (is_legacy() || (!options.es && options.version == 130)) |
| emit_buffer_block_legacy(var); |
| else |
| emit_buffer_block_native(var); |
| } |
| |
| void CompilerGLSL::emit_buffer_block_legacy(const SPIRVariable &var) |
| { |
| auto &type = get<SPIRType>(var.basetype); |
| bool ssbo = var.storage == StorageClassStorageBuffer || |
| meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock); |
| if (ssbo) |
| SPIRV_CROSS_THROW("SSBOs not supported in legacy targets."); |
| |
| // We're emitting the push constant block as a regular struct, so disable the block qualifier temporarily. |
| // Otherwise, we will end up emitting layout() qualifiers on naked structs which is not allowed. |
| auto &block_flags = meta[type.self].decoration.decoration_flags; |
| bool block_flag = block_flags.get(DecorationBlock); |
| block_flags.clear(DecorationBlock); |
| emit_struct(type); |
| if (block_flag) |
| block_flags.set(DecorationBlock); |
| emit_uniform(var); |
| statement(""); |
| } |
| |
| void CompilerGLSL::emit_buffer_block_native(const SPIRVariable &var) |
| { |
| auto &type = get<SPIRType>(var.basetype); |
| |
| Bitset flags = get_buffer_block_flags(var); |
| bool ssbo = var.storage == StorageClassStorageBuffer || |
| meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock); |
| bool is_restrict = ssbo && flags.get(DecorationRestrict); |
| bool is_writeonly = ssbo && flags.get(DecorationNonReadable); |
| bool is_readonly = ssbo && flags.get(DecorationNonWritable); |
| bool is_coherent = ssbo && flags.get(DecorationCoherent); |
| |
| // Block names should never alias, but from HLSL input they kind of can because block types are reused for UAVs ... |
| auto buffer_name = to_name(type.self, false); |
| |
| // Shaders never use the block by interface name, so we don't |
| // have to track this other than updating name caches. |
| if (meta[type.self].decoration.alias.empty() || resource_names.find(buffer_name) != end(resource_names)) |
| buffer_name = get_block_fallback_name(var.self); |
| |
| // Make sure we get something unique. |
| add_variable(resource_names, buffer_name); |
| |
| // If for some reason buffer_name is an illegal name, make a final fallback to a workaround name. |
| // This cannot conflict with anything else, so we're safe now. |
| if (buffer_name.empty()) |
| buffer_name = join("_", get<SPIRType>(var.basetype).self, "_", var.self); |
| |
| // Save for post-reflection later. |
| declared_block_names[var.self] = buffer_name; |
| |
| statement(layout_for_variable(var), is_coherent ? "coherent " : "", is_restrict ? "restrict " : "", |
| is_writeonly ? "writeonly " : "", is_readonly ? "readonly " : "", ssbo ? "buffer " : "uniform ", |
| buffer_name); |
| |
| begin_scope(); |
| |
| type.member_name_cache.clear(); |
| |
| uint32_t i = 0; |
| for (auto &member : type.member_types) |
| { |
| add_member_name(type, i); |
| emit_struct_member(type, member, i); |
| i++; |
| } |
| |
| add_resource_name(var.self); |
| end_scope_decl(to_name(var.self) + type_to_array_glsl(type)); |
| statement(""); |
| } |
| |
| void CompilerGLSL::emit_buffer_block_flattened(const SPIRVariable &var) |
| { |
| auto &type = get<SPIRType>(var.basetype); |
| |
| // Block names should never alias. |
| auto buffer_name = to_name(type.self, false); |
| size_t buffer_size = (get_declared_struct_size(type) + 15) / 16; |
| |
| SPIRType::BaseType basic_type; |
| if (get_common_basic_type(type, basic_type)) |
| { |
| SPIRType tmp; |
| tmp.basetype = basic_type; |
| tmp.vecsize = 4; |
| if (basic_type != SPIRType::Float && basic_type != SPIRType::Int && basic_type != SPIRType::UInt) |
| SPIRV_CROSS_THROW("Basic types in a flattened UBO must be float, int or uint."); |
| |
| auto flags = get_buffer_block_flags(var); |
| statement("uniform ", flags_to_precision_qualifiers_glsl(tmp, flags), type_to_glsl(tmp), " ", buffer_name, "[", |
| buffer_size, "];"); |
| } |
| else |
| SPIRV_CROSS_THROW("All basic types in a flattened block must be the same."); |
| } |
| |
| const char *CompilerGLSL::to_storage_qualifiers_glsl(const SPIRVariable &var) |
| { |
| auto &execution = get_entry_point(); |
| |
| if (var.storage == StorageClassInput || var.storage == StorageClassOutput) |
| { |
| if (is_legacy() && execution.model == ExecutionModelVertex) |
| return var.storage == StorageClassInput ? "attribute " : "varying "; |
| else if (is_legacy() && execution.model == ExecutionModelFragment) |
| return "varying "; // Fragment outputs are renamed so they never hit this case. |
| else |
| return var.storage == StorageClassInput ? "in " : "out "; |
| } |
| else if (var.storage == StorageClassUniformConstant || var.storage == StorageClassUniform || |
| var.storage == StorageClassPushConstant) |
| { |
| return "uniform "; |
| } |
| |
| return ""; |
| } |
| |
| void CompilerGLSL::emit_flattened_io_block(const SPIRVariable &var, const char *qual) |
| { |
| auto &type = get<SPIRType>(var.basetype); |
| if (!type.array.empty()) |
| SPIRV_CROSS_THROW("Array of varying structs cannot be flattened to legacy-compatible varyings."); |
| |
| auto old_flags = meta[type.self].decoration.decoration_flags; |
| // Emit the members as if they are part of a block to get all qualifiers. |
| meta[type.self].decoration.decoration_flags.set(DecorationBlock); |
| |
| type.member_name_cache.clear(); |
| |
| uint32_t i = 0; |
| for (auto &member : type.member_types) |
| { |
| add_member_name(type, i); |
| auto &membertype = get<SPIRType>(member); |
| |
| if (membertype.basetype == SPIRType::Struct) |
| SPIRV_CROSS_THROW("Cannot flatten struct inside structs in I/O variables."); |
| |
| // Pass in the varying qualifier here so it will appear in the correct declaration order. |
| // Replace member name while emitting it so it encodes both struct name and member name. |
| // Sanitize underscores because joining the two identifiers might create more than 1 underscore in a row, |
| // which is not allowed. |
| auto backup_name = get_member_name(type.self, i); |
| auto member_name = to_member_name(type, i); |
| set_member_name(type.self, i, sanitize_underscores(join(to_name(var.self), "_", member_name))); |
| emit_struct_member(type, member, i, qual); |
| // Restore member name. |
| set_member_name(type.self, i, member_name); |
| i++; |
| } |
| |
| meta[type.self].decoration.decoration_flags = old_flags; |
| |
| // Treat this variable as flattened from now on. |
| flattened_structs.insert(var.self); |
| } |
| |
| void CompilerGLSL::emit_interface_block(const SPIRVariable &var) |
| { |
| auto &type = get<SPIRType>(var.basetype); |
| |
| // Either make it plain in/out or in/out blocks depending on what shader is doing ... |
| bool block = meta[type.self].decoration.decoration_flags.get(DecorationBlock); |
| const char *qual = to_storage_qualifiers_glsl(var); |
| |
| if (block) |
| { |
| // ESSL earlier than 310 and GLSL earlier than 150 did not support |
| // I/O variables which are struct types. |
| // To support this, flatten the struct into separate varyings instead. |
| if ((options.es && options.version < 310) || (!options.es && options.version < 150)) |
| { |
| // I/O blocks on ES require version 310 with Android Extension Pack extensions, or core version 320. |
| // On desktop, I/O blocks were introduced with geometry shaders in GL 3.2 (GLSL 150). |
| emit_flattened_io_block(var, qual); |
| } |
| else |
| { |
| if (options.es && options.version < 320) |
| { |
| // Geometry and tessellation extensions imply this extension. |
| if (!has_extension("GL_EXT_geometry_shader") && !has_extension("GL_EXT_tessellation_shader")) |
| require_extension_internal("GL_EXT_shader_io_blocks"); |
| } |
| |
| // Block names should never alias. |
| auto block_name = to_name(type.self, false); |
| |
| // Shaders never use the block by interface name, so we don't |
| // have to track this other than updating name caches. |
| if (resource_names.find(block_name) != end(resource_names)) |
| block_name = get_fallback_name(type.self); |
| else |
| resource_names.insert(block_name); |
| |
| statement(layout_for_variable(var), qual, block_name); |
| begin_scope(); |
| |
| type.member_name_cache.clear(); |
| |
| uint32_t i = 0; |
| for (auto &member : type.member_types) |
| { |
| add_member_name(type, i); |
| emit_struct_member(type, member, i); |
| i++; |
| } |
| |
| add_resource_name(var.self); |
| end_scope_decl(join(to_name(var.self), type_to_array_glsl(type))); |
| statement(""); |
| } |
| } |
| else |
| { |
| // ESSL earlier than 310 and GLSL earlier than 150 did not support |
| // I/O variables which are struct types. |
| // To support this, flatten the struct into separate varyings instead. |
| if (type.basetype == SPIRType::Struct && |
| ((options.es && options.version < 310) || (!options.es && options.version < 150))) |
| { |
| emit_flattened_io_block(var, qual); |
| } |
| else |
| { |
| add_resource_name(var.self); |
| statement(layout_for_variable(var), variable_decl(var), ";"); |
| } |
| } |
| } |
| |
| void CompilerGLSL::emit_uniform(const SPIRVariable &var) |
| { |
| auto &type = get<SPIRType>(var.basetype); |
| if (type.basetype == SPIRType::Image && type.image.sampled == 2) |
| { |
| if (!options.es && options.version < 420) |
| require_extension_internal("GL_ARB_shader_image_load_store"); |
| else if (options.es && options.version < 310) |
| SPIRV_CROSS_THROW("At least ESSL 3.10 required for shader image load store."); |
| } |
| |
| add_resource_name(var.self); |
| statement(layout_for_variable(var), variable_decl(var), ";"); |
| } |
| |
| void CompilerGLSL::emit_specialization_constant_op(const SPIRConstantOp &constant) |
| { |
| auto &type = get<SPIRType>(constant.basetype); |
| auto name = to_name(constant.self); |
| statement("const ", variable_decl(type, name), " = ", constant_op_expression(constant), ";"); |
| } |
| |
| void CompilerGLSL::emit_constant(const SPIRConstant &constant) |
| { |
| auto &type = get<SPIRType>(constant.constant_type); |
| auto name = to_name(constant.self); |
| |
| SpecializationConstant wg_x, wg_y, wg_z; |
| uint32_t workgroup_size_id = get_work_group_size_specialization_constants(wg_x, wg_y, wg_z); |
| |
| if (constant.self == workgroup_size_id || constant.self == wg_x.id || constant.self == wg_y.id || |
| constant.self == wg_z.id) |
| { |
| // These specialization constants are implicitly declared by emitting layout() in; |
| return; |
| } |
| |
| // Only scalars have constant IDs. |
| if (has_decoration(constant.self, DecorationSpecId)) |
| { |
| statement("layout(constant_id = ", get_decoration(constant.self, DecorationSpecId), ") const ", |
| variable_decl(type, name), " = ", constant_expression(constant), ";"); |
| } |
| else |
| { |
| statement("const ", variable_decl(type, name), " = ", constant_expression(constant), ";"); |
| } |
| } |
| |
| void CompilerGLSL::emit_entry_point_declarations() |
| { |
| } |
| |
| void CompilerGLSL::replace_illegal_names() |
| { |
| // clang-format off |
| static const unordered_set<string> keywords = { |
| "abs", "acos", "acosh", "all", "any", "asin", "asinh", "atan", "atanh", |
| "atomicAdd", "atomicCompSwap", "atomicCounter", "atomicCounterDecrement", "atomicCounterIncrement", |
| "atomicExchange", "atomicMax", "atomicMin", "atomicOr", "atomicXor", |
| "bitCount", "bitfieldExtract", "bitfieldInsert", "bitfieldReverse", |
| "ceil", "cos", "cosh", "cross", "degrees", |
| "dFdx", "dFdxCoarse", "dFdxFine", |
| "dFdy", "dFdyCoarse", "dFdyFine", |
| "distance", "dot", "EmitStreamVertex", "EmitVertex", "EndPrimitive", "EndStreamPrimitive", "equal", "exp", "exp2", |
| "faceforward", "findLSB", "findMSB", "floatBitsToInt", "floatBitsToUint", "floor", "fma", "fract", "frexp", "fwidth", "fwidthCoarse", "fwidthFine", |
| "greaterThan", "greaterThanEqual", "groupMemoryBarrier", |
| "imageAtomicAdd", "imageAtomicAnd", "imageAtomicCompSwap", "imageAtomicExchange", "imageAtomicMax", "imageAtomicMin", "imageAtomicOr", "imageAtomicXor", |
| "imageLoad", "imageSamples", "imageSize", "imageStore", "imulExtended", "intBitsToFloat", "interpolateAtOffset", "interpolateAtCentroid", "interpolateAtSample", |
| "inverse", "inversesqrt", "isinf", "isnan", "ldexp", "length", "lessThan", "lessThanEqual", "log", "log2", |
| "matrixCompMult", "max", "memoryBarrier", "memoryBarrierAtomicCounter", "memoryBarrierBuffer", "memoryBarrierImage", "memoryBarrierShared", |
| "min", "mix", "mod", "modf", "noise", "noise1", "noise2", "noise3", "noise4", "normalize", "not", "notEqual", |
| "outerProduct", "packDouble2x32", "packHalf2x16", "packSnorm2x16", "packSnorm4x8", "packUnorm2x16", "packUnorm4x8", "pow", |
| "radians", "reflect", "refract", "round", "roundEven", "sign", "sin", "sinh", "smoothstep", "sqrt", "step", |
| "tan", "tanh", "texelFetch", "texelFetchOffset", "texture", "textureGather", "textureGatherOffset", "textureGatherOffsets", |
| "textureGrad", "textureGradOffset", "textureLod", "textureLodOffset", "textureOffset", "textureProj", "textureProjGrad", |
| "textureProjGradOffset", "textureProjLod", "textureProjLodOffset", "textureProjOffset", "textureQueryLevels", "textureQueryLod", "textureSamples", "textureSize", |
| "transpose", "trunc", "uaddCarry", "uintBitsToFloat", "umulExtended", "unpackDouble2x32", "unpackHalf2x16", "unpackSnorm2x16", "unpackSnorm4x8", |
| "unpackUnorm2x16", "unpackUnorm4x8", "usubBorrow", |
| |
| "active", "asm", "atomic_uint", "attribute", "bool", "break", "buffer", |
| "bvec2", "bvec3", "bvec4", "case", "cast", "centroid", "class", "coherent", "common", "const", "continue", "default", "discard", |
| "dmat2", "dmat2x2", "dmat2x3", "dmat2x4", "dmat3", "dmat3x2", "dmat3x3", "dmat3x4", "dmat4", "dmat4x2", "dmat4x3", "dmat4x4", |
| "do", "double", "dvec2", "dvec3", "dvec4", "else", "enum", "extern", "external", "false", "filter", "fixed", "flat", "float", |
| "for", "fvec2", "fvec3", "fvec4", "goto", "half", "highp", "hvec2", "hvec3", "hvec4", "if", "iimage1D", "iimage1DArray", |
| "iimage2D", "iimage2DArray", "iimage2DMS", "iimage2DMSArray", "iimage2DRect", "iimage3D", "iimageBuffer", "iimageCube", |
| "iimageCubeArray", "image1D", "image1DArray", "image2D", "image2DArray", "image2DMS", "image2DMSArray", "image2DRect", |
| "image3D", "imageBuffer", "imageCube", "imageCubeArray", "in", "inline", "inout", "input", "int", "interface", "invariant", |
| "isampler1D", "isampler1DArray", "isampler2D", "isampler2DArray", "isampler2DMS", "isampler2DMSArray", "isampler2DRect", |
| "isampler3D", "isamplerBuffer", "isamplerCube", "isamplerCubeArray", "ivec2", "ivec3", "ivec4", "layout", "long", "lowp", |
| "mat2", "mat2x2", "mat2x3", "mat2x4", "mat3", "mat3x2", "mat3x3", "mat3x4", "mat4", "mat4x2", "mat4x3", "mat4x4", "mediump", |
| "namespace", "noinline", "noperspective", "out", "output", "packed", "partition", "patch", "precise", "precision", "public", "readonly", |
| "resource", "restrict", "return", "sample", "sampler1D", "sampler1DArray", "sampler1DArrayShadow", |
| "sampler1DShadow", "sampler2D", "sampler2DArray", "sampler2DArrayShadow", "sampler2DMS", "sampler2DMSArray", |
| "sampler2DRect", "sampler2DRectShadow", "sampler2DShadow", "sampler3D", "sampler3DRect", "samplerBuffer", |
| "samplerCube", "samplerCubeArray", "samplerCubeArrayShadow", "samplerCubeShadow", "shared", "short", "sizeof", "smooth", "static", |
| "struct", "subroutine", "superp", "switch", "template", "this", "true", "typedef", "uimage1D", "uimage1DArray", "uimage2D", |
| "uimage2DArray", "uimage2DMS", "uimage2DMSArray", "uimage2DRect", "uimage3D", "uimageBuffer", "uimageCube", |
| "uimageCubeArray", "uint", "uniform", "union", "unsigned", "usampler1D", "usampler1DArray", "usampler2D", "usampler2DArray", |
| "usampler2DMS", "usampler2DMSArray", "usampler2DRect", "usampler3D", "usamplerBuffer", "usamplerCube", |
| "usamplerCubeArray", "using", "uvec2", "uvec3", "uvec4", "varying", "vec2", "vec3", "vec4", "void", "volatile", |
| "while", "writeonly", |
| }; |
| // clang-format on |
| |
| for (auto &id : ids) |
| { |
| if (id.get_type() == TypeVariable) |
| { |
| auto &var = id.get<SPIRVariable>(); |
| if (!is_hidden_variable(var)) |
| { |
| auto &m = meta[var.self].decoration; |
| if (m.alias.compare(0, 3, "gl_") == 0 || keywords.find(m.alias) != end(keywords)) |
| m.alias = join("_", m.alias); |
| } |
| } |
| } |
| } |
| |
| void CompilerGLSL::replace_fragment_output(SPIRVariable &var) |
| { |
| auto &m = meta[var.self].decoration; |
| uint32_t location = 0; |
| if (m.decoration_flags.get(DecorationLocation)) |
| location = m.location; |
| |
| // If our variable is arrayed, we must not emit the array part of this as the SPIR-V will |
| // do the access chain part of this for us. |
| auto &type = get<SPIRType>(var.basetype); |
| |
| if (type.array.empty()) |
| { |
| // Redirect the write to a specific render target in legacy GLSL. |
| m.alias = join("gl_FragData[", location, "]"); |
| |
| if (is_legacy_es() && location != 0) |
| require_extension_internal("GL_EXT_draw_buffers"); |
| } |
| else if (type.array.size() == 1) |
| { |
| // If location is non-zero, we probably have to add an offset. |
| // This gets really tricky since we'd have to inject an offset in the access chain. |
| // FIXME: This seems like an extremely odd-ball case, so it's probably fine to leave it like this for now. |
| m.alias = "gl_FragData"; |
| if (location != 0) |
| SPIRV_CROSS_THROW("Arrayed output variable used, but location is not 0. " |
| "This is unimplemented in SPIRV-Cross."); |
| |
| if (is_legacy_es()) |
| require_extension_internal("GL_EXT_draw_buffers"); |
| } |
| else |
| SPIRV_CROSS_THROW("Array-of-array output variable used. This cannot be implemented in legacy GLSL."); |
| |
| var.compat_builtin = true; // We don't want to declare this variable, but use the name as-is. |
| } |
| |
| void CompilerGLSL::replace_fragment_outputs() |
| { |
| for (auto &id : ids) |
| { |
| if (id.get_type() == TypeVariable) |
| { |
| auto &var = id.get<SPIRVariable>(); |
| auto &type = get<SPIRType>(var.basetype); |
| |
| if (!is_builtin_variable(var) && !var.remapped_variable && type.pointer && |
| var.storage == StorageClassOutput) |
| replace_fragment_output(var); |
| } |
| } |
| } |
| |
| string CompilerGLSL::remap_swizzle(const SPIRType &out_type, uint32_t input_components, const string &expr) |
| { |
| if (out_type.vecsize == input_components) |
| return expr; |
| else if (input_components == 1 && !backend.can_swizzle_scalar) |
| return join(type_to_glsl(out_type), "(", expr, ")"); |
| else |
| { |
| // FIXME: This will not work with packed expressions. |
| auto e = enclose_expression(expr) + "."; |
| // Just clamp the swizzle index if we have more outputs than inputs. |
| for (uint32_t c = 0; c < out_type.vecsize; c++) |
| e += index_to_swizzle(min(c, input_components - 1)); |
| if (backend.swizzle_is_function && out_type.vecsize > 1) |
| e += "()"; |
| |
| remove_duplicate_swizzle(e); |
| return e; |
| } |
| } |
| |
| void CompilerGLSL::emit_pls() |
| { |
| auto &execution = get_entry_point(); |
| if (execution.model != ExecutionModelFragment) |
| SPIRV_CROSS_THROW("Pixel local storage only supported in fragment shaders."); |
| |
| if (!options.es) |
| SPIRV_CROSS_THROW("Pixel local storage only supported in OpenGL ES."); |
| |
| if (options.version < 300) |
| SPIRV_CROSS_THROW("Pixel local storage only supported in ESSL 3.0 and above."); |
| |
| if (!pls_inputs.empty()) |
| { |
| statement("__pixel_local_inEXT _PLSIn"); |
| begin_scope(); |
| for (auto &input : pls_inputs) |
| statement(pls_decl(input), ";"); |
| end_scope_decl(); |
| statement(""); |
| } |
| |
| if (!pls_outputs.empty()) |
| { |
| statement("__pixel_local_outEXT _PLSOut"); |
| begin_scope(); |
| for (auto &output : pls_outputs) |
| statement(pls_decl(output), ";"); |
| end_scope_decl(); |
| statement(""); |
| } |
| } |
| |
| void CompilerGLSL::fixup_image_load_store_access() |
| { |
| for (auto &id : ids) |
| { |
| if (id.get_type() != TypeVariable) |
| continue; |
| |
| uint32_t var = id.get<SPIRVariable>().self; |
| auto &vartype = expression_type(var); |
| if (vartype.basetype == SPIRType::Image) |
| { |
| // Older glslangValidator does not emit required qualifiers here. |
| // Solve this by making the image access as restricted as possible and loosen up if we need to. |
| // If any no-read/no-write flags are actually set, assume that the compiler knows what it's doing. |
| |
| auto &flags = meta.at(var).decoration.decoration_flags; |
| if (!flags.get(DecorationNonWritable) && !flags.get(DecorationNonReadable)) |
| { |
| flags.set(DecorationNonWritable); |
| flags.set(DecorationNonReadable); |
| } |
| } |
| } |
| } |
| |
| void CompilerGLSL::emit_declared_builtin_block(StorageClass storage, ExecutionModel model) |
| { |
| Bitset emitted_builtins; |
| Bitset global_builtins; |
| const SPIRVariable *block_var = nullptr; |
| bool emitted_block = false; |
| bool builtin_array = false; |
| |
| // Need to use declared size in the type. |
| // These variables might have been declared, but not statically used, so we haven't deduced their size yet. |
| uint32_t cull_distance_size = 0; |
| uint32_t clip_distance_size = 0; |
| |
| for (auto &id : ids) |
| { |
| if (id.get_type() != TypeVariable) |
| continue; |
| |
| auto &var = id.get<SPIRVariable>(); |
| auto &type = get<SPIRType>(var.basetype); |
| bool block = has_decoration(type.self, DecorationBlock); |
| Bitset builtins; |
| |
| if (var.storage == storage && block && is_builtin_variable(var)) |
| { |
| uint32_t index = 0; |
| for (auto &m : meta[type.self].members) |
| { |
| if (m.builtin) |
| { |
| builtins.set(m.builtin_type); |
| if (m.builtin_type == BuiltInCullDistance) |
| cull_distance_size = get<SPIRType>(type.member_types[index]).array.front(); |
| else if (m.builtin_type == BuiltInClipDistance) |
| clip_distance_size = get<SPIRType>(type.member_types[index]).array.front(); |
| } |
| index++; |
| } |
| } |
| else if (var.storage == storage && !block && is_builtin_variable(var)) |
| { |
| // While we're at it, collect all declared global builtins (HLSL mostly ...). |
| auto &m = meta[var.self].decoration; |
| if (m.builtin) |
| { |
| global_builtins.set(m.builtin_type); |
| if (m.builtin_type == BuiltInCullDistance) |
| cull_distance_size = type.array.front(); |
| else if (m.builtin_type == BuiltInClipDistance) |
| clip_distance_size = type.array.front(); |
| } |
| } |
| |
| if (builtins.empty()) |
| continue; |
| |
| if (emitted_block) |
| SPIRV_CROSS_THROW("Cannot use more than one builtin I/O block."); |
| |
| emitted_builtins = builtins; |
| emitted_block = true; |
| builtin_array = !type.array.empty(); |
| block_var = &var; |
| } |
| |
| global_builtins = |
| Bitset(global_builtins.get_lower() & ((1ull << BuiltInPosition) | (1ull << BuiltInPointSize) | |
| (1ull << BuiltInClipDistance) | (1ull << BuiltInCullDistance))); |
| |
| // Try to collect all other declared builtins. |
| if (!emitted_block) |
| emitted_builtins = global_builtins; |
| |
| // Can't declare an empty interface block. |
| if (emitted_builtins.empty()) |
| return; |
| |
| if (storage == StorageClassOutput) |
| statement("out gl_PerVertex"); |
| else |
| statement("in gl_PerVertex"); |
| |
| begin_scope(); |
| if (emitted_builtins.get(BuiltInPosition)) |
| statement("vec4 gl_Position;"); |
| if (emitted_builtins.get(BuiltInPointSize)) |
| statement("float gl_PointSize;"); |
| if (emitted_builtins.get(BuiltInClipDistance)) |
| statement("float gl_ClipDistance[", clip_distance_size, "];"); |
| if (emitted_builtins.get(BuiltInCullDistance)) |
| statement("float gl_CullDistance[", cull_distance_size, "];"); |
| |
| bool tessellation = model == ExecutionModelTessellationEvaluation || model == ExecutionModelTessellationControl; |
| if (builtin_array) |
| { |
| // Make sure the array has a supported name in the code. |
| if (storage == StorageClassOutput) |
| set_name(block_var->self, "gl_out"); |
| else if (storage == StorageClassInput) |
| set_name(block_var->self, "gl_in"); |
| |
| if (model == ExecutionModelTessellationControl && storage == StorageClassOutput) |
| end_scope_decl(join(to_name(block_var->self), "[", get_entry_point().output_vertices, "]")); |
| else |
| end_scope_decl(join(to_name(block_var->self), tessellation ? "[gl_MaxPatchVertices]" : "[]")); |
| } |
| else |
| end_scope_decl(); |
| statement(""); |
| } |
| |
| void CompilerGLSL::declare_undefined_values() |
| { |
| bool emitted = false; |
| for (auto &id : ids) |
| { |
| if (id.get_type() != TypeUndef) |
| continue; |
| |
| auto &undef = id.get<SPIRUndef>(); |
| statement(variable_decl(get<SPIRType>(undef.basetype), to_name(undef.self), undef.self), ";"); |
| emitted = true; |
| } |
| |
| if (emitted) |
| statement(""); |
| } |
| |
| void CompilerGLSL::emit_resources() |
| { |
| auto &execution = get_entry_point(); |
| |
| replace_illegal_names(); |
| |
| // Legacy GL uses gl_FragData[], redeclare all fragment outputs |
| // with builtins. |
| if (execution.model == ExecutionModelFragment && is_legacy()) |
| replace_fragment_outputs(); |
| |
| // Emit PLS blocks if we have such variables. |
| if (!pls_inputs.empty() || !pls_outputs.empty()) |
| emit_pls(); |
| |
| // Emit custom gl_PerVertex for SSO compatibility. |
| if (options.separate_shader_objects && !options.es && execution.model != ExecutionModelFragment) |
| { |
| switch (execution.model) |
| { |
| case ExecutionModelGeometry: |
| case ExecutionModelTessellationControl: |
| case ExecutionModelTessellationEvaluation: |
| emit_declared_builtin_block(StorageClassInput, execution.model); |
| emit_declared_builtin_block(StorageClassOutput, execution.model); |
| break; |
| |
| case ExecutionModelVertex: |
| emit_declared_builtin_block(StorageClassOutput, execution.model); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| else |
| { |
| // Need to redeclare clip/cull distance with explicit size to use them. |
| // SPIR-V mandates these builtins have a size declared. |
| const char *storage = execution.model == ExecutionModelFragment ? "in" : "out"; |
| if (clip_distance_count != 0) |
| statement(storage, " float gl_ClipDistance[", clip_distance_count, "];"); |
| if (cull_distance_count != 0) |
| statement(storage, " float gl_CullDistance[", cull_distance_count, "];"); |
| if (clip_distance_count != 0 || cull_distance_count != 0) |
| statement(""); |
| } |
| |
| if (position_invariant) |
| { |
| statement("invariant gl_Position;"); |
| statement(""); |
| } |
| |
| bool emitted = false; |
| |
| // If emitted Vulkan GLSL, |
| // emit specialization constants as actual floats, |
| // spec op expressions will redirect to the constant name. |
| // |
| // TODO: If we have the fringe case that we create a spec constant which depends on a struct type, |
| // we'll have to deal with that, but there's currently no known way to express that. |
| for (auto &id : ids) |
| { |
| if (id.get_type() == TypeConstant) |
| { |
| auto &c = id.get<SPIRConstant>(); |
| |
| bool needs_declaration = (c.specialization && options.vulkan_semantics) || c.is_used_as_lut; |
| |
| if (needs_declaration) |
| { |
| emit_constant(c); |
| emitted = true; |
| } |
| } |
| else if (options.vulkan_semantics && id.get_type() == TypeConstantOp) |
| { |
| emit_specialization_constant_op(id.get<SPIRConstantOp>()); |
| emitted = true; |
| } |
| } |
| |
| if (emitted) |
| statement(""); |
| emitted = false; |
| |
| // Output all basic struct types which are not Block or BufferBlock as these are declared inplace |
| // when such variables are instantiated. |
| for (auto &id : ids) |
| { |
| if (id.get_type() == TypeType) |
| { |
| auto &type = id.get<SPIRType>(); |
| if (type.basetype == SPIRType::Struct && type.array.empty() && !type.pointer && |
| (!meta[type.self].decoration.decoration_flags.get(DecorationBlock) && |
| !meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock))) |
| { |
| emit_struct(type); |
| } |
| } |
| } |
| |
| // Output UBOs and SSBOs |
| for (auto &id : ids) |
| { |
| if (id.get_type() == TypeVariable) |
| { |
| auto &var = id.get<SPIRVariable>(); |
| auto &type = get<SPIRType>(var.basetype); |
| |
| bool is_block_storage = type.storage == StorageClassStorageBuffer || type.storage == StorageClassUniform; |
| bool has_block_flags = meta[type.self].decoration.decoration_flags.get(DecorationBlock) || |
| meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock); |
| |
| if (var.storage != StorageClassFunction && type.pointer && is_block_storage && !is_hidden_variable(var) && |
| has_block_flags) |
| { |
| emit_buffer_block(var); |
| } |
| } |
| } |
| |
| // Output push constant blocks |
| for (auto &id : ids) |
| { |
| if (id.get_type() == TypeVariable) |
| { |
| auto &var = id.get<SPIRVariable>(); |
| auto &type = get<SPIRType>(var.basetype); |
| if (var.storage != StorageClassFunction && type.pointer && type.storage == StorageClassPushConstant && |
| !is_hidden_variable(var)) |
| { |
| emit_push_constant_block(var); |
| } |
| } |
| } |
| |
| bool skip_separate_image_sampler = !combined_image_samplers.empty() || !options.vulkan_semantics; |
| |
| // Output Uniform Constants (values, samplers, images, etc). |
| for (auto &id : ids) |
| { |
| if (id.get_type() == TypeVariable) |
| { |
| auto &var = id.get<SPIRVariable>(); |
| auto &type = get<SPIRType>(var.basetype); |
| |
| // If we're remapping separate samplers and images, only emit the combined samplers. |
| if (skip_separate_image_sampler) |
| { |
| // Sampler buffers are always used without a sampler, and they will also work in regular GL. |
| bool sampler_buffer = type.basetype == SPIRType::Image && type.image.dim == DimBuffer; |
| bool separate_image = type.basetype == SPIRType::Image && type.image.sampled == 1; |
| bool separate_sampler = type.basetype == SPIRType::Sampler; |
| if (!sampler_buffer && (separate_image || separate_sampler)) |
| continue; |
| } |
| |
| if (var.storage != StorageClassFunction && type.pointer && |
| (type.storage == StorageClassUniformConstant || type.storage == StorageClassAtomicCounter) && |
| !is_hidden_variable(var)) |
| { |
| emit_uniform(var); |
| emitted = true; |
| } |
| } |
| } |
| |
| if (emitted) |
| statement(""); |
| emitted = false; |
| |
| // Output in/out interfaces. |
| for (auto &id : ids) |
| { |
| if (id.get_type() == TypeVariable) |
| { |
| auto &var = id.get<SPIRVariable>(); |
| auto &type = get<SPIRType>(var.basetype); |
| |
| if (var.storage != StorageClassFunction && type.pointer && |
| (var.storage == StorageClassInput || var.storage == StorageClassOutput) && |
| interface_variable_exists_in_entry_point(var.self) && !is_hidden_variable(var)) |
| { |
| emit_interface_block(var); |
| emitted = true; |
| } |
| else if (is_builtin_variable(var)) |
| { |
| // For gl_InstanceIndex emulation on GLES, the API user needs to |
| // supply this uniform. |
| if (options.vertex.support_nonzero_base_instance && |
| meta[var.self].decoration.builtin_type == BuiltInInstanceIndex && !options.vulkan_semantics) |
| { |
| statement("uniform int SPIRV_Cross_BaseInstance;"); |
| emitted = true; |
| } |
| } |
| } |
| } |
| |
| // Global variables. |
| for (auto global : global_variables) |
| { |
| auto &var = get<SPIRVariable>(global); |
| if (var.storage != StorageClassOutput) |
| { |
| add_resource_name(var.self); |
| statement(variable_decl(var), ";"); |
| emitted = true; |
| } |
| } |
| |
| if (emitted) |
| statement(""); |
| |
| declare_undefined_values(); |
| } |
| |
| // Returns a string representation of the ID, usable as a function arg. |
| // Default is to simply return the expression representation fo the arg ID. |
| // Subclasses may override to modify the return value. |
| string CompilerGLSL::to_func_call_arg(uint32_t id) |
| { |
| return to_expression(id); |
| } |
| |
| void CompilerGLSL::handle_invalid_expression(uint32_t id) |
| { |
| // We tried to read an invalidated expression. |
| // This means we need another pass at compilation, but next time, force temporary variables so that they cannot be invalidated. |
| forced_temporaries.insert(id); |
| force_recompile = true; |
| } |
| |
| // Converts the format of the current expression from packed to unpacked, |
| // by wrapping the expression in a constructor of the appropriate type. |
| // GLSL does not support packed formats, so simply return the expression. |
| // Subclasses that do will override |
| string CompilerGLSL::unpack_expression_type(string expr_str, const SPIRType &) |
| { |
| return expr_str; |
| } |
| |
| // Sometimes we proactively enclosed an expression where it turns out we might have not needed it after all. |
| void CompilerGLSL::strip_enclosed_expression(string &expr) |
| { |
| if (expr.size() < 2 || expr.front() != '(' || expr.back() != ')') |
| return; |
| |
| // Have to make sure that our first and last parens actually enclose everything inside it. |
| uint32_t paren_count = 0; |
| for (auto &c : expr) |
| { |
| if (c == '(') |
| paren_count++; |
| else if (c == ')') |
| { |
| paren_count--; |
| |
| // If we hit 0 and this is not the final char, our first and final parens actually don't |
| // enclose the expression, and we cannot strip, e.g.: (a + b) * (c + d). |
| if (paren_count == 0 && &c != &expr.back()) |
| return; |
| } |
| } |
| expr.erase(expr.size() - 1, 1); |
| expr.erase(begin(expr)); |
| } |
| |
| string CompilerGLSL::enclose_expression(const string &expr) |
| { |
| bool need_parens = false; |
| |
| // If the expression starts with a unary we need to enclose to deal with cases where we have back-to-back |
| // unary expressions. |
| if (!expr.empty()) |
| { |
| auto c = expr.front(); |
| if (c == '-' || c == '+' || c == '!' || c == '~') |
| need_parens = true; |
| } |
| |
| if (!need_parens) |
| { |
| uint32_t paren_count = 0; |
| for (auto c : expr) |
| { |
| if (c == '(' || c == '[') |
| paren_count++; |
| else if (c == ')' || c == ']') |
| { |
| assert(paren_count); |
| paren_count--; |
| } |
| else if (c == ' ' && paren_count == 0) |
| { |
| need_parens = true; |
| break; |
| } |
| } |
| assert(paren_count == 0); |
| } |
| |
| // If this expression contains any spaces which are not enclosed by parentheses, |
| // we need to enclose it so we can treat the whole string as an expression. |
| // This happens when two expressions have been part of a binary op earlier. |
| if (need_parens) |
| return join('(', expr, ')'); |
| else |
| return expr; |
| } |
| |
| // Just like to_expression except that we enclose the expression inside parentheses if needed. |
| string CompilerGLSL::to_enclosed_expression(uint32_t id) |
| { |
| return enclose_expression(to_expression(id)); |
| } |
| |
| string CompilerGLSL::to_unpacked_expression(uint32_t id) |
| { |
| // If we need to transpose, it will also take care of unpacking rules. |
| auto *e = maybe_get<SPIRExpression>(id); |
| bool need_transpose = e && e->need_transpose; |
| if (!need_transpose && has_decoration(id, DecorationCPacked)) |
| return unpack_expression_type(to_expression(id), expression_type(id)); |
| else |
| return to_expression(id); |
| } |
| |
| string CompilerGLSL::to_enclosed_unpacked_expression(uint32_t id) |
| { |
| // If we need to transpose, it will also take care of unpacking rules. |
| auto *e = maybe_get<SPIRExpression>(id); |
| bool need_transpose = e && e->need_transpose; |
| if (!need_transpose && has_decoration(id, DecorationCPacked)) |
| return unpack_expression_type(to_expression(id), expression_type(id)); |
| else |
| return to_enclosed_expression(id); |
| } |
| |
| string CompilerGLSL::to_extract_component_expression(uint32_t id, uint32_t index) |
| { |
| auto expr = to_enclosed_expression(id); |
| if (has_decoration(id, DecorationCPacked)) |
| return join(expr, "[", index, "]"); |
| else |
| return join(expr, ".", index_to_swizzle(index)); |
| } |
| |
| string CompilerGLSL::to_expression(uint32_t id) |
| { |
| auto itr = invalid_expressions.find(id); |
| if (itr != end(invalid_expressions)) |
| handle_invalid_expression(id); |
| |
| if (ids[id].get_type() == TypeExpression) |
| { |
| // We might have a more complex chain of dependencies. |
| // A possible scenario is that we |
| // |
| // %1 = OpLoad |
| // %2 = OpDoSomething %1 %1. here %2 will have a dependency on %1. |
| // %3 = OpDoSomethingAgain %2 %2. Here %3 will lose the link to %1 since we don't propagate the dependencies like that. |
| // OpStore %1 %foo // Here we can invalidate %1, and hence all expressions which depend on %1. Only %2 will know since it's part of invalid_expressions. |
| // %4 = OpDoSomethingAnotherTime %3 %3 // If we forward all expressions we will see %1 expression after store, not before. |
| // |
| // However, we can propagate up a list of depended expressions when we used %2, so we can check if %2 is invalid when reading %3 after the store, |
| // and see that we should not forward reads of the original variable. |
| auto &expr = get<SPIRExpression>(id); |
| for (uint32_t dep : expr.expression_dependencies) |
| if (invalid_expressions.find(dep) != end(invalid_expressions)) |
| handle_invalid_expression(dep); |
| } |
| |
| track_expression_read(id); |
| |
| switch (ids[id].get_type()) |
| { |
| case TypeExpression: |
| { |
| auto &e = get<SPIRExpression>(id); |
| if (e.base_expression) |
| return to_enclosed_expression(e.base_expression) + e.expression; |
| else if (e.need_transpose) |
| { |
| bool is_packed = has_decoration(id, DecorationCPacked); |
| return convert_row_major_matrix(e.expression, get<SPIRType>(e.expression_type), is_packed); |
| } |
| else |
| { |
| if (force_recompile) |
| { |
| // During first compilation phase, certain expression patterns can trigger exponential growth of memory. |
| // Avoid this by returning dummy expressions during this phase. |
| // Do not use empty expressions here, because those are sentinels for other cases. |
| return "_"; |
| } |
| else |
| return e.expression; |
| } |
| } |
| |
| case TypeConstant: |
| { |
| auto &c = get<SPIRConstant>(id); |
| auto &type = get<SPIRType>(c.constant_type); |
| |
| // WorkGroupSize may be a constant. |
| auto &dec = meta[c.self].decoration; |
| if (dec.builtin) |
| return builtin_to_glsl(dec.builtin_type, StorageClassGeneric); |
| else if (c.specialization && options.vulkan_semantics) |
| return to_name(id); |
| else if (c.is_used_as_lut) |
| return to_name(id); |
| else if (type.basetype == SPIRType::Struct && !backend.can_declare_struct_inline) |
| return to_name(id); |
| else if (!type.array.empty() && !backend.can_declare_arrays_inline) |
| return to_name(id); |
| else |
| return constant_expression(c); |
| } |
| |
| case TypeConstantOp: |
| if (options.vulkan_semantics) |
| return to_name(id); |
| else |
| return constant_op_expression(get<SPIRConstantOp>(id)); |
| |
| case TypeVariable: |
| { |
| auto &var = get<SPIRVariable>(id); |
| // If we try to use a loop variable before the loop header, we have to redirect it to the static expression, |
| // the variable has not been declared yet. |
| if (var.statically_assigned || (var.loop_variable && !var.loop_variable_enable)) |
| return to_expression(var.static_expression); |
| else if (var.deferred_declaration) |
| { |
| var.deferred_declaration = false; |
| return variable_decl(var); |
| } |
| else if (flattened_structs.count(id)) |
| { |
| return load_flattened_struct(var); |
| } |
| else |
| { |
| auto &dec = meta[var.self].decoration; |
| if (dec.builtin) |
| return builtin_to_glsl(dec.builtin_type, var.storage); |
| else |
| return to_name(id); |
| } |
| } |
| |
| case TypeCombinedImageSampler: |
| // This type should never be taken the expression of directly. |
| // The intention is that texture sampling functions will extract the image and samplers |
| // separately and take their expressions as needed. |
| // GLSL does not use this type because OpSampledImage immediately creates a combined image sampler |
| // expression ala sampler2D(texture, sampler). |
| SPIRV_CROSS_THROW("Combined image samplers have no default expression representation."); |
| |
| case TypeAccessChain: |
| // We cannot express this type. They only have meaning in other OpAccessChains, OpStore or OpLoad. |
| SPIRV_CROSS_THROW("Access chains have no default expression representation."); |
| |
| default: |
| return to_name(id); |
| } |
| } |
| |
| string CompilerGLSL::constant_op_expression(const SPIRConstantOp &cop) |
| { |
| auto &type = get<SPIRType>(cop.basetype); |
| bool binary = false; |
| bool unary = false; |
| string op; |
| |
| if (is_legacy() && is_unsigned_opcode(cop.opcode)) |
| SPIRV_CROSS_THROW("Unsigned integers are not supported on legacy targets."); |
| |
| // TODO: Find a clean way to reuse emit_instruction. |
| switch (cop.opcode) |
| { |
| case OpSConvert: |
| case OpUConvert: |
| case OpFConvert: |
| op = type_to_glsl_constructor(type); |
| break; |
| |
| #define GLSL_BOP(opname, x) \ |
| case Op##opname: \ |
| binary = true; \ |
| op = x; \ |
| break |
| |
| #define GLSL_UOP(opname, x) \ |
| case Op##opname: \ |
| unary = true; \ |
| op = x; \ |
| break |
| |
| GLSL_UOP(SNegate, "-"); |
| GLSL_UOP(Not, "~"); |
| GLSL_BOP(IAdd, "+"); |
| GLSL_BOP(ISub, "-"); |
| GLSL_BOP(IMul, "*"); |
| GLSL_BOP(SDiv, "/"); |
| GLSL_BOP(UDiv, "/"); |
| GLSL_BOP(UMod, "%"); |
| GLSL_BOP(SMod, "%"); |
| GLSL_BOP(ShiftRightLogical, ">>"); |
| GLSL_BOP(ShiftRightArithmetic, ">>"); |
| GLSL_BOP(ShiftLeftLogical, "<<"); |
| GLSL_BOP(BitwiseOr, "|"); |
| GLSL_BOP(BitwiseXor, "^"); |
| GLSL_BOP(BitwiseAnd, "&"); |
| GLSL_BOP(LogicalOr, "||"); |
| GLSL_BOP(LogicalAnd, "&&"); |
| GLSL_UOP(LogicalNot, "!"); |
| GLSL_BOP(LogicalEqual, "=="); |
| GLSL_BOP(LogicalNotEqual, "!="); |
| GLSL_BOP(IEqual, "=="); |
| GLSL_BOP(INotEqual, "!="); |
| GLSL_BOP(ULessThan, "<"); |
| GLSL_BOP(SLessThan, "<"); |
| GLSL_BOP(ULessThanEqual, "<="); |
| GLSL_BOP(SLessThanEqual, "<="); |
| GLSL_BOP(UGreaterThan, ">"); |
| GLSL_BOP(SGreaterThan, ">"); |
| GLSL_BOP(UGreaterThanEqual, ">="); |
| GLSL_BOP(SGreaterThanEqual, ">="); |
| |
| case OpSelect: |
| { |
| if (cop.arguments.size() < 3) |
| SPIRV_CROSS_THROW("Not enough arguments to OpSpecConstantOp."); |
| |
| // This one is pretty annoying. It's triggered from |
| // uint(bool), int(bool) from spec constants. |
| // In order to preserve its compile-time constness in Vulkan GLSL, |
| // we need to reduce the OpSelect expression back to this simplified model. |
| // If we cannot, fail. |
| if (to_trivial_mix_op(type, op, cop.arguments[2], cop.arguments[1], cop.arguments[0])) |
| { |
| // Implement as a simple cast down below. |
| } |
| else |
| { |
| // Implement a ternary and pray the compiler understands it :) |
| return to_ternary_expression(type, cop.arguments[0], cop.arguments[1], cop.arguments[2]); |
| } |
| break; |
| } |
| |
| case OpVectorShuffle: |
| { |
| string expr = type_to_glsl_constructor(type); |
| expr += "("; |
| |
| uint32_t left_components = expression_type(cop.arguments[0]).vecsize; |
| string left_arg = to_enclosed_expression(cop.arguments[0]); |
| string right_arg = to_enclosed_expression(cop.arguments[1]); |
| |
| for (uint32_t i = 2; i < uint32_t(cop.arguments.size()); i++) |
| { |
| uint32_t index = cop.arguments[i]; |
| if (index >= left_components) |
| expr += right_arg + "." + "xyzw"[index - left_components]; |
| else |
| expr += left_arg + "." + "xyzw"[index]; |
| |
| if (i + 1 < uint32_t(cop.arguments.size())) |
| expr += ", "; |
| } |
| |
| expr += ")"; |
| return expr; |
| } |
| |
| case OpCompositeExtract: |
| { |
| auto expr = |
| access_chain_internal(cop.arguments[0], &cop.arguments[1], uint32_t(cop.arguments.size() - 1), true, false); |
| return expr; |
| } |
| |
| case OpCompositeInsert: |
| SPIRV_CROSS_THROW("OpCompositeInsert spec constant op is not supported."); |
| |
| default: |
| // Some opcodes are unimplemented here, these are currently not possible to test from glslang. |
| SPIRV_CROSS_THROW("Unimplemented spec constant op."); |
| } |
| |
| SPIRType::BaseType input_type; |
| bool skip_cast_if_equal_type = glsl_opcode_is_sign_invariant(cop.opcode); |
| |
| switch (cop.opcode) |
| { |
| case OpIEqual: |
| case OpINotEqual: |
| input_type = SPIRType::Int; |
| break; |
| |
| default: |
| input_type = type.basetype; |
| break; |
| } |
| |
| #undef GLSL_BOP |
| #undef GLSL_UOP |
| if (binary) |
| { |
| if (cop.arguments.size() < 2) |
| SPIRV_CROSS_THROW("Not enough arguments to OpSpecConstantOp."); |
| |
| string cast_op0; |
| string cast_op1; |
| auto expected_type = binary_op_bitcast_helper(cast_op0, cast_op1, input_type, cop.arguments[0], |
| cop.arguments[1], skip_cast_if_equal_type); |
| |
| if (type.basetype != input_type && type.basetype != SPIRType::Boolean) |
| { |
| expected_type.basetype = input_type; |
| auto expr = bitcast_glsl_op(type, expected_type); |
| expr += '('; |
| expr += join(cast_op0, " ", op, " ", cast_op1); |
| expr += ')'; |
| return expr; |
| } |
| else |
| return join("(", cast_op0, " ", op, " ", cast_op1, ")"); |
| } |
| else if (unary) |
| { |
| if (cop.arguments.size() < 1) |
| SPIRV_CROSS_THROW("Not enough arguments to OpSpecConstantOp."); |
| |
| // Auto-bitcast to result type as needed. |
| // Works around various casting scenarios in glslang as there is no OpBitcast for specialization constants. |
| return join("(", op, bitcast_glsl(type, cop.arguments[0]), ")"); |
| } |
| else |
| { |
| if (cop.arguments.size() < 1) |
| SPIRV_CROSS_THROW("Not enough arguments to OpSpecConstantOp."); |
| return join(op, "(", to_expression(cop.arguments[0]), ")"); |
| } |
| } |
| |
| string CompilerGLSL::constant_expression(const SPIRConstant &c) |
| { |
| if (!c.subconstants.empty()) |
| { |
| // Handles Arrays and structures. |
| string res; |
| if (backend.use_initializer_list) |
| res = "{ "; |
| else |
| res = type_to_glsl_constructor(get<SPIRType>(c.constant_type)) + "("; |
| |
| for (auto &elem : c.subconstants) |
| { |
| auto &subc = get<SPIRConstant>(elem); |
| if (subc.specialization && options.vulkan_semantics) |
| res += to_name(elem); |
| else |
| res += constant_expression(subc); |
| |
| if (&elem != &c.subconstants.back()) |
| res += ", "; |
| } |
| |
| res += backend.use_initializer_list ? " }" : ")"; |
| return res; |
| } |
| else if (c.columns() == 1) |
| { |
| return constant_expression_vector(c, 0); |
| } |
| else |
| { |
| string res = type_to_glsl(get<SPIRType>(c.constant_type)) + "("; |
| for (uint32_t col = 0; col < c.columns(); col++) |
| { |
| if (options.vulkan_semantics && c.specialization_constant_id(col) != 0) |
| res += to_name(c.specialization_constant_id(col)); |
| else |
| res += constant_expression_vector(c, col); |
| |
| if (col + 1 < c.columns()) |
| res += ", "; |
| } |
| res += ")"; |
| return res; |
| } |
| } |
| |
| #ifdef _MSC_VER |
| // sprintf warning. |
| // We cannot rely on snprintf existing because, ..., MSVC. |
| #pragma warning(push) |
| #pragma warning(disable : 4996) |
| #endif |
| |
| string CompilerGLSL::convert_half_to_string(const SPIRConstant &c, uint32_t col, uint32_t row) |
| { |
| string res; |
| float float_value = c.scalar_f16(col, row); |
| |
| if (std::isnan(float_value) || std::isinf(float_value)) |
| { |
| if (backend.half_literal_suffix) |
| { |
| // There is no uintBitsToFloat for 16-bit, so have to rely on legacy fallback here. |
| if (float_value == numeric_limits<float>::infinity()) |
| res = join("(1.0", backend.half_literal_suffix, " / 0.0", backend.half_literal_suffix, ")"); |
| else if (float_value == -numeric_limits<float>::infinity()) |
| res = join("(-1.0", backend.half_literal_suffix, " / 0.0", backend.half_literal_suffix, ")"); |
| else if (std::isnan(float_value)) |
| res = join("(0.0", backend.half_literal_suffix, " / 0.0", backend.half_literal_suffix, ")"); |
| else |
| SPIRV_CROSS_THROW("Cannot represent non-finite floating point constant."); |
| } |
| else |
| { |
| SPIRType type; |
| type.basetype = SPIRType::Half; |
| type.vecsize = 1; |
| type.columns = 1; |
| |
| if (float_value == numeric_limits<float>::infinity()) |
| res = join(type_to_glsl(type), "(1.0 / 0.0)"); |
| else if (float_value == -numeric_limits<float>::infinity()) |
| res = join(type_to_glsl(type), "(-1.0 / 0.0)"); |
| else if (std::isnan(float_value)) |
| res = join(type_to_glsl(type), "(0.0 / 0.0)"); |
| else |
| SPIRV_CROSS_THROW("Cannot represent non-finite floating point constant."); |
| } |
| } |
| else |
| { |
| if (backend.half_literal_suffix) |
| res = convert_to_string(float_value) + backend.half_literal_suffix; |
| else |
| { |
| // In HLSL (FXC), it's important to cast the literals to half precision right away. |
| // There is no literal for it. |
| SPIRType type; |
| type.basetype = SPIRType::Half; |
| type.vecsize = 1; |
| type.columns = 1; |
| res = join(type_to_glsl(type), "(", convert_to_string(float_value), ")"); |
| } |
| } |
| |
| return res; |
| } |
| |
| string CompilerGLSL::convert_float_to_string(const SPIRConstant &c, uint32_t col, uint32_t row) |
| { |
| string res; |
| float float_value = c.scalar_f32(col, row); |
| |
| if (std::isnan(float_value) || std::isinf(float_value)) |
| { |
| // Use special representation. |
| if (!is_legacy()) |
| { |
| SPIRType out_type; |
| SPIRType in_type; |
| out_type.basetype = SPIRType::Float; |
| in_type.basetype = SPIRType::UInt; |
| out_type.vecsize = 1; |
| in_type.vecsize = 1; |
| out_type.width = 32; |
| in_type.width = 32; |
| |
| char print_buffer[32]; |
| sprintf(print_buffer, "0x%xu", c.scalar(col, row)); |
| res = join(bitcast_glsl_op(out_type, in_type), "(", print_buffer, ")"); |
| } |
| else |
| { |
| if (float_value == numeric_limits<float>::infinity()) |
| { |
| if (backend.float_literal_suffix) |
| res = "(1.0f / 0.0f)"; |
| else |
| res = "(1.0 / 0.0)"; |
| } |
| else if (float_value == -numeric_limits<float>::infinity()) |
| { |
| if (backend.float_literal_suffix) |
| res = "(-1.0f / 0.0f)"; |
| else |
| res = "(-1.0 / 0.0)"; |
| } |
| else if (std::isnan(float_value)) |
| { |
| if (backend.float_literal_suffix) |
| res = "(0.0f / 0.0f)"; |
| else |
| res = "(0.0 / 0.0)"; |
| } |
| else |
| SPIRV_CROSS_THROW("Cannot represent non-finite floating point constant."); |
| } |
| } |
| else |
| { |
| res = convert_to_string(float_value); |
| if (backend.float_literal_suffix) |
| res += "f"; |
| } |
| |
| return res; |
| } |
| |
| std::string CompilerGLSL::convert_double_to_string(const SPIRConstant &c, uint32_t col, uint32_t row) |
| { |
| string res; |
| double double_value = c.scalar_f64(col, row); |
| |
| if (std::isnan(double_value) || std::isinf(double_value)) |
| { |
| // Use special representation. |
| if (!is_legacy()) |
| { |
| SPIRType out_type; |
| SPIRType in_type; |
| out_type.basetype = SPIRType::Double; |
| in_type.basetype = SPIRType::UInt64; |
| out_type.vecsize = 1; |
| |