| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "impeller/compiler/spirv_sksl.h" |
| #include "impeller/compiler/uniform_sorter.h" |
| |
| using namespace spv; |
| using namespace SPIRV_CROSS_NAMESPACE; |
| |
| namespace impeller { |
| namespace compiler { |
| |
| // This replaces the SPIRV_CROSS_THROW which aborts and drops the |
| // error message in non-debug modes. |
| void report_and_exit(const std::string& msg) { |
| fprintf(stderr, "There was a compiler error: %s\n", msg.c_str()); |
| fflush(stderr); |
| exit(1); |
| } |
| |
| #define FLUTTER_CROSS_THROW(x) report_and_exit(x) |
| |
| std::string CompilerSkSL::compile() { |
| ir.fixup_reserved_names(); |
| |
| if (get_execution_model() != ExecutionModelFragment) { |
| FLUTTER_CROSS_THROW("Only fragment shaders are supported.'"); |
| return ""; |
| } |
| |
| options.es = false; |
| options.version = 100; |
| options.vulkan_semantics = false; |
| options.enable_420pack_extension = false; |
| options.flatten_multidimensional_arrays = true; |
| |
| backend.allow_precision_qualifiers = false; |
| backend.basic_int16_type = "short"; |
| backend.basic_int_type = "int"; |
| backend.basic_uint16_type = "ushort"; |
| backend.basic_uint_type = "uint"; |
| backend.double_literal_suffix = false; |
| backend.float_literal_suffix = false; |
| backend.long_long_literal_suffix = false; |
| backend.needs_row_major_load_workaround = true; |
| backend.nonuniform_qualifier = ""; |
| backend.support_precise_qualifier = false; |
| backend.uint32_t_literal_suffix = false; |
| backend.use_array_constructor = true; |
| backend.workgroup_size_is_hidden = true; |
| |
| fixup_user_functions(); |
| |
| fixup_anonymous_struct_names(); |
| fixup_type_alias(); |
| reorder_type_alias(); |
| build_function_control_flow_graphs_and_analyze(); |
| fixup_image_load_store_access(); |
| update_active_builtins(); |
| analyze_image_and_sampler_usage(); |
| analyze_interlocked_resource_usage(); |
| |
| uint32_t pass_count = 0; |
| do { |
| reset(pass_count); |
| |
| // Move constructor for this type is broken on GCC 4.9 ... |
| buffer.reset(); |
| |
| emit_header(); |
| emit_resources(); |
| |
| emit_function(get<SPIRFunction>(ir.default_entry_point), Bitset()); |
| |
| pass_count++; |
| } while (is_forcing_recompilation()); |
| |
| statement("half4 main(float2 iFragCoord)"); |
| begin_scope(); |
| statement(" flutter_FragCoord = float4(iFragCoord, 0, 0);"); |
| statement(" FLT_main();"); |
| statement(" return " + output_name_ + ";"); |
| end_scope(); |
| |
| return buffer.str(); |
| } |
| |
| void CompilerSkSL::fixup_user_functions() { |
| const std::string prefix = "FLT_flutter_local_"; |
| ir.for_each_typed_id<SPIRFunction>([&](uint32_t, const SPIRFunction& func) { |
| const auto& original_name = get_name(func.self); |
| // Just in case. Don't add the prefix a second time. |
| if (original_name.rfind(prefix, 0) == 0) { |
| return; |
| } |
| std::string new_name = prefix + original_name; |
| set_name(func.self, new_name); |
| }); |
| |
| ir.for_each_typed_id<SPIRFunctionPrototype>( |
| [&](uint32_t, const SPIRFunctionPrototype& func) { |
| const auto& original_name = get_name(func.self); |
| // Just in case. Don't add the prefix a second time. |
| if (original_name.rfind(prefix, 0) == 0) { |
| return; |
| } |
| std::string new_name = prefix + original_name; |
| set_name(func.self, new_name); |
| }); |
| } |
| |
| void CompilerSkSL::emit_header() { |
| statement("// This SkSL shader is autogenerated by spirv-cross."); |
| statement(""); |
| statement("float4 flutter_FragCoord;"); |
| statement(""); |
| } |
| |
| void CompilerSkSL::emit_uniform(const SPIRVariable& var) { |
| auto& type = get<SPIRType>(var.basetype); |
| if (type.basetype == SPIRType::UInt && is_legacy()) { |
| FLUTTER_CROSS_THROW("SkSL does not support unsigned integers: '" + |
| get_name(var.self) + "'"); |
| } |
| |
| add_resource_name(var.self); |
| statement(variable_decl(var), ";"); |
| |
| // The Flutter FragmentProgram implementation passes additional uniforms along |
| // with shader uniforms that encode the shader width and height. |
| if (type.basetype == SPIRType::SampledImage) { |
| std::string name = to_name(var.self); |
| statement("uniform half2 " + name + "_size;"); |
| } |
| } |
| |
| bool CompilerSkSL::emit_constant_resources() { |
| bool emitted = false; |
| |
| for (auto& id : ir.ids) { |
| if (id.get_type() == TypeConstant) { |
| auto& c = id.get<SPIRConstant>(); |
| bool needs_declaration = c.specialization || c.is_used_as_lut; |
| if (needs_declaration) { |
| if (!options.vulkan_semantics && c.specialization) { |
| c.specialization_constant_macro_name = constant_value_macro_name( |
| get_decoration(c.self, DecorationSpecId)); |
| } |
| emit_constant(c); |
| emitted = true; |
| } |
| } else if (id.get_type() == TypeConstantOp) { |
| emit_specialization_constant_op(id.get<SPIRConstantOp>()); |
| emitted = true; |
| } |
| } |
| |
| return emitted; |
| } |
| |
| bool CompilerSkSL::emit_struct_resources() { |
| bool 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 : ir.ids) { |
| if (id.get_type() == TypeType) { |
| auto& type = id.get<SPIRType>(); |
| if (type.basetype == SPIRType::Struct && type.array.empty() && |
| !type.pointer && |
| (!ir.meta[type.self].decoration.decoration_flags.get( |
| DecorationBlock) && |
| !ir.meta[type.self].decoration.decoration_flags.get( |
| DecorationBufferBlock))) { |
| emit_struct(type); |
| emitted = true; |
| } |
| } |
| } |
| |
| return emitted; |
| } |
| |
| void CompilerSkSL::detect_unsupported_resources() { |
| for (auto& id : ir.ids) { |
| if (id.get_type() == TypeVariable) { |
| auto& var = id.get<SPIRVariable>(); |
| auto& type = get<SPIRType>(var.basetype); |
| |
| // UBOs and SSBOs are not supported. |
| if (var.storage != StorageClassFunction && type.pointer && |
| type.storage == StorageClassUniform && !is_hidden_variable(var) && |
| (ir.meta[type.self].decoration.decoration_flags.get( |
| DecorationBlock) || |
| ir.meta[type.self].decoration.decoration_flags.get( |
| DecorationBufferBlock))) { |
| FLUTTER_CROSS_THROW("SkSL does not support UBOs or SSBOs: '" + |
| get_name(var.self) + "'"); |
| } |
| |
| // Push constant blocks are not supported. |
| if (!is_hidden_variable(var) && var.storage != StorageClassFunction && |
| type.pointer && type.storage == StorageClassPushConstant) { |
| FLUTTER_CROSS_THROW("SkSL does not support push constant blocks: '" + |
| get_name(var.self) + "'"); |
| } |
| |
| // User specified inputs are not supported. |
| if (!is_hidden_variable(var) && var.storage != StorageClassFunction && |
| type.pointer && type.storage == StorageClassInput) { |
| FLUTTER_CROSS_THROW("SkSL does not support inputs: '" + |
| get_name(var.self) + "'"); |
| } |
| } |
| } |
| } |
| |
| bool CompilerSkSL::emit_uniform_resources() { |
| bool emitted = false; |
| |
| // Output Uniform Constants (values, samplers, images, etc). |
| std::vector<ID> regular_uniforms = |
| SortUniforms(&ir, this, SPIRType::SampledImage, /*include=*/false); |
| std::vector<ID> shader_uniforms = |
| SortUniforms(&ir, this, SPIRType::SampledImage); |
| if (regular_uniforms.size() > 0 || shader_uniforms.size() > 0) { |
| emitted = true; |
| } |
| |
| for (const auto& id : regular_uniforms) { |
| auto& var = get<SPIRVariable>(id); |
| emit_uniform(var); |
| } |
| |
| for (const auto& id : shader_uniforms) { |
| auto& var = get<SPIRVariable>(id); |
| emit_uniform(var); |
| } |
| |
| return emitted; |
| } |
| |
| bool CompilerSkSL::emit_output_resources() { |
| bool emitted = false; |
| |
| // Output 'out' variables. These are restricted to the cases handled by |
| // SkSL in 'emit_interface_block'. |
| for (auto& id : ir.ids) { |
| if (id.get_type() == TypeVariable) { |
| auto& var = id.get<SPIRVariable>(); |
| auto& type = get<SPIRType>(var.basetype); |
| if (var.storage != StorageClassFunction && !is_hidden_variable(var) && |
| type.pointer && |
| (var.storage == StorageClassInput || |
| var.storage == StorageClassOutput) && |
| interface_variable_exists_in_entry_point(var.self)) { |
| emit_interface_block(var); |
| emitted = true; |
| } |
| } |
| } |
| |
| return emitted; |
| } |
| |
| bool CompilerSkSL::emit_global_variable_resources() { |
| bool emitted = false; |
| |
| for (auto global : global_variables) { |
| auto& var = get<SPIRVariable>(global); |
| if (is_hidden_variable(var, true)) { |
| continue; |
| } |
| if (var.storage != StorageClassOutput) { |
| if (!variable_is_lut(var)) { |
| add_resource_name(var.self); |
| std::string initializer; |
| if (options.force_zero_initialized_variables && |
| var.storage == StorageClassPrivate && !var.initializer && |
| !var.static_expression && |
| type_can_zero_initialize(get_variable_data_type(var))) { |
| initializer = join(" = ", to_zero_initialized_expression( |
| get_variable_data_type_id(var))); |
| } |
| statement(variable_decl(var), initializer, ";"); |
| emitted = true; |
| } |
| } else if (var.initializer && |
| maybe_get<SPIRConstant>(var.initializer) != nullptr) { |
| emit_output_variable_initializer(var); |
| } |
| } |
| |
| return emitted; |
| } |
| |
| bool CompilerSkSL::emit_undefined_values() { |
| bool emitted = false; |
| |
| ir.for_each_typed_id<SPIRUndef>([&](uint32_t, const SPIRUndef& undef) { |
| auto& type = this->get<SPIRType>(undef.basetype); |
| // OpUndef can be void for some reason ... |
| if (type.basetype == SPIRType::Void) { |
| return; |
| } |
| |
| std::string initializer; |
| if (options.force_zero_initialized_variables && |
| type_can_zero_initialize(type)) { |
| initializer = join(" = ", to_zero_initialized_expression(undef.basetype)); |
| } |
| |
| statement(variable_decl(type, to_name(undef.self), undef.self), initializer, |
| ";"); |
| emitted = true; |
| }); |
| |
| return emitted; |
| } |
| |
| void CompilerSkSL::emit_resources() { |
| detect_unsupported_resources(); |
| |
| if (emit_constant_resources()) { |
| statement(""); |
| } |
| |
| if (emit_struct_resources()) { |
| statement(""); |
| } |
| |
| if (emit_uniform_resources()) { |
| statement(""); |
| } |
| |
| if (emit_output_resources()) { |
| statement(""); |
| } |
| |
| if (emit_global_variable_resources()) { |
| statement(""); |
| } |
| |
| if (emit_undefined_values()) { |
| statement(""); |
| } |
| } |
| |
| void CompilerSkSL::emit_interface_block(const SPIRVariable& var) { |
| auto& type = get<SPIRType>(var.basetype); |
| bool block = |
| ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock); |
| if (block) { |
| FLUTTER_CROSS_THROW("Interface blocks are not supported: '" + |
| to_name(var.self) + "'"); |
| } |
| |
| // The output is emitted as a global variable, which is returned from the |
| // wrapper around the 'main' function. Only one output variable is allowed. |
| add_resource_name(var.self); |
| statement(variable_decl(type, to_name(var.self), var.self), ";"); |
| if (output_name_.empty()) { |
| output_name_ = to_name(var.self); |
| } else if (to_name(var.self) != output_name_) { |
| FLUTTER_CROSS_THROW("Only one output variable is supported: '" + |
| to_name(var.self) + "'"); |
| } |
| } |
| |
| void CompilerSkSL::emit_function_prototype(SPIRFunction& func, |
| const Bitset& return_flags) { |
| // If this is not the entrypoint, then no special processsing for SkSL is |
| // required. |
| if (func.self != ir.default_entry_point) { |
| CompilerGLSL::emit_function_prototype(func, return_flags); |
| return; |
| } |
| |
| auto& type = get<SPIRType>(func.return_type); |
| if (type.basetype != SPIRType::Void) { |
| FLUTTER_CROSS_THROW( |
| "Return type of the entrypoint function must be 'void'"); |
| } |
| |
| if (func.arguments.size() != 0) { |
| FLUTTER_CROSS_THROW( |
| "The entry point function should not acept any parameters."); |
| } |
| |
| processing_entry_point = true; |
| |
| // If this is the entrypoint of a fragment shader, then GLSL requires the |
| // prototype to be "void main()", and so it is safe to rewrite as |
| // "void FLT_main()". |
| statement("void FLT_main()"); |
| } |
| |
| std::string CompilerSkSL::image_type_glsl(const SPIRType& type, |
| uint32_t id, |
| bool member) { |
| if (type.basetype != SPIRType::SampledImage || type.image.dim != Dim2D) { |
| FLUTTER_CROSS_THROW("Only sampler2D uniform image types are supported."); |
| return "???"; |
| } |
| return "shader"; |
| } |
| |
| std::string CompilerSkSL::builtin_to_glsl(BuiltIn builtin, |
| StorageClass storage) { |
| std::string gl_builtin = CompilerGLSL::builtin_to_glsl(builtin, storage); |
| switch (builtin) { |
| case BuiltInFragCoord: |
| return "flutter_FragCoord"; |
| default: |
| FLUTTER_CROSS_THROW("Builtin '" + gl_builtin + "' is not supported."); |
| break; |
| } |
| |
| return "???"; |
| } |
| |
| std::string CompilerSkSL::to_texture_op( |
| const Instruction& i, |
| bool sparse, |
| bool* forward, |
| SmallVector<uint32_t>& inherited_expressions) { |
| auto op = static_cast<Op>(i.op); |
| if (op != OpImageSampleImplicitLod) { |
| FLUTTER_CROSS_THROW("Only simple shader sampling is supported."); |
| return "???"; |
| } |
| return CompilerGLSL::to_texture_op(i, sparse, forward, inherited_expressions); |
| } |
| |
| std::string CompilerSkSL::to_function_name( |
| const CompilerGLSL::TextureFunctionNameArguments& args) { |
| std::string name = to_expression(args.base.img); |
| return name + ".eval"; |
| } |
| |
| std::string CompilerSkSL::to_function_args(const TextureFunctionArguments& args, |
| bool* p_forward) { |
| std::string name = to_expression(args.base.img); |
| |
| std::string glsl_args = CompilerGLSL::to_function_args(args, p_forward); |
| // SkSL only supports coordinates. All other arguments to texture are |
| // unsupported and will generate invalid SkSL. |
| if (args.grad_x || args.grad_y || args.lod || args.offset || args.sample || |
| args.min_lod || args.sparse_texel || args.bias || args.component) { |
| FLUTTER_CROSS_THROW( |
| "Only sampler and position arguments are supported in texture() " |
| "calls."); |
| } |
| |
| // GLSL puts the shader as the first argument, but in SkSL the shader is |
| // implicitly passed as the reciever of the 'eval' method. Therefore, the |
| // shader is removed from the GLSL argument list. |
| std::string no_shader; |
| auto npos = glsl_args.find(", "); // The first ','. |
| if (npos != std::string::npos) { |
| no_shader = glsl_args.substr(npos + 1); // The string after the first ','. |
| } |
| |
| if (no_shader.empty()) { |
| FLUTTER_CROSS_THROW("Unexpected shader sampling arguments: '(" + glsl_args + |
| ")'"); |
| return "()"; |
| } |
| |
| return name + "_size * (" + no_shader + ")"; |
| } |
| |
| } // namespace compiler |
| } // namespace impeller |