diff --git a/engine/src/flutter/impeller/aiks/aiks_unittests.cc b/engine/src/flutter/impeller/aiks/aiks_unittests.cc index b7a7e0d8bc..61ddc200d3 100644 --- a/engine/src/flutter/impeller/aiks/aiks_unittests.cc +++ b/engine/src/flutter/impeller/aiks/aiks_unittests.cc @@ -3033,13 +3033,15 @@ TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) { // Regression test for https://github.com/flutter/flutter/issues/126701 . TEST_P(AiksTest, CanRenderClippedRuntimeEffects) { - if (GetParam() != PlaygroundBackend::kMetal) { + if (!BackendSupportsFragmentProgram()) { GTEST_SKIP_("This backend doesn't support runtime effects."); } auto runtime_stages = OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); - auto runtime_stage = runtime_stages[RuntimeStageBackend::kMetal]; + + auto runtime_stage = + runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; ASSERT_TRUE(runtime_stage); ASSERT_TRUE(runtime_stage->IsDirty()); @@ -3068,7 +3070,7 @@ TEST_P(AiksTest, CanRenderClippedRuntimeEffects) { } TEST_P(AiksTest, DrawPaintTransformsBounds) { - if (GetParam() != PlaygroundBackend::kMetal) { + if (!BackendSupportsFragmentProgram()) { GTEST_SKIP_("This backend doesn't support runtime effects."); } diff --git a/engine/src/flutter/impeller/core/shader_types.h b/engine/src/flutter/impeller/core/shader_types.h index 8b267dbe48..25c405b1f0 100644 --- a/engine/src/flutter/impeller/core/shader_types.h +++ b/engine/src/flutter/impeller/core/shader_types.h @@ -70,6 +70,7 @@ struct ShaderStructMemberMetadata { }; struct ShaderMetadata { + // This must match the uniform name in the shader program. std::string name; std::vector members; }; diff --git a/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc b/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc index 82ad3508ff..a2a5244b6b 100644 --- a/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc @@ -11,6 +11,7 @@ #include "flutter/fml/make_copyable.h" #include "impeller/base/validation.h" #include "impeller/core/formats.h" +#include "impeller/core/runtime_types.h" #include "impeller/core/shader_types.h" #include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/contents/content_context.h" @@ -41,16 +42,53 @@ bool RuntimeEffectContents::CanInheritOpacity(const Entity& entity) const { return false; } +static ShaderType GetShaderType(RuntimeUniformType type) { + switch (type) { + case kSampledImage: + 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; + } +} + +static std::shared_ptr MakeShaderMetadata( + const RuntimeUniformDescription& uniform) { + auto metadata = std::make_shared(); + metadata->name = uniform.name; + metadata->members.emplace_back(ShaderStructMemberMetadata{ + .type = GetShaderType(uniform.type), + .size = uniform.GetSize(), + .byte_length = uniform.bit_width / 8, + }); + + return metadata; +} + 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. -#ifdef FML_OS_ANDROID - return true; -#else - + // 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(); @@ -172,10 +210,7 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, size_t buffer_index = 0; size_t buffer_offset = 0; for (const auto& uniform : runtime_stage_->GetUniforms()) { - // TODO(113715): Populate this metadata once GLES is able to handle - // non-struct uniform names. - std::shared_ptr metadata = - std::make_shared(); + std::shared_ptr metadata = MakeShaderMetadata(uniform); switch (uniform.type) { case kSampledImage: { @@ -226,9 +261,7 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, size_t sampler_index = 0; for (const auto& uniform : runtime_stage_->GetUniforms()) { - // TODO(113715): Populate this metadata once GLES is able to handle - // non-struct uniform names. - ShaderMetadata metadata; + std::shared_ptr metadata = MakeShaderMetadata(uniform); switch (uniform.type) { case kSampledImage: { @@ -241,7 +274,7 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, SampledImageSlot image_slot; image_slot.name = uniform.name.c_str(); image_slot.texture_index = uniform.location - minimum_sampler_index; - cmd.BindResource(ShaderStage::kFragment, image_slot, metadata, + cmd.BindResource(ShaderStage::kFragment, image_slot, *metadata, input.texture, sampler); sampler_index++; @@ -260,7 +293,6 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, return restore.Render(renderer, entity, pass); } return true; -#endif // FML_OS_ANDROID } } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/entity_unittests.cc b/engine/src/flutter/impeller/entity/entity_unittests.cc index 4cb0c416d5..91d6b6a273 100644 --- a/engine/src/flutter/impeller/entity/entity_unittests.cc +++ b/engine/src/flutter/impeller/entity/entity_unittests.cc @@ -2132,13 +2132,14 @@ TEST_P(EntityTest, YUVToRGBFilter) { } TEST_P(EntityTest, RuntimeEffect) { - if (GetParam() != PlaygroundBackend::kMetal) { + if (!BackendSupportsFragmentProgram()) { GTEST_SKIP_("This backend doesn't support runtime effects."); } auto runtime_stages = OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); - auto runtime_stage = runtime_stages[RuntimeStageBackend::kMetal]; + auto runtime_stage = + runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; ASSERT_TRUE(runtime_stage); ASSERT_TRUE(runtime_stage->IsDirty()); diff --git a/engine/src/flutter/impeller/fixtures/BUILD.gn b/engine/src/flutter/impeller/fixtures/BUILD.gn index 1867b65862..bce07ac400 100644 --- a/engine/src/flutter/impeller/fixtures/BUILD.gn +++ b/engine/src/flutter/impeller/fixtures/BUILD.gn @@ -60,7 +60,14 @@ impellerc("runtime_stages") { "gradient.frag", ] sl_file_extension = "iplr" - shader_target_flag = "--runtime-stage-metal" + + # TODO(dnfield): Make impellerc able to bundle multiple non-SkSL shaders. + # https://github.com/flutter/flutter/issues/140817 + if (is_ios || is_mac) { + shader_target_flag = "--runtime-stage-metal" + } else { + shader_target_flag = "--runtime-stage-gles" + } iplr = true } diff --git a/engine/src/flutter/impeller/golden_tests/golden_playground_test.h b/engine/src/flutter/impeller/golden_tests/golden_playground_test.h index f411615b51..e4fdde82fb 100644 --- a/engine/src/flutter/impeller/golden_tests/golden_playground_test.h +++ b/engine/src/flutter/impeller/golden_tests/golden_playground_test.h @@ -36,6 +36,12 @@ 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/engine/src/flutter/impeller/playground/playground.h b/engine/src/flutter/impeller/playground/playground.h index 57e6b882eb..6f8f37fca8 100644 --- a/engine/src/flutter/impeller/playground/playground.h +++ b/engine/src/flutter/impeller/playground/playground.h @@ -12,6 +12,7 @@ #include "flutter/fml/macros.h" #include "flutter/fml/status.h" #include "flutter/fml/time/time_delta.h" +#include "impeller/core/runtime_types.h" #include "impeller/core/texture.h" #include "impeller/geometry/point.h" #include "impeller/image/compressed_image.h" @@ -30,6 +31,19 @@ enum class PlaygroundBackend { kVulkan, }; +constexpr inline RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend( + PlaygroundBackend backend) { + switch (backend) { + case PlaygroundBackend::kMetal: + return RuntimeStageBackend::kMetal; + case PlaygroundBackend::kOpenGLES: + return RuntimeStageBackend::kOpenGLES; + case PlaygroundBackend::kVulkan: + return RuntimeStageBackend::kVulkan; + } + FML_UNREACHABLE(); +} + std::string PlaygroundBackendToString(PlaygroundBackend backend); class Playground { diff --git a/engine/src/flutter/impeller/playground/playground_test.h b/engine/src/flutter/impeller/playground/playground_test.h index e2051a2967..65ac08e67e 100644 --- a/engine/src/flutter/impeller/playground/playground_test.h +++ b/engine/src/flutter/impeller/playground/playground_test.h @@ -42,6 +42,12 @@ 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/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc index 696ab62b42..899cbbb5b7 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc @@ -65,8 +65,10 @@ static std::string CreateUniformMemberKey(const std::string& struct_name, std::string result; result.reserve(struct_name.length() + member.length() + (is_array ? 4 : 1)); result += struct_name; - result += '.'; - result += member; + if (!member.empty()) { + result += '.'; + result += member; + } if (is_array) { result += "[0]"; } @@ -312,6 +314,9 @@ bool BufferBindingsGLES::BindUniformBuffer(const ProcTableGLES& gl, ); continue; } + VALIDATION_LOG << "Size " << member.size + << " could not be mapped ShaderType::kFloat for key: " + << member.name; case ShaderType::kBoolean: case ShaderType::kSignedByte: case ShaderType::kUnsignedByte: diff --git a/engine/src/flutter/impeller/runtime_stage/runtime_stage_unittests.cc b/engine/src/flutter/impeller/runtime_stage/runtime_stage_unittests.cc index f5b95aa7b2..082b4a44a5 100644 --- a/engine/src/flutter/impeller/runtime_stage/runtime_stage_unittests.cc +++ b/engine/src/flutter/impeller/runtime_stage/runtime_stage_unittests.cc @@ -23,18 +23,26 @@ namespace testing { using RuntimeStageTest = RuntimeStagePlayground; INSTANTIATE_PLAYGROUND_SUITE(RuntimeStageTest); -TEST(RuntimeStageTest, CanReadValidBlob) { +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); ASSERT_GT(fixture->GetSize(), 0u); auto stages = RuntimeStage::DecodeRuntimeStages(fixture); - auto stage = stages[RuntimeStageBackend::kMetal]; + auto stage = stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; ASSERT_TRUE(stage->IsValid()); ASSERT_EQ(stage->GetShaderStage(), RuntimeShaderStage::kFragment); } -TEST(RuntimeStageTest, CanRejectInvalidBlob) { +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"); @@ -46,16 +54,20 @@ TEST(RuntimeStageTest, CanRejectInvalidBlob) { ::memset(junk_allocation->GetBuffer(), 127, junk_allocation->GetLength()); auto stages = RuntimeStage::DecodeRuntimeStages( CreateMappingFromAllocation(junk_allocation)); - ASSERT_FALSE(stages[RuntimeStageBackend::kMetal]); + ASSERT_FALSE(stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]); } -TEST(RuntimeStageTest, CanReadUniforms) { +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); ASSERT_GT(fixture->GetSize(), 0u); auto stages = RuntimeStage::DecodeRuntimeStages(fixture); - auto stage = stages[RuntimeStageBackend::kMetal]; + auto stage = stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; ASSERT_TRUE(stage->IsValid()); ASSERT_EQ(stage->GetUniforms().size(), 17u); @@ -191,15 +203,16 @@ TEST(RuntimeStageTest, CanReadUniforms) { } TEST_P(RuntimeStageTest, CanRegisterStage) { - if (GetParam() != PlaygroundBackend::kMetal) { - GTEST_SKIP_("Skipped: https://github.com/flutter/flutter/issues/105538"); + 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); ASSERT_GT(fixture->GetSize(), 0u); auto stages = RuntimeStage::DecodeRuntimeStages(fixture); - auto stage = stages[RuntimeStageBackend::kMetal]; + auto stage = stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; ASSERT_TRUE(stage->IsValid()); std::promise registration; auto future = registration.get_future(); @@ -229,11 +242,11 @@ TEST_P(RuntimeStageTest, CanRegisterStage) { } TEST_P(RuntimeStageTest, CanCreatePipelineFromRuntimeStage) { - if (GetParam() != PlaygroundBackend::kMetal) { - GTEST_SKIP_("Skipped: https://github.com/flutter/flutter/issues/105538"); + if (!BackendSupportsFragmentProgram()) { + GTEST_SKIP_("This backend doesn't support runtime effects."); } auto stages = OpenAssetAsRuntimeStage("ink_sparkle.frag.iplr"); - auto stage = stages[RuntimeStageBackend::kMetal]; + auto stage = stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; ASSERT_TRUE(stage); ASSERT_NE(stage, nullptr);