diff --git a/BUILD.gn b/BUILD.gn index 6226da6525712..9b1d448ba2617 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -161,9 +161,9 @@ group("unittests") { "//flutter/flow:flow_unittests", "//flutter/fml:fml_unittests", "//flutter/lib/spirv/test/exception_shaders:spirv_compile_exception_shaders", - "//flutter/lib/spirv/test/general_shaders:spirv_compile_general_shaders", - "//flutter/lib/spirv/test/supported_glsl_op_shaders:spirv_compile_supported_glsl_shaders", - "//flutter/lib/spirv/test/supported_op_shaders:spirv_compile_supported_op_shaders", + "//flutter/lib/spirv/test/general_shaders", + "//flutter/lib/spirv/test/supported_glsl_op_shaders", + "//flutter/lib/spirv/test/supported_op_shaders", "//flutter/lib/ui:ui_unittests", "//flutter/runtime:dart_plugin_registrant_unittests", "//flutter/runtime:no_dart_plugin_registrant_unittests", diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 04e3b9e9fefca..23bb99b4b353c 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -491,6 +491,8 @@ FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/texture.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/types.glsl FILE: ../../../flutter/impeller/compiler/source_options.cc FILE: ../../../flutter/impeller/compiler/source_options.h +FILE: ../../../flutter/impeller/compiler/spirv_sksl.cc +FILE: ../../../flutter/impeller/compiler/spirv_sksl.h FILE: ../../../flutter/impeller/compiler/switches.cc FILE: ../../../flutter/impeller/compiler/switches.h FILE: ../../../flutter/impeller/compiler/types.cc diff --git a/impeller/compiler/BUILD.gn b/impeller/compiler/BUILD.gn index 9e2de582a9a6d..4c2b94802bad6 100644 --- a/impeller/compiler/BUILD.gn +++ b/impeller/compiler/BUILD.gn @@ -28,6 +28,8 @@ impeller_component("compiler_lib") { "runtime_stage_data.h", "source_options.cc", "source_options.h", + "spirv_sksl.cc", + "spirv_sksl.h", "switches.cc", "switches.h", "types.cc", diff --git a/impeller/compiler/compiler.cc b/impeller/compiler/compiler.cc index 3df96cc40155e..54b577252211d 100644 --- a/impeller/compiler/compiler.cc +++ b/impeller/compiler/compiler.cc @@ -27,7 +27,7 @@ static CompilerBackend CreateMSLCompiler(const spirv_cross::ParsedIR& ir, sl_options.msl_version = spirv_cross::CompilerMSL::Options::make_msl_version(1, 2); sl_compiler->set_msl_options(sl_options); - return sl_compiler; + return CompilerBackend(sl_compiler); } static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR& ir, @@ -44,7 +44,13 @@ static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR& ir, sl_options.es = false; } gl_compiler->set_common_options(sl_options); - return gl_compiler; + return CompilerBackend(gl_compiler); +} + +static CompilerBackend CreateSkSLCompiler(const spirv_cross::ParsedIR& ir, + const SourceOptions& source_options) { + auto sksl_compiler = std::make_shared(ir); + return CompilerBackend(sksl_compiler); } static bool EntryPointMustBeNamedMain(TargetPlatform platform) { @@ -56,6 +62,7 @@ static bool EntryPointMustBeNamedMain(TargetPlatform platform) { case TargetPlatform::kRuntimeStageMetal: return false; case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kSkSL: case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageGLES: @@ -80,6 +87,8 @@ static CompilerBackend CreateCompiler(const spirv_cross::ParsedIR& ir, case TargetPlatform::kOpenGLDesktop: compiler = CreateGLSLCompiler(ir, source_options); break; + case TargetPlatform::kSkSL: + compiler = CreateSkSLCompiler(ir, source_options); } if (!compiler) { return {}; @@ -165,6 +174,19 @@ Compiler::Compiler(const fml::Mapping& source_mapping, spirv_options.SetTargetSpirv( shaderc_spirv_version::shaderc_spirv_version_1_0); break; + case TargetPlatform::kSkSL: + // When any optimization level above 'zero' is enabled, the phi merges at + // loop continue blocks are rendered using syntax that is supported in + // GLSL, but not in SkSL. + // https://bugs.chromium.org/p/skia/issues/detail?id=13518. + spirv_options.SetOptimizationLevel( + shaderc_optimization_level::shaderc_optimization_level_zero); + spirv_options.SetTargetEnvironment( + shaderc_target_env::shaderc_target_env_opengl, + shaderc_env_version::shaderc_env_version_opengl_4_5); + spirv_options.SetTargetSpirv( + shaderc_spirv_version::shaderc_spirv_version_1_0); + break; case TargetPlatform::kUnknown: COMPILER_ERROR << "Target platform invalid."; return; diff --git a/impeller/compiler/compiler_backend.cc b/impeller/compiler/compiler_backend.cc index cb44cffa41e36..1df6332498c49 100644 --- a/impeller/compiler/compiler_backend.cc +++ b/impeller/compiler/compiler_backend.cc @@ -15,6 +15,9 @@ CompilerBackend::CompilerBackend(MSLCompiler compiler) CompilerBackend::CompilerBackend(GLSLCompiler compiler) : CompilerBackend(Type::kGLSL, compiler) {} +CompilerBackend::CompilerBackend(SkSLCompiler compiler) + : CompilerBackend(Type::kSkSL, compiler) {} + CompilerBackend::CompilerBackend() = default; CompilerBackend::CompilerBackend(Type type, Compiler compiler) @@ -54,6 +57,10 @@ const spirv_cross::Compiler* CompilerBackend::GetCompiler() const { return compiler; } + if (auto compiler = GetSkSLCompiler()) { + return compiler; + } + return nullptr; } @@ -64,6 +71,9 @@ spirv_cross::Compiler* CompilerBackend::GetCompiler() { if (auto* glsl = std::get_if(&compiler_)) { return glsl->get(); } + if (auto* sksl = std::get_if(&compiler_)) { + return sksl->get(); + } return nullptr; } @@ -81,6 +91,13 @@ const spirv_cross::CompilerGLSL* CompilerBackend::GetGLSLCompiler() const { return nullptr; } +const CompilerSkSL* CompilerBackend::GetSkSLCompiler() const { + if (auto* sksl = std::get_if(&compiler_)) { + return sksl->get(); + } + return nullptr; +} + CompilerBackend::operator bool() const { return !!GetCompiler(); } diff --git a/impeller/compiler/compiler_backend.h b/impeller/compiler/compiler_backend.h index a7210931d9301..b8225fce186ef 100644 --- a/impeller/compiler/compiler_backend.h +++ b/impeller/compiler/compiler_backend.h @@ -11,6 +11,7 @@ #include "flutter/fml/macros.h" #include "spirv_glsl.hpp" #include "spirv_msl.hpp" +#include "spirv_sksl.h" namespace impeller { namespace compiler { @@ -18,16 +19,20 @@ namespace compiler { struct CompilerBackend { using MSLCompiler = std::shared_ptr; using GLSLCompiler = std::shared_ptr; - using Compiler = std::variant; + using SkSLCompiler = std::shared_ptr; + using Compiler = std::variant; enum class Type { kMSL, kGLSL, + kSkSL, }; - CompilerBackend(MSLCompiler compiler); + explicit CompilerBackend(MSLCompiler compiler); - CompilerBackend(GLSLCompiler compiler); + explicit CompilerBackend(GLSLCompiler compiler); + + explicit CompilerBackend(SkSLCompiler compiler); CompilerBackend(Type type, Compiler compiler); @@ -59,6 +64,8 @@ struct CompilerBackend { const spirv_cross::CompilerMSL* GetMSLCompiler() const; const spirv_cross::CompilerGLSL* GetGLSLCompiler() const; + + const CompilerSkSL* GetSkSLCompiler() const; }; } // namespace compiler diff --git a/impeller/compiler/impellerc_main.cc b/impeller/compiler/impellerc_main.cc index 79ff437355df9..38b96d29b0b1e 100644 --- a/impeller/compiler/impellerc_main.cc +++ b/impeller/compiler/impellerc_main.cc @@ -172,6 +172,7 @@ bool Main(const fml::CommandLine& command_line) { case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageMetal: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kSkSL: result_file = switches.sl_file_name; break; case TargetPlatform::kFlutterSPIRV: diff --git a/impeller/compiler/reflector.cc b/impeller/compiler/reflector.cc index 0693ef34d68b7..a8d099d042e42 100644 --- a/impeller/compiler/reflector.cc +++ b/impeller/compiler/reflector.cc @@ -342,6 +342,8 @@ static std::string ToString(CompilerBackend::Type type) { return "Metal Shading Language"; case CompilerBackend::Type::kGLSL: return "OpenGL Shading Language"; + case CompilerBackend::Type::kSkSL: + return "SkSL Shading Language"; } FML_UNREACHABLE(); } diff --git a/impeller/compiler/runtime_stage_data.cc b/impeller/compiler/runtime_stage_data.cc index 07b187eb7e3d4..20809a25c52e8 100644 --- a/impeller/compiler/runtime_stage_data.cc +++ b/impeller/compiler/runtime_stage_data.cc @@ -55,6 +55,7 @@ static std::optional ToTargetPlatform( case TargetPlatform::kMetalDesktop: case TargetPlatform::kMetalIOS: case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kSkSL: case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: return std::nullopt; diff --git a/impeller/compiler/spirv_sksl.cc b/impeller/compiler/spirv_sksl.cc new file mode 100644 index 0000000000000..a16f93476cb65 --- /dev/null +++ b/impeller/compiler/spirv_sksl.cc @@ -0,0 +1,408 @@ +// 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" + +using namespace spv; +using namespace SPIRV_CROSS_NAMESPACE; + +namespace impeller { +namespace compiler { + +std::string CompilerSkSL::compile() { + ir.fixup_reserved_names(); + + if (get_execution_model() != ExecutionModelFragment) { + SPIRV_CROSS_THROW("Only fragment shaders are supported.'"); + return ""; + } + + options.es = false; + options.version = 300; + options.vulkan_semantics = false; + options.enable_420pack_extension = false; + + 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_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(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(" __main();"); + statement(" return " + output_name_ + ";"); + end_scope(); + + return buffer.str(); +} + +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(var.basetype); + add_resource_name(var.self); + statement(layout_for_variable(var), variable_decl(var), ";"); + + // The Flutter FragmentProgram implementation passes additional unifroms 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(); + 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()); + 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(); + 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() { + // UBOs and SSBOs are not supported. + for (auto& id : ir.ids) { + if (id.get_type() == TypeVariable) { + auto& var = id.get(); + auto& type = get(var.basetype); + + 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))) { + SPIRV_CROSS_THROW("SkSL does not support UBOs or SSBOs: '" + + get_name(var.self) + "'"); + } + } + } + + // Push constant blocks are not supported. + for (auto& id : ir.ids) { + if (id.get_type() == TypeVariable) { + auto& var = id.get(); + auto& type = get(var.basetype); + if (!is_hidden_variable(var) && var.storage != StorageClassFunction && + type.pointer && type.storage == StorageClassPushConstant) { + SPIRV_CROSS_THROW("SkSL does not support push constant blocks: '" + + get_name(var.self) + "'"); + } + } + } +} + +bool CompilerSkSL::emit_uniform_resources() { + bool emitted = false; + + // Output Uniform Constants (values, samplers, images, etc). + std::vector regular_uniforms; + std::vector shader_uniforms; + for (auto& id : ir.ids) { + if (id.get_type() == TypeVariable) { + auto& var = id.get(); + auto& type = get(var.basetype); + if (var.storage != StorageClassFunction && !is_hidden_variable(var) && + type.pointer && + (type.storage == StorageClassUniformConstant || + type.storage == StorageClassAtomicCounter)) { + // Separate out the uniforms that will be of SkSL 'shader' type since + // we need to make sure they are emitted only after the other uniforms. + if (type.basetype == SPIRType::SampledImage) { + shader_uniforms.push_back(var.self); + } else { + regular_uniforms.push_back(var.self); + } + emitted = true; + } + } + } + + for (const auto& id : regular_uniforms) { + auto& var = get(id); + emit_uniform(var); + } + + for (const auto& id : shader_uniforms) { + auto& var = get(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(); + auto& type = get(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(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(var.initializer) != nullptr) { + emit_output_variable_initializer(var); + } + } + + 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(""); + } + + declare_undefined_values(); +} + +void CompilerSkSL::emit_interface_block(const SPIRVariable& var) { + auto& type = get(var.basetype); + bool block = + ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock); + if (block) { + SPIRV_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 { + SPIRV_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(func.return_type); + if (type.basetype != SPIRType::Void) { + SPIRV_CROSS_THROW("Return type of the entrypoint function must be 'void'"); + } + + if (func.arguments.size() != 0) { + SPIRV_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 __main()". + statement("void __main()"); +} + +std::string CompilerSkSL::image_type_glsl(const SPIRType& type, uint32_t id) { + if (type.basetype != SPIRType::SampledImage || type.image.dim != Dim2D) { + SPIRV_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: + SPIRV_CROSS_THROW("Builtin '" + gl_builtin + "' is not supported."); + break; + } + + return "???"; +} + +std::string CompilerSkSL::to_texture_op( + const Instruction& i, + bool sparse, + bool* forward, + SmallVector& inherited_expressions) { + auto op = static_cast(i.op); + if (op != OpImageSampleImplicitLod) { + SPIRV_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); + + // 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()) { + SPIRV_CROSS_THROW("Unexpected shader sampling arguments: '(" + glsl_args + + ")'"); + return "()"; + } + + return name + "_size * " + no_shader; +} + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/spirv_sksl.h b/impeller/compiler/spirv_sksl.h new file mode 100644 index 0000000000000..3fbe4cc1fdcae --- /dev/null +++ b/impeller/compiler/spirv_sksl.h @@ -0,0 +1,77 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/logging.h" +#include "flutter/fml/macros.h" +#include "spirv_glsl.hpp" + +namespace impeller { +namespace compiler { + +class CompilerSkSL : public spirv_cross::CompilerGLSL { + public: + explicit CompilerSkSL(std::vector spirv_) + : CompilerGLSL(std::move(spirv_)) {} + + CompilerSkSL(const uint32_t* ir_, size_t word_count) + : CompilerGLSL(ir_, word_count) {} + + explicit CompilerSkSL(const spirv_cross::ParsedIR& ir_) + : spirv_cross::CompilerGLSL(ir_) {} + + explicit CompilerSkSL(spirv_cross::ParsedIR&& ir_) + : spirv_cross::CompilerGLSL(std::move(ir_)) {} + + std::string compile() override; + + private: + std::string output_name_; + + void emit_header() override; + + void emit_uniform(const spirv_cross::SPIRVariable& var) override; + + void detect_unsupported_resources(); + bool emit_constant_resources(); + bool emit_struct_resources(); + bool emit_uniform_resources(); + bool emit_output_resources(); + bool emit_global_variable_resources(); + void emit_resources(); + + void emit_interface_block(const spirv_cross::SPIRVariable& var); + + void emit_function_prototype( + spirv_cross::SPIRFunction& func, + const spirv_cross::Bitset& return_flags) override; + + std::string image_type_glsl(const spirv_cross::SPIRType& type, + uint32_t id = 0) override; + + std::string builtin_to_glsl(spv::BuiltIn builtin, + spv::StorageClass storage) override; + + std::string to_texture_op( + const spirv_cross::Instruction& i, + bool sparse, + bool* forward, + spirv_cross::SmallVector& inherited_expressions) override; + + std::string to_function_name( + const spirv_cross::CompilerGLSL::TextureFunctionNameArguments& args) + override; + + std::string to_function_args( + const spirv_cross::CompilerGLSL::TextureFunctionArguments& args, + bool* p_forward) override; +}; + +} // namespace compiler +} // namespace impeller diff --git a/impeller/compiler/switches.cc b/impeller/compiler/switches.cc index aae3f076021bb..85e1ed80db945 100644 --- a/impeller/compiler/switches.cc +++ b/impeller/compiler/switches.cc @@ -18,6 +18,7 @@ static const std::map kKnownPlatforms = { {"opengl-es", TargetPlatform::kOpenGLES}, {"opengl-desktop", TargetPlatform::kOpenGLDesktop}, {"flutter-spirv", TargetPlatform::kFlutterSPIRV}, + {"sksl", TargetPlatform::kSkSL}, {"runtime-stage-metal", TargetPlatform::kRuntimeStageMetal}, {"runtime-stage-gles", TargetPlatform::kRuntimeStageGLES}, }; diff --git a/impeller/compiler/types.cc b/impeller/compiler/types.cc index 77634329b58ec..05fa2083cab0a 100644 --- a/impeller/compiler/types.cc +++ b/impeller/compiler/types.cc @@ -67,6 +67,8 @@ std::string TargetPlatformToString(TargetPlatform platform) { return "RuntimeStageMetal"; case TargetPlatform::kRuntimeStageGLES: return "RuntimeStageGLES"; + case TargetPlatform::kSkSL: + return "SkSL"; } FML_UNREACHABLE(); } @@ -108,6 +110,7 @@ bool TargetPlatformNeedsSL(TargetPlatform platform) { case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageMetal: case TargetPlatform::kRuntimeStageGLES: + case TargetPlatform::kSkSL: return true; case TargetPlatform::kUnknown: case TargetPlatform::kFlutterSPIRV: @@ -127,6 +130,7 @@ bool TargetPlatformNeedsReflection(TargetPlatform platform) { return true; case TargetPlatform::kUnknown: case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kSkSL: return false; } FML_UNREACHABLE(); @@ -202,6 +206,7 @@ spirv_cross::CompilerMSL::Options::Platform TargetPlatformToMSLPlatform( case TargetPlatform::kMetalDesktop: return spirv_cross::CompilerMSL::Options::Platform::macOS; case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kSkSL: case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageGLES: @@ -238,6 +243,7 @@ std::string TargetPlatformSLExtension(TargetPlatform platform) { case TargetPlatform::kRuntimeStageMetal: return "metal"; case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kSkSL: case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageGLES: @@ -266,6 +272,7 @@ bool TargetPlatformIsOpenGL(TargetPlatform platform) { case TargetPlatform::kMetalIOS: case TargetPlatform::kUnknown: case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kSkSL: return false; } FML_UNREACHABLE(); @@ -279,11 +286,11 @@ bool TargetPlatformIsMetal(TargetPlatform platform) { return true; case TargetPlatform::kUnknown: case TargetPlatform::kFlutterSPIRV: + case TargetPlatform::kSkSL: case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: case TargetPlatform::kRuntimeStageGLES: return false; - break; } FML_UNREACHABLE(); } diff --git a/impeller/compiler/types.h b/impeller/compiler/types.h index 3df651f4accc7..95d0257f9c99f 100644 --- a/impeller/compiler/types.h +++ b/impeller/compiler/types.h @@ -34,6 +34,7 @@ enum class TargetPlatform { kOpenGLDesktop, kRuntimeStageMetal, kRuntimeStageGLES, + kSkSL, }; bool TargetPlatformIsMetal(TargetPlatform platform); diff --git a/impeller/tools/impeller.gni b/impeller/tools/impeller.gni index 3ba80e79a321f..8e92129c97a17 100644 --- a/impeller/tools/impeller.gni +++ b/impeller/tools/impeller.gni @@ -237,6 +237,7 @@ template("impellerc") { "The flag to impellerc for target selection must be specified.") flutter_spirv = invoker.shader_target_flag == "--flutter-spirv" + sksl = invoker.shader_target_flag == "--sksl" if (!flutter_spirv) { assert( defined(invoker.sl_file_extension), @@ -275,8 +276,19 @@ template("impellerc") { ] if (flutter_spirv) { + not_needed([ "sksl" ]) args += [ "--spirv=$spirv_intermediate_path" ] outputs = [ spirv_intermediate ] + } else if (sksl) { + sl_intermediate = + "$generated_dir/{{source_file_part}}.${invoker.sl_file_extension}" + sl_intermediate_path = rebase_path(sl_intermediate, root_build_dir) + args += [ + "--sl=$sl_intermediate_path", + "--spirv=$spirv_intermediate_path", + ] + + outputs = [ sl_intermediate ] } else { sl_intermediate = "$generated_dir/{{source_file_part}}.${invoker.sl_file_extension}" diff --git a/lib/spirv/lib/spirv.dart b/lib/spirv/lib/spirv.dart index 56b0b67921ee6..14cd1d6ffcfb4 100644 --- a/lib/spirv/lib/spirv.dart +++ b/lib/spirv/lib/spirv.dart @@ -6,7 +6,6 @@ library spirv; import 'dart:convert'; -import 'dart:math'; import 'dart:typed_data'; // These parts only contain private members, all public diff --git a/lib/spirv/lib/src/constants.dart b/lib/spirv/lib/src/constants.dart index a5549822005e8..79d1a855779f9 100644 --- a/lib/spirv/lib/src/constants.dart +++ b/lib/spirv/lib/src/constants.dart @@ -79,7 +79,6 @@ const int _opVectorShuffle = 79; const int _opCompositeConstruct = 80; const int _opCompositeExtract = 81; const int _opImageSampleImplicitLod = 87; -const int _opImageQuerySize = 104; const int _opConvertFToS = 110; const int _opConvertSToF = 111; const int _opFNegate = 127; diff --git a/lib/spirv/test/general_shaders/BUILD.gn b/lib/spirv/test/general_shaders/BUILD.gn index 2edcb014ea138..e12f315b8dafd 100644 --- a/lib/spirv/test/general_shaders/BUILD.gn +++ b/lib/spirv/test/general_shaders/BUILD.gn @@ -7,15 +7,31 @@ import("//flutter/impeller/tools/impeller.gni") import("//flutter/testing/testing.gni") if (enable_unittests) { - impellerc("spirv_compile_general_shaders") { - shaders = [ - "blue_green_sampler.frag", - "children_and_uniforms.frag", - "functions.frag", - "simple.frag", - "uniforms.frag", + test_shaders = [ + "blue_green_sampler.frag", + "children_and_uniforms.frag", + "functions.frag", + "simple.frag", + "uniforms.frag", + ] + + group("general_shaders") { + deps = [ + ":sksl_compile_general_shaders", + ":spirv_compile_general_shaders", ] + } + impellerc("spirv_compile_general_shaders") { + shaders = test_shaders shader_target_flag = "--flutter-spirv" + intermediates_subdir = "spirv" + } + + impellerc("sksl_compile_general_shaders") { + shaders = test_shaders + shader_target_flag = "--sksl" + intermediates_subdir = "sksl" + sl_file_extension = "sksl" } } diff --git a/lib/spirv/test/supported_glsl_op_shaders/BUILD.gn b/lib/spirv/test/supported_glsl_op_shaders/BUILD.gn index b2c78ee13facb..3f8b442af6419 100644 --- a/lib/spirv/test/supported_glsl_op_shaders/BUILD.gn +++ b/lib/spirv/test/supported_glsl_op_shaders/BUILD.gn @@ -7,43 +7,59 @@ import("//flutter/impeller/tools/impeller.gni") import("//flutter/testing/testing.gni") if (enable_unittests) { - impellerc("spirv_compile_supported_glsl_shaders") { - shaders = [ - "10_fract.frag", - "11_radians.frag", - "12_degrees.frag", - "13_sin.frag", - "14_cos.frag", - "15_tan.frag", - "16_asin.frag", - "17_acos.frag", - "18_atan.frag", - "25_atan2.frag", - "26_pow.frag", - "27_exp.frag", - "28_log.frag", - "29_exp2.frag", - "30_log2.frag", - "31_sqrt.frag", - "32_inversesqrt.frag", - "37_fmin.frag", - "40_fmax.frag", - "43_fclamp.frag", - "46_fmix.frag", - "48_step.frag", - "49_smoothstep.frag", - "4_abs.frag", - "66_length.frag", - "67_distance.frag", - "68_cross.frag", - "69_normalize.frag", - "6_sign.frag", - "70_faceforward.frag", - "71_reflect.frag", - "8_floor.frag", - "9_ceil.frag", + test_shaders = [ + "10_fract.frag", + "11_radians.frag", + "12_degrees.frag", + "13_sin.frag", + "14_cos.frag", + "15_tan.frag", + "16_asin.frag", + "17_acos.frag", + "18_atan.frag", + "25_atan2.frag", + "26_pow.frag", + "27_exp.frag", + "28_log.frag", + "29_exp2.frag", + "30_log2.frag", + "31_sqrt.frag", + "32_inversesqrt.frag", + "37_fmin.frag", + "40_fmax.frag", + "43_fclamp.frag", + "46_fmix.frag", + "48_step.frag", + "49_smoothstep.frag", + "4_abs.frag", + "66_length.frag", + "67_distance.frag", + "68_cross.frag", + "69_normalize.frag", + "6_sign.frag", + "70_faceforward.frag", + "71_reflect.frag", + "8_floor.frag", + "9_ceil.frag", + ] + + group("supported_glsl_op_shaders") { + deps = [ + ":sksl_compile_supported_glsl_shaders", + ":spirv_compile_supported_glsl_shaders", ] + } + impellerc("spirv_compile_supported_glsl_shaders") { + shaders = test_shaders shader_target_flag = "--flutter-spirv" + intermediates_subdir = "spirv" + } + + impellerc("sksl_compile_supported_glsl_shaders") { + shaders = test_shaders + shader_target_flag = "--sksl" + intermediates_subdir = "sksl" + sl_file_extension = "sksl" } } diff --git a/lib/spirv/test/supported_op_shaders/BUILD.gn b/lib/spirv/test/supported_op_shaders/BUILD.gn index e81be7f649fd2..13e3ba55fceb5 100644 --- a/lib/spirv/test/supported_op_shaders/BUILD.gn +++ b/lib/spirv/test/supported_op_shaders/BUILD.gn @@ -7,39 +7,55 @@ import("//flutter/impeller/tools/impeller.gni") import("//flutter/testing/testing.gni") if (enable_unittests) { - impellerc("spirv_compile_supported_op_shaders") { - shaders = [ - "127_OpFNegate.frag", - "129_OpFAdd.frag", - "131_OpFSub.frag", - "142_OpVectorTimesScalar.frag", - "143_OpMatrixTimesScalar.frag", - "144_OpVectorTimesMatrix.frag", - "145_OpMatrixTimesVector.frag", - "146_OpMatrixTimesMatrix.frag", - "148_OpDot.frag", - "164_OpLogicalEqual.frag", - "165_OpLogicalNotEqual.frag", - "166_OpLogicalOr.frag", - "167_OpLogicalAnd.frag", - "168_OpLogicalNot.frag", - "180_OpFOrdEqual.frag", - "183_OpFUnordNotEqual.frag", - "184_OpFOrdLessThan.frag", - "186_OpFOrdGreaterThan.frag", - "188_OpFOrdLessThanEqual.frag", - "190_OpFOrdGreaterThanEqual.frag", - "19_OpTypeVoid.frag", - "20_OpTypeBool.frag", - "21_OpTypeInt.frag", - "22_OpTypeFloat.frag", - "23_OpTypeVector.frag", - "246_OpLoopMerge.frag", - "24_OpTypeMatrix.frag", - "250_OpBranchConditional.frag", - "33_OpTypeFunction.frag", + test_shaders = [ + "127_OpFNegate.frag", + "129_OpFAdd.frag", + "131_OpFSub.frag", + "142_OpVectorTimesScalar.frag", + "143_OpMatrixTimesScalar.frag", + "144_OpVectorTimesMatrix.frag", + "145_OpMatrixTimesVector.frag", + "146_OpMatrixTimesMatrix.frag", + "148_OpDot.frag", + "164_OpLogicalEqual.frag", + "165_OpLogicalNotEqual.frag", + "166_OpLogicalOr.frag", + "167_OpLogicalAnd.frag", + "168_OpLogicalNot.frag", + "180_OpFOrdEqual.frag", + "183_OpFUnordNotEqual.frag", + "184_OpFOrdLessThan.frag", + "186_OpFOrdGreaterThan.frag", + "188_OpFOrdLessThanEqual.frag", + "190_OpFOrdGreaterThanEqual.frag", + "19_OpTypeVoid.frag", + "20_OpTypeBool.frag", + "21_OpTypeInt.frag", + "22_OpTypeFloat.frag", + "23_OpTypeVector.frag", + "246_OpLoopMerge.frag", + "24_OpTypeMatrix.frag", + "250_OpBranchConditional.frag", + "33_OpTypeFunction.frag", + ] + + group("supported_op_shaders") { + deps = [ + ":sksl_compile_supported_op_shaders", + ":spirv_compile_supported_op_shaders", ] + } + impellerc("spirv_compile_supported_op_shaders") { + shaders = test_shaders shader_target_flag = "--flutter-spirv" + intermediates_subdir = "spirv" + } + + impellerc("sksl_compile_supported_op_shaders") { + shaders = test_shaders + shader_target_flag = "--sksl" + intermediates_subdir = "sksl" + sl_file_extension = "sksl" } } diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 92fa4967a15b5..b57bbaad28250 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -3906,27 +3906,65 @@ class FragmentProgram extends NativeFieldWrapperClass1 { /// [A current specification of valid SPIR-V is here.](https://github.com/flutter/engine/blob/master/lib/spirv/README.md) /// SPIR-V not meeting this specification will throw an exception. static Future compile({ - required ByteBuffer spirv, + ByteBuffer? spirv, + ByteBuffer? raw, + int? uniformFloatCount, + int? samplerCount, bool debugPrint = false, }) { + final bool spirvNull = spirv == null; + final bool rawNull = raw == null; + if (spirvNull && rawNull) { + throw ArgumentError( + 'FragmentProgram.compile must be passed either the "spirv" or the ' + '"raw" argument.', + ); + } + if (!spirvNull && !rawNull) { + throw ArgumentError( + 'FragmentProgram.compile must be passed only one of the "spirv" or the ' + '"raw" arguments.', + ); + } + if (!rawNull && uniformFloatCount == null && samplerCount == null) { + throw ArgumentError( + 'FragmentProgram.compile requires the "uniformFloatCount" or the ' + '"samplerCount" argument when passing the "raw" argument.', + ); + } // Delay compilation without creating a timer, which interacts poorly with the // flutter test framework. See: https://github.com/flutter/flutter/issues/104084 - return Future.microtask(() => FragmentProgram._(spirv: spirv, debugPrint: debugPrint)); + return Future.microtask(() => FragmentProgram._( + spirv: spirv, + raw: raw, + uniformFloatCount: uniformFloatCount ?? 0, + samplerCount: samplerCount ?? 0, + debugPrint: debugPrint, + )); } @pragma('vm:entry-point') FragmentProgram._({ - required ByteBuffer spirv, + ByteBuffer? spirv, + ByteBuffer? raw, + required int uniformFloatCount, + required int samplerCount, bool debugPrint = false, }) { _constructor(); - final spv.TranspileResult result = spv.transpile( - spirv, - spv.TargetLanguage.sksl, - ); - _init(result.src, debugPrint); - _uniformFloatCount = result.uniformFloatCount; - _samplerCount = result.samplerCount; + if (raw == null) { + final spv.TranspileResult result = spv.transpile( + spirv!, + spv.TargetLanguage.sksl, + ); + _init(result.src, debugPrint); + _uniformFloatCount = result.uniformFloatCount; + _samplerCount = result.samplerCount; + } else { + _init(utf8.decode(raw.asUint8List()), debugPrint); + _uniformFloatCount = uniformFloatCount; + _samplerCount = samplerCount; + } } late final int _uniformFloatCount; @@ -3993,9 +4031,12 @@ class FragmentProgram extends NativeFieldWrapperClass1 { } if (floatUniforms.length != _uniformFloatCount) { throw ArgumentError( - 'floatUniforms size: ${floatUniforms.length} must match given shader uniform count: $_uniformFloatCount.'); + 'floatUniforms size: ${floatUniforms.length} must match given shader ' + 'uniform count: $_uniformFloatCount.', + ); } - if (_samplerCount > 0 && (samplerUniforms == null || samplerUniforms.length != _samplerCount)) { + if (_samplerCount > 0 && + (samplerUniforms == null || samplerUniforms.length != _samplerCount)) { throw ArgumentError('samplerUniforms must have length $_samplerCount'); } if (samplerUniforms == null) { @@ -4004,7 +4045,10 @@ class FragmentProgram extends NativeFieldWrapperClass1 { samplerUniforms = [...samplerUniforms]; } final _FragmentShader shader = _FragmentShader( - this, Float32List.fromList(floatUniforms), samplerUniforms); + this, + Float32List.fromList(floatUniforms), + samplerUniforms, + ); _shader(shader, floatUniforms, samplerUniforms); return shader; } diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index 7883b77b7a95d..ad9def4c9508b 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -836,7 +836,10 @@ class ImageDescriptor { class FragmentProgram { static Future compile({ - required ByteBuffer spirv, + ByteBuffer? spirv, + ByteBuffer? raw, + int? uniformFloatCount, + int? samplerCount, bool debugPrint = false, }) { throw UnsupportedError('FragmentProgram is not supported for the CanvasKit or HTML renderers.'); diff --git a/testing/dart/fragment_shader_test.dart b/testing/dart/fragment_shader_test.dart index 4d352a9e26683..87d8be1f7fd9f 100644 --- a/testing/dart/fragment_shader_test.dart +++ b/testing/dart/fragment_shader_test.dart @@ -15,7 +15,9 @@ import 'shader_test_file_utils.dart'; void main() { test('throws exception for invalid shader', () async { - final ByteBuffer invalidBytes = Uint8List.fromList([1, 2, 3, 4, 5]).buffer; + final ByteBuffer invalidBytes = Uint8List.fromList( + [1, 2, 3, 4, 5], + ).buffer; try { await FragmentProgram.compile(spirv: invalidBytes); fail('expected compile to throw an exception'); @@ -24,7 +26,10 @@ void main() { }); test('simple shader renders correctly', () async { - final Uint8List shaderBytes = await spvFile('general_shaders', 'functions.frag.spirv').readAsBytes(); + final Uint8List shaderBytes = await shaderFile( + path.join('general_shaders', 'spirv'), + 'functions.frag.spirv', + ).readAsBytes(); final FragmentProgram program = await FragmentProgram.compile( spirv: shaderBytes.buffer, ); @@ -34,8 +39,26 @@ void main() { _expectShaderRendersGreen(shader); }); + test('simple sksl shader renders correctly', () async { + final Uint8List shaderBytes = await shaderFile( + path.join('general_shaders', 'sksl'), + 'functions.frag.sksl', + ).readAsBytes(); + final FragmentProgram program = await FragmentProgram.compile( + raw: shaderBytes.buffer, + uniformFloatCount: 1, + ); + final Shader shader = program.shader( + floatUniforms: Float32List.fromList([1]), + ); + _expectShaderRendersGreen(shader); + }); + test('shader with functions renders green', () async { - final ByteBuffer spirv = spvFile('general_shaders', 'functions.frag.spirv').readAsBytesSync().buffer; + final ByteBuffer spirv = shaderFile( + path.join('general_shaders', 'spirv'), + 'functions.frag.spirv', + ).readAsBytesSync().buffer; final FragmentProgram program = await FragmentProgram.compile( spirv: spirv, ); @@ -45,8 +68,26 @@ void main() { _expectShaderRendersGreen(shader); }); + test('sksl shader with functions renders green', () async { + final ByteBuffer sksl = shaderFile( + path.join('general_shaders', 'sksl'), + 'functions.frag.sksl', + ).readAsBytesSync().buffer; + final FragmentProgram program = await FragmentProgram.compile( + raw: sksl, + uniformFloatCount: 1, + ); + final Shader shader = program.shader( + floatUniforms: Float32List.fromList([1]), + ); + _expectShaderRendersGreen(shader); + }); + test('blue-green image renders green', () async { - final ByteBuffer spirv = spvFile('general_shaders', 'blue_green_sampler.frag.spirv').readAsBytesSync().buffer; + final ByteBuffer spirv = shaderFile( + path.join('general_shaders', 'spirv'), + 'blue_green_sampler.frag.spirv', + ).readAsBytesSync().buffer; final FragmentProgram program = await FragmentProgram.compile( debugPrint: true, spirv: spirv, @@ -55,14 +96,41 @@ void main() { final ImageShader imageShader = ImageShader( blueGreenImage, TileMode.clamp, TileMode.clamp, _identityMatrix); final Shader shader = program.shader( + floatUniforms: Float32List.fromList([]), + samplerUniforms: [imageShader], + ); + await _expectShaderRendersGreen(shader); + }); + + test('sksl blue-green image renders green', () async { + final ByteBuffer sksl = shaderFile( + path.join('general_shaders', 'sksl'), + 'blue_green_sampler.frag.sksl', + ).readAsBytesSync().buffer; + final FragmentProgram program = await FragmentProgram.compile( + debugPrint: true, + raw: sksl, + uniformFloatCount: 0, + samplerCount: 1, + ); + final Image blueGreenImage = await _createBlueGreenImage(); + final ImageShader imageShader = ImageShader( + blueGreenImage, TileMode.clamp, TileMode.clamp, _identityMatrix); + final Shader shader = program.shader( + floatUniforms: Float32List.fromList([]), samplerUniforms: [imageShader], ); await _expectShaderRendersGreen(shader); }); test('shader with uniforms renders correctly', () async { - final Uint8List shaderBytes = await spvFile('general_shaders', 'uniforms.frag.spirv').readAsBytes(); - final FragmentProgram program = await FragmentProgram.compile(spirv: shaderBytes.buffer); + final Uint8List shaderBytes = await shaderFile( + path.join('general_shaders', 'spirv'), + 'uniforms.frag.spirv', + ).readAsBytes(); + final FragmentProgram program = await FragmentProgram.compile( + spirv: shaderBytes.buffer, + ); final Shader shader = program.shader( floatUniforms: Float32List.fromList([ @@ -85,23 +153,74 @@ void main() { expect(toFloat(renderedBytes.getUint8(3)), closeTo(1.0, epsilon)); }); + test('sksl shader with uniforms renders correctly', () async { + final Uint8List shaderBytes = await shaderFile( + path.join('general_shaders', 'sksl'), + 'uniforms.frag.sksl', + ).readAsBytes(); + final FragmentProgram program = await FragmentProgram.compile( + raw: shaderBytes.buffer, + uniformFloatCount: 7, + ); + + final Shader shader = program.shader( + floatUniforms: Float32List.fromList([ + 0.0, // iFloatUniform + 0.25, // iVec2Uniform.x + 0.75, // iVec2Uniform.y + 0.0, // iMat2Uniform[0][0] + 0.0, // iMat2Uniform[0][1] + 0.0, // iMat2Uniform[1][0] + 1.0, // iMat2Uniform[1][1] + ])); + + final ByteData renderedBytes = (await _imageByteDataFromShader( + shader: shader, + ))!; + + expect(toFloat(renderedBytes.getUint8(0)), closeTo(0.0, epsilon)); + expect(toFloat(renderedBytes.getUint8(1)), closeTo(0.25, epsilon)); + expect(toFloat(renderedBytes.getUint8(2)), closeTo(0.75, epsilon)); + expect(toFloat(renderedBytes.getUint8(3)), closeTo(1.0, epsilon)); + }); + // Test all supported GLSL ops. See lib/spirv/lib/src/constants.dart - final Map supportedGLSLOpShaders = - _loadSpv('supported_glsl_op_shaders'); + final Map supportedGLSLOpShaders = _loadShaders( + path.join('supported_glsl_op_shaders', 'spirv'), + '.spirv', + ); expect(supportedGLSLOpShaders.isNotEmpty, true); _expectShadersRenderGreen(supportedGLSLOpShaders); _expectShadersHaveOp(supportedGLSLOpShaders, true /* glsl ops */); + final Map skslSupportedGLSLOpShaders = _loadShaders( + path.join('supported_glsl_op_shaders', 'sksl'), + '.sksl', + ); + expect(skslSupportedGLSLOpShaders.isNotEmpty, true); + _expectSkSLShadersRenderGreen(skslSupportedGLSLOpShaders); + // Test all supported instructions. See lib/spirv/lib/src/constants.dart - final Map supportedOpShaders = - _loadSpv('supported_op_shaders'); + final Map supportedOpShaders = _loadShaders( + path.join('supported_op_shaders', 'spirv'), + '.spirv', + ); expect(supportedOpShaders.isNotEmpty, true); _expectShadersRenderGreen(supportedOpShaders); _expectShadersHaveOp(supportedOpShaders, false /* glsl ops */); + final Map skslSupportedOpShaders = _loadShaders( + path.join('supported_op_shaders', 'sksl'), + '.sksl', + ); + expect(skslSupportedOpShaders.isNotEmpty, true); + _expectSkSLShadersRenderGreen(skslSupportedOpShaders); + test('equality depends on floatUniforms', () async { - final ByteBuffer spirv = spvFile('general_shaders', 'simple.frag.spirv') - .readAsBytesSync().buffer; + final ByteBuffer spirv = shaderFile( + path.join('general_shaders', 'spirv'), + 'simple.frag.spirv', + ).readAsBytesSync().buffer; final FragmentProgram program = await FragmentProgram.compile(spirv: spirv); final Float32List ones = Float32List.fromList([1]); final Float32List zeroes = Float32List.fromList([0]); @@ -121,11 +240,42 @@ void main() { } }); + test('sksl equality depends on floatUniforms', () async { + final ByteBuffer sksl = shaderFile( + path.join('general_shaders', 'sksl'), + 'simple.frag.sksl', + ).readAsBytesSync().buffer; + final FragmentProgram program = await FragmentProgram.compile( + raw: sksl, + uniformFloatCount: 1, + ); + final Float32List ones = Float32List.fromList([1]); + final Float32List zeroes = Float32List.fromList([0]); + + { + final Shader a = program.shader(floatUniforms: ones); + final Shader b = program.shader(floatUniforms: ones); + expect(a, b); + expect(a.hashCode, b.hashCode); + } + + { + final Shader a = program.shader(floatUniforms: ones); + final Shader b = program.shader(floatUniforms: zeroes); + expect(a, notEquals(b)); + expect(a.hashCode, notEquals(b.hashCode)); + } + }); + test('equality depends on spirv', () async { - final ByteBuffer spirvA = spvFile('general_shaders', 'simple.frag.spirv') - .readAsBytesSync().buffer; - final ByteBuffer spirvB = spvFile('general_shaders', 'uniforms.frag.spirv') - .readAsBytesSync().buffer; + final ByteBuffer spirvA = shaderFile( + path.join('general_shaders', 'spirv'), + 'simple.frag.spirv', + ).readAsBytesSync().buffer; + final ByteBuffer spirvB = shaderFile( + path.join('general_shaders', 'spirv'), + 'uniforms.frag.spirv', + ).readAsBytesSync().buffer; final FragmentProgram programA = await FragmentProgram.compile(spirv: spirvA); final FragmentProgram programB = await FragmentProgram.compile(spirv: spirvB); final Shader a = programA.shader(); @@ -135,20 +285,70 @@ void main() { expect(a.hashCode, notEquals(b.hashCode)); }); + test('equality depends on data', () async { + final ByteBuffer skslA = shaderFile( + path.join('general_shaders', 'sksl'), + 'simple.frag.sksl', + ).readAsBytesSync().buffer; + final ByteBuffer skslB = shaderFile( + path.join('general_shaders', 'sksl'), + 'uniforms.frag.sksl', + ).readAsBytesSync().buffer; + final FragmentProgram programA = await FragmentProgram.compile( + raw: skslA, + uniformFloatCount: 1, + ); + final FragmentProgram programB = await FragmentProgram.compile( + raw: skslB, + uniformFloatCount: 7, + ); + final Shader a = programA.shader(); + final Shader b = programB.shader(); + + expect(a, notEquals(b)); + expect(a.hashCode, notEquals(b.hashCode)); + }); + test('Compilation does not create a Timer object', () async { - final ByteBuffer spirvA = spvFile('general_shaders', 'simple.frag.spirv') - .readAsBytesSync().buffer; + final ByteBuffer spirvA = shaderFile( + path.join('general_shaders', 'spirv'), + 'simple.frag.spirv', + ).readAsBytesSync().buffer; bool createdTimer = false; - final ZoneSpecification specification = ZoneSpecification(createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void Function() f) { - createdTimer = true; - return parent.createTimer(zone, duration, f); - }); + final ZoneSpecification specification = ZoneSpecification( + createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void Function() f) { + createdTimer = true; + return parent.createTimer(zone, duration, f); + }, + ); await runZoned(() async { await FragmentProgram.compile(spirv: spirvA); }, zoneSpecification: specification); expect(createdTimer, false); }); + + test('sksl "compile" does not create a Timer object', () async { + final ByteBuffer skslA = shaderFile( + path.join('general_shaders', 'sksl'), + 'simple.frag.sksl', + ).readAsBytesSync().buffer; + bool createdTimer = false; + final ZoneSpecification specification = ZoneSpecification( + createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void Function() f) { + createdTimer = true; + return parent.createTimer(zone, duration, f); + }, + ); + await runZoned(() async { + await FragmentProgram.compile( + raw: skslA, + uniformFloatCount: 1, + ); + }, zoneSpecification: specification); + + expect(createdTimer, false); + }); } // Expect that all of the spirv shaders in this folder render green. @@ -168,6 +368,24 @@ void _expectShadersRenderGreen(Map shaders) { } } +// Expect that all of the shaders in this folder render green. +// Keeping the outer loop of the test synchronous allows for easy printing +// of the file name within the test case. +void _expectSkSLShadersRenderGreen(Map shaders) { + for (final String key in shaders.keys) { + test('SkSL $key renders green', () async { + final FragmentProgram program = await FragmentProgram.compile( + raw: shaders[key]!, + uniformFloatCount: 1, + ); + final Shader shader = program.shader( + floatUniforms: Float32List.fromList([1]), + ); + _expectShaderRendersGreen(shader); + }); + } +} + void _expectShadersHaveOp(Map shaders, bool glsl) { for (final String key in shaders.keys) { test('$key contains opcode', () { @@ -244,17 +462,17 @@ Future _imageByteDataFromShader({ // $FLUTTER_BUILD_DIRECTORY/gen/flutter/lib/spirv/test/$leafFolderName // This is synchronous so that tests can be inside of a loop with // the proper test name. -Map _loadSpv(String leafFolderName) { +Map _loadShaders(String leafFolderName, String ext) { final Map out = SplayTreeMap(); - final Directory directory = spvDirectory(leafFolderName); + final Directory directory = shaderDirectory(leafFolderName); if (!directory.existsSync()) { return out; } directory .listSync() - .where((FileSystemEntity entry) => path.extension(entry.path) == '.spirv') + .where((FileSystemEntity entry) => path.extension(entry.path) == ext) .forEach((FileSystemEntity entry) { final String key = path.basenameWithoutExtension(entry.path); out[key] = (entry as File).readAsBytesSync().buffer; @@ -266,7 +484,6 @@ Map _loadSpv(String leafFolderName) { const int _shaderImageDimension = 4; const Color _greenColor = Color(0xFF00FF00); -const Color _blueColor = Color(0xFF0000FF); // Precision for checking uniform values. const double epsilon = 0.5 / 255.0; @@ -279,8 +496,8 @@ String toHexString(int color) => '#${color.toRadixString(16)}'; // 10x10 image where the left half is blue and the right half is // green. Future _createBlueGreenImage() async { - final int length = 10; - final int bytesPerPixel = 4; + const int length = 10; + const int bytesPerPixel = 4; final Uint8List pixels = Uint8List(length * length * bytesPerPixel); int i = 0; for (int y = 0; y < length; y++) { @@ -305,9 +522,6 @@ Future _createBlueGreenImage() async { return frame.image; } -// A single uniform with value 1. -final Float32List _singleUniform = Float32List.fromList([1]); - final Float64List _identityMatrix = Float64List.fromList([ 1, 0, 0, 0, 0, 1, 0, 0, diff --git a/testing/dart/shader_test_file_utils.dart b/testing/dart/shader_test_file_utils.dart index ebc5b35a40ade..17360abbe767e 100644 --- a/testing/dart/shader_test_file_utils.dart +++ b/testing/dart/shader_test_file_utils.dart @@ -17,7 +17,7 @@ const String _testPath = 'gen/flutter/lib/spirv/test'; /// Gets the [Directory] of .spv files that are generated by `lib/spirv/test`. /// /// `folderName` is a leaf folder within the generated directory. -Directory spvDirectory(String leafFolderName) { +Directory shaderDirectory(String leafFolderName) { return Directory(path.joinAll([ ...path.split(_flutterBuildDirectoryPath()), ...path.split(_testPath), @@ -30,11 +30,11 @@ Directory spvDirectory(String leafFolderName) { /// `folderName` is the leaf folder within the generated directory. /// /// `fileName` is the name of the filer within `folderName`. -File spvFile(String folderName, String fileName) { +File shaderFile(String folderName, String fileName) { return File(path.joinAll([ ...path.split(_flutterBuildDirectoryPath()), ...path.split(_testPath), folderName, fileName, ])); -} \ No newline at end of file +} diff --git a/testing/dart/spirv_exception_test.dart b/testing/dart/spirv_exception_test.dart index 5d96e34220d7f..013024cc05de1 100644 --- a/testing/dart/spirv_exception_test.dart +++ b/testing/dart/spirv_exception_test.dart @@ -33,7 +33,7 @@ void main() { } Stream _exceptionShaders() async* { - final Directory dir = spvDirectory('exception_shaders'); + final Directory dir = shaderDirectory('exception_shaders'); await for (final FileSystemEntity entry in dir.list()) { if (entry is! File) { continue;