diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index aa667eca97f0d..d7e27826c6080 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -2704,10 +2704,6 @@ TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) { // Regression test for https://github.com/flutter/flutter/issues/126701 . TEST_P(AiksTest, CanRenderClippedRuntimeEffects) { - if (!BackendSupportsFragmentProgram()) { - GTEST_SKIP_("This backend doesn't support runtime effects."); - } - auto runtime_stages = OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); @@ -2741,10 +2737,6 @@ TEST_P(AiksTest, CanRenderClippedRuntimeEffects) { } TEST_P(AiksTest, DrawPaintTransformsBounds) { - if (!BackendSupportsFragmentProgram()) { - GTEST_SKIP_("This backend doesn't support runtime effects."); - } - auto runtime_stages = OpenAssetAsRuntimeStage("gradient.frag.iplr"); auto runtime_stage = runtime_stages[RuntimeStageBackend::kMetal]; ASSERT_TRUE(runtime_stage); diff --git a/impeller/compiler/compiler.cc b/impeller/compiler/compiler.cc index 261ab60a64360..6123ad3f15982 100644 --- a/impeller/compiler/compiler.cc +++ b/impeller/compiler/compiler.cc @@ -19,6 +19,7 @@ #include "impeller/compiler/includer.h" #include "impeller/compiler/logger.h" #include "impeller/compiler/spirv_compiler.h" +#include "impeller/compiler/types.h" #include "impeller/compiler/uniform_sorter.h" #include "impeller/compiler/utilities.h" @@ -122,15 +123,13 @@ static CompilerBackend CreateMSLCompiler( static CompilerBackend CreateVulkanCompiler( const spirv_cross::ParsedIR& ir, const SourceOptions& source_options) { - // TODO(dnfield): It seems like what we'd want is a CompilerGLSL with - // vulkan_semantics set to true, but that has regressed some things on GLES - // somehow. In the mean time, go back to using CompilerMSL, but set the Metal - // Language version to something really high so that we don't get weird - // complaints about using Metal features while trying to build Vulkan shaders. - // https://github.com/flutter/flutter/issues/123795 - return CreateMSLCompiler( - ir, source_options, - spirv_cross::CompilerMSL::Options::make_msl_version(3, 0, 0)); + auto gl_compiler = std::make_shared(ir); + spirv_cross::CompilerGLSL::Options sl_options; + sl_options.force_zero_initialized_variables = true; + sl_options.vertex.fixup_clipspace = true; + sl_options.vulkan_semantics = true; + gl_compiler->set_common_options(sl_options); + return CompilerBackend(gl_compiler); } static CompilerBackend CreateGLSLCompiler(const spirv_cross::ParsedIR& ir, @@ -302,18 +301,23 @@ Compiler::Compiler(const std::shared_ptr& source_mapping, } break; case TargetPlatform::kOpenGLES: case TargetPlatform::kOpenGLDesktop: - case TargetPlatform::kVulkan: { + case TargetPlatform::kVulkan: + case TargetPlatform::kRuntimeStageVulkan: { SPIRVCompilerTargetEnv target; target.env = shaderc_target_env::shaderc_target_env_vulkan; target.version = shaderc_env_version::shaderc_env_version_vulkan_1_1; target.spirv_version = shaderc_spirv_version::shaderc_spirv_version_1_3; + if (source_options.target_platform == + TargetPlatform::kRuntimeStageVulkan) { + spirv_options.macro_definitions.push_back("IMPELLER_GRAPHICS_BACKEND"); + spirv_options.relaxed_vulkan_rules = true; + } spirv_options.target = target; } break; case TargetPlatform::kRuntimeStageMetal: - case TargetPlatform::kRuntimeStageGLES: - case TargetPlatform::kRuntimeStageVulkan: { + case TargetPlatform::kRuntimeStageGLES: { SPIRVCompilerTargetEnv target; target.env = shaderc_target_env::shaderc_target_env_opengl; @@ -398,7 +402,8 @@ Compiler::Compiler(const std::shared_ptr& source_mapping, // If the target is Vulkan, our shading language is SPIRV which we already // have. We just need to strip it of debug information. If it isn't, we need // to invoke the appropriate compiler to compile the SPIRV to the target SL. - if (source_options.target_platform == TargetPlatform::kVulkan) { + if (source_options.target_platform == TargetPlatform::kVulkan || + source_options.target_platform == TargetPlatform::kRuntimeStageVulkan) { auto stripped_spirv_options = spirv_options; stripped_spirv_options.generate_debug_info = false; sl_mapping_ = spv_compiler.CompileToSPV( diff --git a/impeller/compiler/compiler_backend.cc b/impeller/compiler/compiler_backend.cc index d6111c9f232e5..58afead588383 100644 --- a/impeller/compiler/compiler_backend.cc +++ b/impeller/compiler/compiler_backend.cc @@ -13,7 +13,10 @@ CompilerBackend::CompilerBackend(MSLCompiler compiler) : CompilerBackend(Type::kMSL, compiler) {} CompilerBackend::CompilerBackend(GLSLCompiler compiler) - : CompilerBackend(Type::kGLSL, compiler) {} + : CompilerBackend(compiler->get_common_options().vulkan_semantics + ? Type::kGLSLVulkan + : Type::kGLSL, + compiler) {} CompilerBackend::CompilerBackend(SkSLCompiler compiler) : CompilerBackend(Type::kSkSL, compiler) {} diff --git a/impeller/compiler/compiler_backend.h b/impeller/compiler/compiler_backend.h index 4b2aabd0e00a3..d2918177fa983 100644 --- a/impeller/compiler/compiler_backend.h +++ b/impeller/compiler/compiler_backend.h @@ -27,6 +27,7 @@ struct CompilerBackend { enum class Type { kMSL, kGLSL, + kGLSLVulkan, kSkSL, }; diff --git a/impeller/compiler/reflector.cc b/impeller/compiler/reflector.cc index e38b89faa0ce3..e01755bccc38f 100644 --- a/impeller/compiler/reflector.cc +++ b/impeller/compiler/reflector.cc @@ -12,6 +12,7 @@ #include #include "flutter/fml/logging.h" +#include "fml/backtrace.h" #include "impeller/base/strings.h" #include "impeller/base/validation.h" #include "impeller/compiler/code_gen_template.h" @@ -23,54 +24,12 @@ #include "impeller/geometry/half.h" #include "impeller/geometry/matrix.h" #include "impeller/geometry/scalar.h" +#include "impeller/runtime_stage/runtime_stage.h" +#include "spirv_common.hpp" namespace impeller { namespace compiler { -static std::string BaseTypeToString(spirv_cross::SPIRType::BaseType type) { - using Type = spirv_cross::SPIRType::BaseType; - switch (type) { - case Type::Void: - return "ShaderType::kVoid"; - case Type::Boolean: - return "ShaderType::kBoolean"; - case Type::SByte: - return "ShaderType::kSignedByte"; - case Type::UByte: - return "ShaderType::kUnsignedByte"; - case Type::Short: - return "ShaderType::kSignedShort"; - case Type::UShort: - return "ShaderType::kUnsignedShort"; - case Type::Int: - return "ShaderType::kSignedInt"; - case Type::UInt: - return "ShaderType::kUnsignedInt"; - case Type::Int64: - return "ShaderType::kSignedInt64"; - case Type::UInt64: - return "ShaderType::kUnsignedInt64"; - case Type::AtomicCounter: - return "ShaderType::kAtomicCounter"; - case Type::Half: - return "ShaderType::kHalfFloat"; - case Type::Float: - return "ShaderType::kFloat"; - case Type::Double: - return "ShaderType::kDouble"; - case Type::Struct: - return "ShaderType::kStruct"; - case Type::Image: - return "ShaderType::kImage"; - case Type::SampledImage: - return "ShaderType::kSampledImage"; - case Type::Sampler: - return "ShaderType::kSampler"; - default: - return "ShaderType::kUnknown"; - } -} - static std::string ExecutionModelToString(spv::ExecutionModel model) { switch (model) { case spv::ExecutionModel::ExecutionModelVertex: @@ -370,7 +329,6 @@ std::shared_ptr Reflector::GenerateRuntimeStageData() // Sort the IR so that the uniforms are in declaration order. std::vector uniforms = SortUniforms(ir_.get(), compiler_.GetCompiler()); - for (auto& sorted_id : uniforms) { auto var = ir_->ids[sorted_id].get(); const auto spir_type = compiler_->get_type(var.basetype); @@ -383,9 +341,69 @@ std::shared_ptr Reflector::GenerateRuntimeStageData() uniform_description.columns = spir_type.columns; uniform_description.bit_width = spir_type.width; uniform_description.array_elements = GetArrayElements(spir_type); + FML_CHECK(data->backend != RuntimeStageBackend::kVulkan || + spir_type.basetype == + spirv_cross::SPIRType::BaseType::SampledImage) + << "Vulkan runtime effect had unexpected uniforms outside of the " + "uniform buffer object."; data->uniforms.emplace_back(std::move(uniform_description)); } + const auto ubos = compiler_->get_shader_resources().uniform_buffers; + if (data->backend == RuntimeStageBackend::kVulkan && !ubos.empty()) { + if (ubos.size() != 1 && ubos[0].name != RuntimeStage::kVulkanUBOName) { + VALIDATION_LOG << "Expected a single UBO resource named " + "'" + << RuntimeStage::kVulkanUBOName + << "' " + "for Vulkan runtime stage backend."; + return nullptr; + } + + const auto& ubo = ubos[0]; + + auto members = ReadStructMembers(ubo.type_id); + std::vector struct_layout; + size_t float_count = 0; + + for (size_t i = 0; i < members.size(); i += 1) { + const auto& member = members[i]; + std::vector bytes; + switch (member.underlying_type) { + case StructMember::UnderlyingType::kPadding: { + size_t padding_count = + (member.size + sizeof(float) - 1) / sizeof(float); + while (padding_count > 0) { + struct_layout.push_back(0); + padding_count--; + } + break; + } + case StructMember::UnderlyingType::kFloat: { + size_t member_float_count = member.byte_length / sizeof(float); + float_count += member_float_count; + while (member_float_count > 0) { + struct_layout.push_back(1); + member_float_count--; + } + break; + } + case StructMember::UnderlyingType::kOther: + VALIDATION_LOG << "Non-floating-type struct member " << member.name + << " is not supported."; + return nullptr; + } + } + data->uniforms.emplace_back(UniformDescription{ + .name = ubo.name, + .location = 64, // Magic constant that must match the descriptor set + // location for fragment programs. + .type = spirv_cross::SPIRType::Struct, + .struct_layout = std::move(struct_layout), + .struct_float_count = float_count, + }); + } + // We only need to worry about storing vertex attributes. if (entrypoints.front().execution_model == spv::ExecutionModelVertex) { const auto inputs = compiler_->get_shader_resources().stage_inputs; @@ -532,6 +550,8 @@ static std::string ToString(CompilerBackend::Type type) { return "Metal Shading Language"; case CompilerBackend::Type::kGLSL: return "OpenGL Shading Language"; + case CompilerBackend::Type::kGLSLVulkan: + return "OpenGL Shading Language (Relaxed Vulkan Semantics)"; case CompilerBackend::Type::kSkSL: return "SkSL Shading Language"; } @@ -627,7 +647,7 @@ std::optional Reflector::ReflectType( const auto type = compiler_->get_type(type_id); - result["type_name"] = BaseTypeToString(type.basetype); + result["type_name"] = StructMember::BaseTypeToString(type.basetype); result["bit_width"] = type.width; result["vec_size"] = type.vecsize; result["columns"] = type.columns; @@ -637,7 +657,8 @@ std::optional Reflector::ReflectType( auto member = nlohmann::json::object_t{}; member["name"] = struct_member.name; member["type"] = struct_member.type; - member["base_type"] = BaseTypeToString(struct_member.base_type); + member["base_type"] = + StructMember::BaseTypeToString(struct_member.base_type); member["offset"] = struct_member.offset; member["size"] = struct_member.size; member["byte_length"] = struct_member.byte_length; @@ -1117,7 +1138,8 @@ nlohmann::json::object_t Reflector::EmitStructDefinition( auto& member = members.emplace_back(nlohmann::json::object_t{}); member["name"] = struct_member.name; member["type"] = struct_member.type; - member["base_type"] = BaseTypeToString(struct_member.base_type); + member["base_type"] = + StructMember::BaseTypeToString(struct_member.base_type); member["offset"] = struct_member.offset; member["byte_length"] = struct_member.byte_length; if (struct_member.array_elements.has_value()) { @@ -1142,7 +1164,7 @@ static VertexType VertexTypeFromInputResource( const spirv_cross::Resource* resource) { VertexType result; result.variable_name = resource->name; - const auto type = compiler.get_type(resource->type_id); + const auto& type = compiler.get_type(resource->type_id); result.base_type = type.basetype; const auto total_size = type.columns * type.vecsize * type.width / 8u; result.byte_length = total_size; diff --git a/impeller/compiler/reflector.h b/impeller/compiler/reflector.h index bf8961e4d9f57..3de086c00a553 100644 --- a/impeller/compiler/reflector.h +++ b/impeller/compiler/reflector.h @@ -11,10 +11,13 @@ #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" +#include "fml/logging.h" +#include "impeller/base/strings.h" #include "impeller/compiler/compiler_backend.h" #include "impeller/compiler/runtime_stage_data.h" #include "impeller/compiler/shader_bundle_data.h" #include "inja/inja.hpp" +#include "spirv_common.hpp" #include "spirv_msl.hpp" #include "spirv_parser.hpp" @@ -22,6 +25,14 @@ namespace impeller { namespace compiler { struct StructMember { + // Runtime stages on Vulkan use this information to validate that a struct + // only contains floats and encode where padding gets inserted. + enum class UnderlyingType { + kPadding, + kFloat, + kOther, + }; + std::string type; spirv_cross::SPIRType::BaseType base_type; std::string name; @@ -30,6 +41,87 @@ struct StructMember { size_t byte_length = 0u; std::optional array_elements = std::nullopt; size_t element_padding = 0u; + UnderlyingType underlying_type = UnderlyingType::kOther; + + static std::string BaseTypeToString(spirv_cross::SPIRType::BaseType type) { + using Type = spirv_cross::SPIRType::BaseType; + switch (type) { + case Type::Void: + return "ShaderType::kVoid"; + case Type::Boolean: + return "ShaderType::kBoolean"; + case Type::SByte: + return "ShaderType::kSignedByte"; + case Type::UByte: + return "ShaderType::kUnsignedByte"; + case Type::Short: + return "ShaderType::kSignedShort"; + case Type::UShort: + return "ShaderType::kUnsignedShort"; + case Type::Int: + return "ShaderType::kSignedInt"; + case Type::UInt: + return "ShaderType::kUnsignedInt"; + case Type::Int64: + return "ShaderType::kSignedInt64"; + case Type::UInt64: + return "ShaderType::kUnsignedInt64"; + case Type::AtomicCounter: + return "ShaderType::kAtomicCounter"; + case Type::Half: + return "ShaderType::kHalfFloat"; + case Type::Float: + return "ShaderType::kFloat"; + case Type::Double: + return "ShaderType::kDouble"; + case Type::Struct: + return "ShaderType::kStruct"; + case Type::Image: + return "ShaderType::kImage"; + case Type::SampledImage: + return "ShaderType::kSampledImage"; + case Type::Sampler: + return "ShaderType::kSampler"; + default: + return "ShaderType::kUnknown"; + } + FML_UNREACHABLE(); + } + + static UnderlyingType DetermineUnderlyingType( + spirv_cross::SPIRType::BaseType type) { + switch (type) { + case spirv_cross::SPIRType::Void: + return UnderlyingType::kPadding; + case spirv_cross::SPIRType::Float: + return UnderlyingType::kFloat; + case spirv_cross::SPIRType::Unknown: + case spirv_cross::SPIRType::Boolean: + case spirv_cross::SPIRType::SByte: + case spirv_cross::SPIRType::UByte: + case spirv_cross::SPIRType::Short: + case spirv_cross::SPIRType::UShort: + case spirv_cross::SPIRType::Int: + case spirv_cross::SPIRType::UInt: + case spirv_cross::SPIRType::Int64: + case spirv_cross::SPIRType::UInt64: + case spirv_cross::SPIRType::AtomicCounter: + case spirv_cross::SPIRType::Half: + case spirv_cross::SPIRType::Double: + case spirv_cross::SPIRType::Struct: + case spirv_cross::SPIRType::Image: + case spirv_cross::SPIRType::SampledImage: + case spirv_cross::SPIRType::Sampler: + case spirv_cross::SPIRType::AccelerationStructure: + case spirv_cross::SPIRType::RayQuery: + case spirv_cross::SPIRType::ControlPointArray: + case spirv_cross::SPIRType::Interpolant: + case spirv_cross::SPIRType::Char: + default: + return UnderlyingType::kOther; + } + FML_UNREACHABLE(); + } StructMember(std::string p_type, spirv_cross::SPIRType::BaseType p_base_type, @@ -38,7 +130,8 @@ struct StructMember { size_t p_size, size_t p_byte_length, std::optional p_array_elements, - size_t p_element_padding) + size_t p_element_padding, + UnderlyingType p_underlying_type = UnderlyingType::kOther) : type(std::move(p_type)), base_type(p_base_type), name(std::move(p_name)), @@ -46,7 +139,8 @@ struct StructMember { size(p_size), byte_length(p_byte_length), array_elements(p_array_elements), - element_padding(p_element_padding) {} + element_padding(p_element_padding), + underlying_type(DetermineUnderlyingType(p_base_type)) {} }; class Reflector { diff --git a/impeller/compiler/runtime_stage_data.cc b/impeller/compiler/runtime_stage_data.cc index 87db421d688a1..b45926305f55b 100644 --- a/impeller/compiler/runtime_stage_data.cc +++ b/impeller/compiler/runtime_stage_data.cc @@ -61,32 +61,23 @@ static std::optional ToJsonStage(spv::ExecutionModel stage) { static std::optional ToUniformType( spirv_cross::SPIRType::BaseType type) { switch (type) { + case spirv_cross::SPIRType::Float: + return fb::UniformDataType::kFloat; + case spirv_cross::SPIRType::SampledImage: + return fb::UniformDataType::kSampledImage; + case spirv_cross::SPIRType::Struct: + return fb::UniformDataType::kStruct; case spirv_cross::SPIRType::Boolean: - return fb::UniformDataType::kBoolean; case spirv_cross::SPIRType::SByte: - return fb::UniformDataType::kSignedByte; case spirv_cross::SPIRType::UByte: - return fb::UniformDataType::kUnsignedByte; case spirv_cross::SPIRType::Short: - return fb::UniformDataType::kSignedShort; case spirv_cross::SPIRType::UShort: - return fb::UniformDataType::kUnsignedShort; case spirv_cross::SPIRType::Int: - return fb::UniformDataType::kSignedInt; case spirv_cross::SPIRType::UInt: - return fb::UniformDataType::kUnsignedInt; case spirv_cross::SPIRType::Int64: - return fb::UniformDataType::kSignedInt64; case spirv_cross::SPIRType::UInt64: - return fb::UniformDataType::kUnsignedInt64; case spirv_cross::SPIRType::Half: - return fb::UniformDataType::kHalfFloat; - case spirv_cross::SPIRType::Float: - return fb::UniformDataType::kFloat; case spirv_cross::SPIRType::Double: - return fb::UniformDataType::kDouble; - case spirv_cross::SPIRType::SampledImage: - return fb::UniformDataType::kSampledImage; case spirv_cross::SPIRType::AccelerationStructure: case spirv_cross::SPIRType::AtomicCounter: case spirv_cross::SPIRType::Char: @@ -95,7 +86,6 @@ static std::optional ToUniformType( case spirv_cross::SPIRType::Interpolant: case spirv_cross::SPIRType::RayQuery: case spirv_cross::SPIRType::Sampler: - case spirv_cross::SPIRType::Struct: case spirv_cross::SPIRType::Unknown: case spirv_cross::SPIRType::Void: return std::nullopt; @@ -174,6 +164,8 @@ static std::optional ToJsonType( return 11; // fb::UniformDataType::kDouble; case spirv_cross::SPIRType::SampledImage: return 12; // fb::UniformDataType::kSampledImage; + case spirv_cross::SPIRType::Struct: + return 13; case spirv_cross::SPIRType::AccelerationStructure: case spirv_cross::SPIRType::AtomicCounter: case spirv_cross::SPIRType::Char: @@ -182,7 +174,6 @@ static std::optional ToJsonType( case spirv_cross::SPIRType::Interpolant: case spirv_cross::SPIRType::RayQuery: case spirv_cross::SPIRType::Sampler: - case spirv_cross::SPIRType::Struct: case spirv_cross::SPIRType::Unknown: case spirv_cross::SPIRType::Void: return std::nullopt; @@ -273,13 +264,9 @@ std::shared_ptr RuntimeStageData::CreateJsonMapping() const { uniform_object[kUniformTypeKey] = uniform_type.value(); uniform_object[kUniformBitWidthKey] = uniform.bit_width; + uniform_object[kUniformArrayElementsKey] = + uniform.array_elements.value_or(0); - if (uniform.array_elements.has_value()) { - uniform_object[kUniformArrayElementsKey] = - uniform.array_elements.value(); - } else { - uniform_object[kUniformArrayElementsKey] = 0; - } uniforms.push_back(uniform_object); } @@ -339,6 +326,11 @@ std::unique_ptr RuntimeStageData::CreateStageFlatbuffer( desc->array_elements = uniform.array_elements.value(); } + for (const auto& byte_type : uniform.struct_layout) { + desc->struct_layout.push_back(static_cast(byte_type)); + } + desc->struct_float_count = uniform.struct_float_count; + runtime_stage->uniforms.emplace_back(std::move(desc)); } diff --git a/impeller/compiler/spirv_compiler.cc b/impeller/compiler/spirv_compiler.cc index a5bd9b66b8808..211073ab5ddca 100644 --- a/impeller/compiler/spirv_compiler.cc +++ b/impeller/compiler/spirv_compiler.cc @@ -310,6 +310,8 @@ shaderc::CompileOptions SPIRVCompilerOptions::BuildShadercOptions() const { options.SetIncluder(UniqueIncluder::Make(includer)); } + options.SetVulkanRulesRelaxed(relaxed_vulkan_rules); + return options; } diff --git a/impeller/compiler/spirv_compiler.h b/impeller/compiler/spirv_compiler.h index 9f6463d4de542..3798b6d53601d 100644 --- a/impeller/compiler/spirv_compiler.h +++ b/impeller/compiler/spirv_compiler.h @@ -49,6 +49,8 @@ struct SPIRVCompilerOptions { std::shared_ptr includer; + bool relaxed_vulkan_rules = false; + shaderc::CompileOptions BuildShadercOptions() const; }; diff --git a/impeller/compiler/types.h b/impeller/compiler/types.h index db108c9824477..c41423014071f 100644 --- a/impeller/compiler/types.h +++ b/impeller/compiler/types.h @@ -53,6 +53,8 @@ struct UniformDescription { size_t columns = 0u; size_t bit_width = 0u; std::optional array_elements = std::nullopt; + std::vector struct_layout = {}; + size_t struct_float_count = 0u; }; struct InputDescription { diff --git a/impeller/core/runtime_types.cc b/impeller/core/runtime_types.cc index 481ee9253f6ea..08811275967ba 100644 --- a/impeller/core/runtime_types.cc +++ b/impeller/core/runtime_types.cc @@ -13,6 +13,7 @@ size_t RuntimeUniformDescription::GetSize() const { // NOLINTNEXTLINE(bugprone-unchecked-optional-access) size *= array_elements.value(); } + size += sizeof(float) * struct_layout.size(); return size; } diff --git a/impeller/core/runtime_types.h b/impeller/core/runtime_types.h index c4f4b38738867..48a8744e0e654 100644 --- a/impeller/core/runtime_types.h +++ b/impeller/core/runtime_types.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace impeller { @@ -19,19 +20,9 @@ enum class RuntimeStageBackend { }; enum RuntimeUniformType { - kBoolean, - kSignedByte, - kUnsignedByte, - kSignedShort, - kUnsignedShort, - kSignedInt, - kUnsignedInt, - kSignedInt64, - kUnsignedInt64, - kHalfFloat, kFloat, - kDouble, kSampledImage, + kStruct, }; enum class RuntimeShaderStage { @@ -49,9 +40,11 @@ struct RuntimeUniformDescription { std::string name; size_t location = 0u; RuntimeUniformType type = RuntimeUniformType::kFloat; - RuntimeUniformDimensions dimensions; - size_t bit_width; + RuntimeUniformDimensions dimensions = {}; + size_t bit_width = 0u; std::optional array_elements; + std::vector struct_layout = {}; + size_t struct_float_count = 0u; /// @brief Computes the total number of bytes that this uniform requires. size_t GetSize() const; diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index 17c194e64e022..634ae0b8e6fa7 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -490,4 +490,18 @@ void ContentContext::SetWireframe(bool wireframe) { wireframe_ = wireframe; } +std::shared_ptr> +ContentContext::GetCachedRuntimeEffectPipeline( + const std::string& unique_entrypoint_name, + const ContentContextOptions& options, + const std::function>()>& + create_callback) const { + RuntimeEffectPipelineKey key{unique_entrypoint_name, options}; + auto it = runtime_effect_pipelines_.find(key); + if (it == runtime_effect_pipelines_.end()) { + it = runtime_effect_pipelines_.insert(it, {key, create_callback()}); + } + return it->second; +} + } // namespace impeller diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index c1b287e633dda..94d4af747750c 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -730,6 +730,21 @@ class ContentContext { return render_target_cache_; } + /// RuntimeEffect pipelines must be obtained via this method to avoid + /// re-creating them every frame. + /// + /// The unique_entrypoint_name comes from RuntimeEffect::GetEntrypoint. + /// Impellerc generates a unique entrypoint name for runtime effect shaders + /// based on the input file name and shader stage. + /// + /// The create_callback is synchronously invoked exactly once if a cached + /// pipeline is not found. + std::shared_ptr> GetCachedRuntimeEffectPipeline( + const std::string& unique_entrypoint_name, + const ContentContextOptions& options, + const std::function>()>& + create_callback) const; + /// @brief Retrieve the currnent host buffer for transient storage. /// /// This is only safe to use from the raster threads. Other threads should @@ -740,6 +755,32 @@ class ContentContext { std::shared_ptr context_; std::shared_ptr lazy_glyph_atlas_; + struct RuntimeEffectPipelineKey { + std::string unique_entrypoint_name; + ContentContextOptions options; + + struct Hash { + std::size_t operator()(const RuntimeEffectPipelineKey& key) const { + return fml::HashCombine(key.unique_entrypoint_name, + ContentContextOptions::Hash{}(key.options)); + } + }; + + struct Equal { + constexpr bool operator()(const RuntimeEffectPipelineKey& lhs, + const RuntimeEffectPipelineKey& rhs) const { + return lhs.unique_entrypoint_name == rhs.unique_entrypoint_name && + ContentContextOptions::Equal{}(lhs.options, rhs.options); + } + }; + }; + + mutable std::unordered_map>, + RuntimeEffectPipelineKey::Hash, + RuntimeEffectPipelineKey::Equal> + runtime_effect_pipelines_; + template class Variants { public: diff --git a/impeller/entity/contents/runtime_effect_contents.cc b/impeller/entity/contents/runtime_effect_contents.cc index 7cf6e931986af..0b1e049aeef21 100644 --- a/impeller/entity/contents/runtime_effect_contents.cc +++ b/impeller/entity/contents/runtime_effect_contents.cc @@ -47,19 +47,8 @@ static ShaderType GetShaderType(RuntimeUniformType type) { return ShaderType::kSampledImage; case kFloat: return ShaderType::kFloat; - case kBoolean: - case kSignedByte: - case kUnsignedByte: - case kSignedShort: - case kUnsignedShort: - case kSignedInt: - case kUnsignedInt: - case kSignedInt64: - case kUnsignedInt64: - case kHalfFloat: - case kDouble: - VALIDATION_LOG << "Unsupported uniform type."; - return ShaderType::kVoid; + case kStruct: + return ShaderType::kStruct; } } @@ -79,15 +68,6 @@ static std::shared_ptr MakeShaderMetadata( bool RuntimeEffectContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - // TODO(jonahwilliams): FragmentProgram API is not fully wired up on Android. - // Disable until this is complete so that integration tests and benchmarks can - // run m3 applications. - if (renderer.GetContext()->GetBackendType() == - Context::BackendType::kVulkan) { - FML_DLOG(WARNING) - << "Fragment programs not supported on Vulkan. Content dropped."; - return true; - } auto context = renderer.GetContext(); auto library = context->GetShaderLibrary(); @@ -147,7 +127,8 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, GetGeometry()->GetPositionBuffer(renderer, entity, pass); //-------------------------------------------------------------------------- - /// Get or create runtime stage pipeline. + /// Set up the command. Defer setting up the pipeline until the descriptor set + /// layouts are known from the uniforms. /// const auto& caps = context->GetCapabilities(); @@ -155,41 +136,9 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, const auto stencil_attachment_format = caps->GetDefaultStencilFormat(); using VS = RuntimeEffectVertexShader; - PipelineDescriptor desc; - desc.SetLabel("Runtime Stage"); - desc.AddStageEntrypoint( - library->GetFunction(VS::kEntrypointName, ShaderStage::kVertex)); - desc.AddStageEntrypoint(library->GetFunction(runtime_stage_->GetEntrypoint(), - ShaderStage::kFragment)); - auto vertex_descriptor = std::make_shared(); - vertex_descriptor->SetStageInputs(VS::kAllShaderStageInputs, - VS::kInterleavedBufferLayout); - desc.SetVertexDescriptor(std::move(vertex_descriptor)); - desc.SetColorAttachmentDescriptor( - 0u, {.format = color_attachment_format, .blending_enabled = true}); - - StencilAttachmentDescriptor stencil0; - stencil0.stencil_compare = CompareFunction::kEqual; - desc.SetStencilAttachmentDescriptors(stencil0); - desc.SetStencilPixelFormat(stencil_attachment_format); - - auto options = OptionsFromPassAndEntity(pass, entity); - if (geometry_result.prevent_overdraw) { - options.stencil_compare = CompareFunction::kEqual; - options.stencil_operation = StencilOperation::kIncrementClamp; - } - options.primitive_type = geometry_result.type; - options.ApplyToPipelineDescriptor(desc); - - auto pipeline = context->GetPipelineLibrary()->GetPipeline(desc).Get(); - if (!pipeline) { - VALIDATION_LOG << "Failed to get or create runtime effect pipeline."; - return false; - } Command cmd; DEBUG_COMMAND_INFO(cmd, "RuntimeEffectContents"); - cmd.pipeline = pipeline; cmd.stencil_reference = entity.GetClipDepth(); cmd.BindVertices(std::move(geometry_result.vertex_buffer)); @@ -209,6 +158,9 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, size_t minimum_sampler_index = 100000000; size_t buffer_index = 0; size_t buffer_offset = 0; + + std::vector descriptor_set_layouts; + for (const auto& uniform : runtime_stage_->GetUniforms()) { std::shared_ptr metadata = MakeShaderMetadata(uniform); @@ -227,6 +179,10 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, break; } case kFloat: { + FML_DCHECK(renderer.GetContext()->GetBackendType() != + Context::BackendType::kVulkan) + << "Uniform " << uniform.name + << " had unexpected type kFloat for Vulkan backend."; size_t alignment = std::max(uniform.bit_width / 8, DefaultUniformAlignment()); auto buffer_view = renderer.GetTransientsBuffer().Emplace( @@ -242,20 +198,41 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, buffer_offset += uniform.GetSize(); break; } - case kBoolean: - case kSignedByte: - case kUnsignedByte: - case kSignedShort: - case kUnsignedShort: - case kSignedInt: - case kUnsignedInt: - case kSignedInt64: - case kUnsignedInt64: - case kHalfFloat: - case kDouble: - VALIDATION_LOG << "Unsupported uniform type for " << uniform.name - << "."; - return true; + case kStruct: { + FML_DCHECK(renderer.GetContext()->GetBackendType() == + Context::BackendType::kVulkan); + descriptor_set_layouts.emplace_back(DescriptorSetLayout{ + static_cast(uniform.location), + DescriptorType::kUniformBuffer, + ShaderStage::kFragment, + }); + ShaderUniformSlot uniform_slot; + uniform_slot.name = uniform.name.c_str(); + uniform_slot.binding = uniform.location; + + std::vector uniform_buffer; + uniform_buffer.reserve(uniform.struct_layout.size()); + size_t uniform_byte_index = 0u; + for (const auto& byte_type : uniform.struct_layout) { + if (byte_type == 0) { + uniform_buffer.push_back(0.f); + } else if (byte_type == 1) { + uniform_buffer.push_back(reinterpret_cast( + uniform_data_->data())[uniform_byte_index++]); + } else { + FML_UNREACHABLE(); + } + } + + size_t alignment = std::max(sizeof(float) * uniform_buffer.size(), + DefaultUniformAlignment()); + + auto buffer_view = renderer.GetTransientsBuffer().Emplace( + reinterpret_cast(uniform_buffer.data()), alignment, + alignment); + cmd.BindResource(ShaderStage::kFragment, uniform_slot, ShaderMetadata{}, + buffer_view); + } } } @@ -273,6 +250,19 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, SampledImageSlot image_slot; image_slot.name = uniform.name.c_str(); + + uint32_t sampler_binding_location = 0u; + if (!descriptor_set_layouts.empty()) { + sampler_binding_location = descriptor_set_layouts.back().binding + 1; + } + + descriptor_set_layouts.emplace_back(DescriptorSetLayout{ + sampler_binding_location, + DescriptorType::kSampledImage, + ShaderStage::kFragment, + }); + + image_slot.binding = sampler_binding_location; image_slot.texture_index = uniform.location - minimum_sampler_index; cmd.BindResource(ShaderStage::kFragment, image_slot, *metadata, input.texture, sampler); @@ -285,6 +275,51 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, } } + /// Now that the descriptor set layouts are known, get the pipeline. + + auto options = OptionsFromPassAndEntity(pass, entity); + if (geometry_result.prevent_overdraw) { + options.stencil_compare = CompareFunction::kEqual; + options.stencil_operation = StencilOperation::kIncrementClamp; + } + options.primitive_type = geometry_result.type; + + auto create_callback = + [&]() -> std::shared_ptr> { + PipelineDescriptor desc; + desc.SetLabel("Runtime Stage"); + desc.AddStageEntrypoint( + library->GetFunction(VS::kEntrypointName, ShaderStage::kVertex)); + desc.AddStageEntrypoint(library->GetFunction( + runtime_stage_->GetEntrypoint(), ShaderStage::kFragment)); + auto vertex_descriptor = std::make_shared(); + vertex_descriptor->SetStageInputs(VS::kAllShaderStageInputs, + VS::kInterleavedBufferLayout); + vertex_descriptor->RegisterDescriptorSetLayouts(VS::kDescriptorSetLayouts); + vertex_descriptor->RegisterDescriptorSetLayouts( + descriptor_set_layouts.data(), descriptor_set_layouts.size()); + desc.SetVertexDescriptor(std::move(vertex_descriptor)); + desc.SetColorAttachmentDescriptor( + 0u, {.format = color_attachment_format, .blending_enabled = true}); + + StencilAttachmentDescriptor stencil0; + stencil0.stencil_compare = CompareFunction::kEqual; + desc.SetStencilAttachmentDescriptors(stencil0); + desc.SetStencilPixelFormat(stencil_attachment_format); + + options.ApplyToPipelineDescriptor(desc); + auto pipeline = context->GetPipelineLibrary()->GetPipeline(desc).Get(); + if (!pipeline) { + VALIDATION_LOG << "Failed to get or create runtime effect pipeline."; + return nullptr; + } + + return pipeline; + }; + + cmd.pipeline = renderer.GetCachedRuntimeEffectPipeline( + runtime_stage_->GetEntrypoint(), options, create_callback); + pass.AddCommand(std::move(cmd)); if (geometry_result.prevent_overdraw) { diff --git a/impeller/entity/contents/test/contents_test_helpers.h b/impeller/entity/contents/test/contents_test_helpers.h index afcef40c84075..3e0a6d5aae845 100644 --- a/impeller/entity/contents/test/contents_test_helpers.h +++ b/impeller/entity/contents/test/contents_test_helpers.h @@ -32,7 +32,8 @@ typename T::FragInfo* GetFragInfo(const Command& command) { auto resource = std::find_if(command.fragment_bindings.buffers.begin(), command.fragment_bindings.buffers.end(), [](const BufferAndUniformSlot& data) { - return data.slot.ext_res_0 == 0u; + return data.slot.ext_res_0 == 0u || + data.slot.binding == 64; }); if (resource == command.fragment_bindings.buffers.end()) { return nullptr; diff --git a/impeller/entity/contents/vertices_contents_unittests.cc b/impeller/entity/contents/vertices_contents_unittests.cc index 5d928e12f5159..173b13ea4bcfb 100644 --- a/impeller/entity/contents/vertices_contents_unittests.cc +++ b/impeller/entity/contents/vertices_contents_unittests.cc @@ -71,6 +71,7 @@ TEST_P(EntityTest, RendersDstPerColorWithAlpha) { const auto& cmd = render_pass->GetCommands()[0]; auto* frag_uniforms = GetFragInfo(cmd); + ASSERT_TRUE(frag_uniforms); ASSERT_EQ(frag_uniforms->alpha, 0.5); } diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 452cf7ba38ebb..52ee276ba103f 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -2132,10 +2132,6 @@ TEST_P(EntityTest, YUVToRGBFilter) { } TEST_P(EntityTest, RuntimeEffect) { - if (!BackendSupportsFragmentProgram()) { - GTEST_SKIP_("This backend doesn't support runtime effects."); - } - auto runtime_stages = OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); auto runtime_stage = @@ -2144,6 +2140,7 @@ TEST_P(EntityTest, RuntimeEffect) { ASSERT_TRUE(runtime_stage->IsDirty()); bool first_frame = true; + Pipeline* first_pipeline = nullptr; auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { if (first_frame) { first_frame = false; @@ -2170,7 +2167,13 @@ TEST_P(EntityTest, RuntimeEffect) { Entity entity; entity.SetContents(contents); - return contents->Render(context, entity, pass); + bool result = contents->Render(context, entity, pass); + if (!first_pipeline) { + first_pipeline = pass.GetCommands().back().pipeline.get(); + } else { + EXPECT_EQ(pass.GetCommands().back().pipeline.get(), first_pipeline); + } + return result; }; ASSERT_TRUE(OpenPlaygroundHere(callback)); } diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn index 3b00710a7772b..9165f06542a2a 100644 --- a/impeller/fixtures/BUILD.gn +++ b/impeller/fixtures/BUILD.gn @@ -64,6 +64,7 @@ impellerc("runtime_stages") { shader_target_flags = [ "--runtime-stage-metal", "--runtime-stage-gles", + "--runtime-stage-vulkan", ] iplr = true diff --git a/impeller/golden_tests/golden_playground_test.h b/impeller/golden_tests/golden_playground_test.h index e4fdde82fb5a6..f411615b51d8e 100644 --- a/impeller/golden_tests/golden_playground_test.h +++ b/impeller/golden_tests/golden_playground_test.h @@ -36,12 +36,6 @@ class GoldenPlaygroundTest PlaygroundBackend GetBackend() const; - // TODO(dnfield): Delete this once - // https://github.com/flutter/flutter/issues/122823 is fixed. - bool BackendSupportsFragmentProgram() const { - return GetBackend() != PlaygroundBackend::kVulkan; - } - void SetTypographerContext( std::shared_ptr typographer_context); diff --git a/impeller/golden_tests/golden_playground_test_mac.cc b/impeller/golden_tests/golden_playground_test_mac.cc index 80f5a8630dc08..81cec2c283a10 100644 --- a/impeller/golden_tests/golden_playground_test_mac.cc +++ b/impeller/golden_tests/golden_playground_test_mac.cc @@ -53,6 +53,8 @@ static const std::vector kSkipTests = { // Gold. "impeller_Play_AiksTest_TextRotated_Metal", "impeller_Play_AiksTest_TextRotated_Vulkan", + // Runtime stage based tests get confused with a Metal context. + "impeller_Play_AiksTest_CanRenderClippedRuntimeEffects_Vulkan", }; namespace { diff --git a/impeller/playground/playground_test.h b/impeller/playground/playground_test.h index 65ac08e67ecfd..e2051a296776b 100644 --- a/impeller/playground/playground_test.h +++ b/impeller/playground/playground_test.h @@ -42,12 +42,6 @@ class PlaygroundTest : public Playground, // |Playground| std::string GetWindowTitle() const override; - // TODO(dnfield): Delete this once - // https://github.com/flutter/flutter/issues/122823 is fixed. - bool BackendSupportsFragmentProgram() const { - return GetBackend() != PlaygroundBackend::kVulkan; - } - private: // |Playground| bool ShouldKeepRendering() const; diff --git a/impeller/renderer/backend/gles/shader_library_gles.cc b/impeller/renderer/backend/gles/shader_library_gles.cc index 5acdcdfd4dc93..48e52e4edc30e 100644 --- a/impeller/renderer/backend/gles/shader_library_gles.cc +++ b/impeller/renderer/backend/gles/shader_library_gles.cc @@ -141,7 +141,7 @@ void ShaderLibraryGLES::UnregisterFunction(std::string name, const auto key = ShaderKey{name, stage}; auto found = functions_.find(key); - if (found != functions_.end()) { + if (found == functions_.end()) { VALIDATION_LOG << "Library function named " << name << " was not found, so it couldn't be unregistered."; return; diff --git a/impeller/renderer/backend/vulkan/binding_helpers_vk.cc b/impeller/renderer/backend/vulkan/binding_helpers_vk.cc index 6a96d0119c059..19fe5350d602b 100644 --- a/impeller/renderer/backend/vulkan/binding_helpers_vk.cc +++ b/impeller/renderer/backend/vulkan/binding_helpers_vk.cc @@ -173,7 +173,6 @@ fml::StatusOr> AllocateAndBindDescriptorSets( auto desc_set = command.pipeline->GetDescriptor() .GetVertexDescriptor() ->GetDescriptorSetLayouts(); - if (!BindBuffers(command.vertex_bindings, allocator, encoder, descriptor_sets[desc_index], desc_set, buffers, writes) || !BindBuffers(command.fragment_bindings, allocator, encoder, diff --git a/impeller/renderer/backend/vulkan/context_vk_unittests.cc b/impeller/renderer/backend/vulkan/context_vk_unittests.cc index 25128aa347966..53dd23b755488 100644 --- a/impeller/renderer/backend/vulkan/context_vk_unittests.cc +++ b/impeller/renderer/backend/vulkan/context_vk_unittests.cc @@ -85,7 +85,7 @@ TEST(ContextVKTest, DeleteShaderFunctionAfterContext) { pipeline_desc.SetVertexDescriptor(std::make_shared()); std::vector data = {0x03, 0x02, 0x23, 0x07}; context->GetShaderLibrary()->RegisterFunction( - "foobar", ShaderStage::kFragment, + "foobar_fragment_main", ShaderStage::kFragment, std::make_shared(data), [](bool) {}); shader_function = context->GetShaderLibrary()->GetFunction( "foobar_fragment_main", ShaderStage::kFragment); diff --git a/impeller/renderer/backend/vulkan/shader_library_vk.cc b/impeller/renderer/backend/vulkan/shader_library_vk.cc index bd85f2559b72f..cf722ae9259fb 100644 --- a/impeller/renderer/backend/vulkan/shader_library_vk.cc +++ b/impeller/renderer/backend/vulkan/shader_library_vk.cc @@ -59,7 +59,9 @@ ShaderLibraryVK::ShaderLibraryVK( const auto& name, // const auto& code // ) -> bool { - if (!RegisterFunction(name, ToShaderStage(type), code)) { + const auto stage = ToShaderStage(type); + if (!RegisterFunction(VKShaderNameToShaderKeyName(name, stage), stage, + code)) { success = false; return false; } @@ -157,17 +159,15 @@ bool ShaderLibraryVK::RegisterFunction( return false; } - const auto key_name = VKShaderNameToShaderKeyName(name, stage); - vk::UniqueShaderModule shader_module = std::move(module.value); ContextVK::SetDebugName(device_holder->GetDevice(), *shader_module, "Shader " + name); WriterLock lock(functions_mutex_); - functions_[ShaderKey{key_name, stage}] = std::shared_ptr( + functions_[ShaderKey{name, stage}] = std::shared_ptr( new ShaderFunctionVK(device_holder_, library_id_, // - key_name, // + name, // stage, // std::move(shader_module) // )); @@ -182,7 +182,7 @@ void ShaderLibraryVK::UnregisterFunction(std::string name, ShaderStage stage) { const auto key = ShaderKey{name, stage}; auto found = functions_.find(key); - if (found != functions_.end()) { + if (found == functions_.end()) { VALIDATION_LOG << "Library function named " << name << " was not found, so it couldn't be unregistered."; return; diff --git a/impeller/runtime_stage/runtime_stage.cc b/impeller/runtime_stage/runtime_stage.cc index 357f54313441c..4b5c6924fa386 100644 --- a/impeller/runtime_stage/runtime_stage.cc +++ b/impeller/runtime_stage/runtime_stage.cc @@ -9,6 +9,7 @@ #include "fml/mapping.h" #include "impeller/base/validation.h" +#include "impeller/core/runtime_types.h" #include "impeller/runtime_stage/runtime_stage_flatbuffers.h" #include "runtime_stage_types_flatbuffers.h" @@ -16,32 +17,12 @@ namespace impeller { static RuntimeUniformType ToType(fb::UniformDataType type) { switch (type) { - case fb::UniformDataType::kBoolean: - return RuntimeUniformType::kBoolean; - case fb::UniformDataType::kSignedByte: - return RuntimeUniformType::kSignedByte; - case fb::UniformDataType::kUnsignedByte: - return RuntimeUniformType::kUnsignedByte; - case fb::UniformDataType::kSignedShort: - return RuntimeUniformType::kSignedShort; - case fb::UniformDataType::kUnsignedShort: - return RuntimeUniformType::kUnsignedShort; - case fb::UniformDataType::kSignedInt: - return RuntimeUniformType::kSignedInt; - case fb::UniformDataType::kUnsignedInt: - return RuntimeUniformType::kUnsignedInt; - case fb::UniformDataType::kSignedInt64: - return RuntimeUniformType::kSignedInt64; - case fb::UniformDataType::kUnsignedInt64: - return RuntimeUniformType::kUnsignedInt64; - case fb::UniformDataType::kHalfFloat: - return RuntimeUniformType::kHalfFloat; case fb::UniformDataType::kFloat: return RuntimeUniformType::kFloat; - case fb::UniformDataType::kDouble: - return RuntimeUniformType::kDouble; case fb::UniformDataType::kSampledImage: return RuntimeUniformType::kSampledImage; + case fb::UniformDataType::kStruct: + return RuntimeUniformType::kStruct; } FML_UNREACHABLE(); } @@ -58,6 +39,13 @@ static RuntimeShaderStage ToShaderStage(fb::Stage stage) { FML_UNREACHABLE(); } +/// The generated name from GLSLang/shaderc for the UBO containing non-opaque +/// uniforms specified in the user-written runtime effect shader. +/// +/// Vulkan does not allow non-opaque uniforms outside of a UBO. +const char* RuntimeStage::kVulkanUBOName = + "_RESERVED_IDENTIFIER_FIXUP_gl_DefaultUniformBlock"; + std::unique_ptr RuntimeStage::RuntimeStageIfPresent( const fb::RuntimeStage* runtime_stage, const std::shared_ptr& payload) { @@ -110,6 +98,12 @@ RuntimeStage::RuntimeStage(const fb::RuntimeStage* runtime_stage, static_cast(i->rows()), static_cast(i->columns())}; desc.bit_width = i->bit_width(); desc.array_elements = i->array_elements(); + if (i->struct_layout()) { + for (const auto& byte_type : *i->struct_layout()) { + desc.struct_layout.push_back(static_cast(byte_type)); + } + } + desc.struct_float_count = i->struct_float_count(); uniforms_.emplace_back(std::move(desc)); } } diff --git a/impeller/runtime_stage/runtime_stage.h b/impeller/runtime_stage/runtime_stage.h index 1c433906004d6..a86344a89d4c5 100644 --- a/impeller/runtime_stage/runtime_stage.h +++ b/impeller/runtime_stage/runtime_stage.h @@ -18,6 +18,8 @@ namespace impeller { class RuntimeStage { public: + static const char* kVulkanUBOName; + using Map = std::map>; static Map DecodeRuntimeStages(const std::shared_ptr& payload); diff --git a/impeller/runtime_stage/runtime_stage_types.fbs b/impeller/runtime_stage/runtime_stage_types.fbs index 21e6f802717a6..b6f88f1fa73b7 100644 --- a/impeller/runtime_stage/runtime_stage_types.fbs +++ b/impeller/runtime_stage/runtime_stage_types.fbs @@ -11,20 +11,20 @@ enum Stage:byte { } // The subset of impeller::ShaderType that may be used for uniform bindings. +// kStruct is only supported for Impeller Vulkan. +// kFloat encompases multiple float-based types e.g. vec2. enum UniformDataType:uint32 { - kBoolean, - kSignedByte, - kUnsignedByte, - kSignedShort, - kUnsignedShort, - kSignedInt, - kUnsignedInt, - kSignedInt64, - kUnsignedInt64, - kHalfFloat, kFloat, - kDouble, kSampledImage, + kStruct, +} + +// A struct is made up solely of 4 byte floats and 4-byte paddings between +// them. +// This enum describes whether a particular byte is a float or padding. +enum StructByteType:uint8 { + kPadding = 0, + kFloat = 1, } table UniformDescription { @@ -35,6 +35,8 @@ table UniformDescription { rows: uint64; columns: uint64; array_elements: uint64; + struct_layout: [StructByteType]; + struct_float_count: uint64; } // The subset of impeller::ShaderType that may be used for vertex attributes. @@ -72,6 +74,8 @@ table RuntimeStage { inputs: [StageInput]; uniforms: [UniformDescription]; shader: [ubyte]; + struct_byte_length: uint64; + float_count: uint64; } table RuntimeStages { diff --git a/impeller/runtime_stage/runtime_stage_unittests.cc b/impeller/runtime_stage/runtime_stage_unittests.cc index 1d88146010889..72eed52cced07 100644 --- a/impeller/runtime_stage/runtime_stage_unittests.cc +++ b/impeller/runtime_stage/runtime_stage_unittests.cc @@ -5,12 +5,13 @@ #include #include "flutter/fml/make_copyable.h" -#include "flutter/impeller/fixtures/simple.vert.h" #include "flutter/testing/testing.h" +#include "gmock/gmock.h" #include "impeller/base/allocation.h" #include "impeller/base/validation.h" #include "impeller/core/runtime_types.h" #include "impeller/core/shader_types.h" +#include "impeller/entity/runtime_effect.vert.h" #include "impeller/playground/playground.h" #include "impeller/renderer/pipeline_descriptor.h" #include "impeller/renderer/pipeline_library.h" @@ -25,10 +26,6 @@ using RuntimeStageTest = RuntimeStagePlayground; INSTANTIATE_PLAYGROUND_SUITE(RuntimeStageTest); TEST_P(RuntimeStageTest, CanReadValidBlob) { - if (!BackendSupportsFragmentProgram()) { - GTEST_SKIP_("This backend doesn't support runtime effects."); - } - const std::shared_ptr fixture = flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr"); ASSERT_TRUE(fixture); @@ -40,10 +37,6 @@ TEST_P(RuntimeStageTest, CanReadValidBlob) { } TEST_P(RuntimeStageTest, CanRejectInvalidBlob) { - if (!BackendSupportsFragmentProgram()) { - GTEST_SKIP_("This backend doesn't support runtime effects."); - } - ScopedValidationDisable disable_validation; const std::shared_ptr fixture = flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr"); @@ -59,10 +52,6 @@ TEST_P(RuntimeStageTest, CanRejectInvalidBlob) { } TEST_P(RuntimeStageTest, CanReadUniforms) { - if (!BackendSupportsFragmentProgram()) { - GTEST_SKIP_("This backend doesn't support runtime effects."); - } - const std::shared_ptr fixture = flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr"); ASSERT_TRUE(fixture); @@ -71,143 +60,171 @@ TEST_P(RuntimeStageTest, CanReadUniforms) { auto stage = stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; ASSERT_TRUE(stage->IsValid()); - ASSERT_EQ(stage->GetUniforms().size(), 17u); - { - auto uni = stage->GetUniform("u_color"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 4u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 0u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_alpha"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 1u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 1u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_sparkle_color"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 4u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 2u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_sparkle_alpha"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 1u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 3u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_blur"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 1u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 4u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_radius_scale"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 1u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 6u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_max_radius"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 1u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 7u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_resolution_scale"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 2u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 8u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_noise_scale"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 2u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 9u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_noise_phase"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 1u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 10u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } + switch (GetBackend()) { + case PlaygroundBackend::kMetal: + case PlaygroundBackend::kOpenGLES: { + ASSERT_EQ(stage->GetUniforms().size(), 17u); + { + auto uni = stage->GetUniform("u_color"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 4u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 0u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_alpha"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 1u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 1u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_sparkle_color"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 4u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 2u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_sparkle_alpha"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 1u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 3u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_blur"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 1u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 4u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_radius_scale"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 1u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 6u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_max_radius"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 1u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 7u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_resolution_scale"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 2u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 8u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_noise_scale"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 2u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 9u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_noise_phase"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 1u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 10u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } - { - auto uni = stage->GetUniform("u_circle1"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 2u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 11u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_circle2"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 2u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 12u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_circle3"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 2u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 13u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_rotation1"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 2u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 14u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_rotation2"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 2u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 15u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); - } - { - auto uni = stage->GetUniform("u_rotation3"); - ASSERT_NE(uni, nullptr); - ASSERT_EQ(uni->dimensions.rows, 2u); - ASSERT_EQ(uni->dimensions.cols, 1u); - ASSERT_EQ(uni->location, 16u); - ASSERT_EQ(uni->type, RuntimeUniformType::kFloat); + { + auto uni = stage->GetUniform("u_circle1"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 2u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 11u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_circle2"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 2u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 12u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_circle3"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 2u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 13u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_rotation1"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 2u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 14u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_rotation2"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 2u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 15u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + { + auto uni = stage->GetUniform("u_rotation3"); + ASSERT_NE(uni, nullptr); + EXPECT_EQ(uni->dimensions.rows, 2u); + EXPECT_EQ(uni->dimensions.cols, 1u); + EXPECT_EQ(uni->location, 16u); + EXPECT_EQ(uni->type, RuntimeUniformType::kFloat); + } + break; + } + case PlaygroundBackend::kVulkan: { + EXPECT_EQ(stage->GetUniforms().size(), 1u); + auto uni = stage->GetUniform(RuntimeStage::kVulkanUBOName); + ASSERT_TRUE(uni); + EXPECT_EQ(uni->type, RuntimeUniformType::kStruct); + EXPECT_EQ(uni->struct_float_count, 32u); + + // There are 36 4 byte chunks in the UBO: 32 for the 32 floats, and 4 for + // padding. Initialize a vector as if they'll all be floats, then manually + // set the few padding bytes. If the shader changes, the padding locations + // will change as well. For example, if `u_alpha` was moved to the end, + // three bytes of padding could potentially be dropped - or if some of the + // scalar floats were changed to vec2 or vec4s, or if any vec3s are + // introduced. + // This means 36 * 4 = 144 bytes total. + + EXPECT_EQ(uni->GetSize(), 144u); + std::vector layout(uni->GetSize() / sizeof(float), 1); + layout[5] = 0; + layout[6] = 0; + layout[7] = 0; + layout[23] = 0; + + EXPECT_THAT(uni->struct_layout, ::testing::ElementsAreArray(layout)); + break; + } } } TEST_P(RuntimeStageTest, CanRegisterStage) { - if (!BackendSupportsFragmentProgram()) { - GTEST_SKIP_("This backend doesn't support runtime effects."); - } - const std::shared_ptr fixture = flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr"); ASSERT_TRUE(fixture); @@ -243,9 +260,6 @@ TEST_P(RuntimeStageTest, CanRegisterStage) { } TEST_P(RuntimeStageTest, CanCreatePipelineFromRuntimeStage) { - if (!BackendSupportsFragmentProgram()) { - GTEST_SKIP_("This backend doesn't support runtime effects."); - } auto stages = OpenAssetAsRuntimeStage("ink_sparkle.frag.iplr"); auto stage = stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; @@ -253,7 +267,7 @@ TEST_P(RuntimeStageTest, CanCreatePipelineFromRuntimeStage) { ASSERT_NE(stage, nullptr); ASSERT_TRUE(RegisterStage(*stage)); auto library = GetContext()->GetShaderLibrary(); - using VS = SimpleVertexShader; + using VS = RuntimeEffectVertexShader; PipelineDescriptor desc; desc.SetLabel("Runtime Stage InkSparkle"); desc.AddStageEntrypoint( @@ -263,6 +277,7 @@ TEST_P(RuntimeStageTest, CanCreatePipelineFromRuntimeStage) { auto vertex_descriptor = std::make_shared(); vertex_descriptor->SetStageInputs(VS::kAllShaderStageInputs, VS::kInterleavedBufferLayout); + vertex_descriptor->RegisterDescriptorSetLayouts(VS::kDescriptorSetLayouts); desc.SetVertexDescriptor(std::move(vertex_descriptor)); ColorAttachmentDescriptor color0; @@ -288,9 +303,7 @@ TEST_P(RuntimeStageTest, ContainsExpectedShaderTypes) { EXPECT_TRUE(stages[RuntimeStageBackend::kOpenGLES]); EXPECT_TRUE(stages[RuntimeStageBackend::kMetal]); - // TODO(dnfield): Flip this when - // https://github.com/flutter/flutter/issues/122823 is fixed. - EXPECT_FALSE(stages[RuntimeStageBackend::kVulkan]); + EXPECT_TRUE(stages[RuntimeStageBackend::kVulkan]); } } // namespace testing diff --git a/impeller/tools/impeller.gni b/impeller/tools/impeller.gni index b682fddc00f47..6541c3b12173b 100644 --- a/impeller/tools/impeller.gni +++ b/impeller/tools/impeller.gni @@ -289,6 +289,7 @@ template("_impellerc") { # --vulkan # --runtime-stage-metal # --runtime-stage-gles +# --runtime-stage-vulkan # Not required for --shader_bundle mode. # Required: sl_file_extension The file extension to use for output files. # Not required for --shader_bundle mode. diff --git a/lib/ui/fixtures/shaders/BUILD.gn b/lib/ui/fixtures/shaders/BUILD.gn index 1ea359df4cf64..ff3b6eb3cc896 100644 --- a/lib/ui/fixtures/shaders/BUILD.gn +++ b/lib/ui/fixtures/shaders/BUILD.gn @@ -41,6 +41,7 @@ if (enable_unittests) { shader_target_flags = [ "--runtime-stage-metal", "--runtime-stage-gles", + "--runtime-stage-vulkan", ] intermediates_subdir = "iplr-remap" sl_file_extension = "iplr" diff --git a/lib/ui/painting/fragment_program.cc b/lib/ui/painting/fragment_program.cc index b74f33ab2e7ab..5e1f91cac6ebd 100644 --- a/lib/ui/painting/fragment_program.cc +++ b/lib/ui/painting/fragment_program.cc @@ -113,16 +113,17 @@ std::string FragmentProgram::initFromAsset(const std::string& asset_name) { Dart_Handle result = Dart_SetField(ths, tonic::ToDart("_samplerCount"), Dart_NewInteger(sampled_image_count)); if (Dart_IsError(result)) { - Dart_PropagateError(result); + return "Failed to set sampler count for fragment program."; } size_t rounded_uniform_bytes = (other_uniforms_bytes + sizeof(float) - 1) & ~(sizeof(float) - 1); size_t float_count = rounded_uniform_bytes / sizeof(float); + result = Dart_SetField(ths, tonic::ToDart("_uniformFloatCount"), Dart_NewInteger(float_count)); if (Dart_IsError(result)) { - Dart_PropagateError(result); + return "Failed to set uniform float count for fragment program."; } return "";