diff --git a/DEPS b/DEPS index 8d0230be48124..ca94512df3514 100644 --- a/DEPS +++ b/DEPS @@ -43,7 +43,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS. # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': 'cc7a770ed1d993ef3e6aec99f58dd506acb03278', + 'dart_revision': 'dc20e31ba74c445dadd77fcc608311c69116cce9', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -214,7 +214,7 @@ deps = { Var('dart_git') + '/dart_style.git@f79a9828ad07e50d6e8352ac154cc16eb4d78d5c', 'src/third_party/dart/third_party/pkg/dartdoc': - Var('dart_git') + '/dartdoc.git@8878245e0766bc943955e3e3883832251e48d95d', + Var('dart_git') + '/dartdoc.git@4902beaa24bd153252e3a83d62dd9bc8887dac01', 'src/third_party/dart/third_party/pkg/ffi': Var('dart_git') + '/ffi.git@fb5f2667826c0900e551d19101052f84e35f41bf', diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index da4791076b283..c225182016aa6 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1107,6 +1107,7 @@ FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/blending.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/branching.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/color.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/constants.glsl +FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/gaussian.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/texture.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/transform.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/types.glsl diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 7dae6d987f7b3..73d96fe474d68 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 073bf3be567deaef937fac2e6837d81b +Signature: db07a0f32b0efc60ab748191d2080f97 UNUSED LICENSES: diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index ab4b76ab66e92..2ced180b3e7d2 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -101,8 +101,6 @@ TEST_P(AiksTest, CanRenderTiledTexture) { if (first_frame) { first_frame = false; GenerateMipmap(context, texture, "table_mountain_nx"); - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); } const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; @@ -303,14 +301,7 @@ TEST_P(AiksTest, CanSaveLayerStandalone) { } TEST_P(AiksTest, CanRenderLinearGradient) { - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); - } - const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; const Entity::TileMode tile_modes[] = { Entity::TileMode::kClamp, Entity::TileMode::kRepeat, @@ -361,14 +352,7 @@ TEST_P(AiksTest, CanRenderLinearGradient) { } TEST_P(AiksTest, CanRenderLinearGradientManyColors) { - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); - } - const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; const Entity::TileMode tile_modes[] = { Entity::TileMode::kClamp, Entity::TileMode::kRepeat, @@ -433,14 +417,7 @@ TEST_P(AiksTest, CanRenderLinearGradientManyColors) { } TEST_P(AiksTest, CanRenderLinearGradientWayManyColors) { - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); - } - const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; const Entity::TileMode tile_modes[] = { Entity::TileMode::kClamp, Entity::TileMode::kRepeat, @@ -495,14 +472,7 @@ TEST_P(AiksTest, CanRenderLinearGradientWayManyColors) { } TEST_P(AiksTest, CanRenderLinearGradientManyColorsUnevenStops) { - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); - } - const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; const Entity::TileMode tile_modes[] = { Entity::TileMode::kClamp, Entity::TileMode::kRepeat, @@ -559,14 +529,7 @@ TEST_P(AiksTest, CanRenderLinearGradientManyColorsUnevenStops) { } TEST_P(AiksTest, CanRenderRadialGradient) { - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); - } - const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; const Entity::TileMode tile_modes[] = { Entity::TileMode::kClamp, Entity::TileMode::kRepeat, @@ -614,14 +577,7 @@ TEST_P(AiksTest, CanRenderRadialGradient) { } TEST_P(AiksTest, CanRenderRadialGradientManyColors) { - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); - } - const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; const Entity::TileMode tile_modes[] = { Entity::TileMode::kClamp, Entity::TileMode::kRepeat, @@ -683,14 +639,7 @@ TEST_P(AiksTest, CanRenderRadialGradientManyColors) { } TEST_P(AiksTest, CanRenderSweepGradient) { - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); - } - const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; const Entity::TileMode tile_modes[] = { Entity::TileMode::kClamp, Entity::TileMode::kRepeat, @@ -737,14 +686,7 @@ TEST_P(AiksTest, CanRenderSweepGradient) { } TEST_P(AiksTest, CanRenderSweepGradientManyColors) { - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); - } - const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; const Entity::TileMode tile_modes[] = { Entity::TileMode::kClamp, Entity::TileMode::kRepeat, @@ -835,14 +777,7 @@ TEST_P(AiksTest, CanRenderDifferentShapesWithSameColorSource) { } TEST_P(AiksTest, CanPictureConvertToImage) { - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); - } - static int size[2] = {1000, 1000}; ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::SliderInt2("Size", size, 0, 1000); @@ -1296,13 +1231,7 @@ TEST_P(AiksTest, ColorWheel) { std::shared_ptr color_wheel_image; Matrix color_wheel_transform; - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowPos({25, 25}); - } - // UI state. static int current_blend_index = 3; static float dst_alpha = 1; @@ -1430,19 +1359,12 @@ TEST_P(AiksTest, TransformMultipliesCorrectly) { TEST_P(AiksTest, SolidStrokesRenderCorrectly) { // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2 - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); - } - static Color color = Color::Black().WithAlpha(0.5); static float scale = 3; static bool add_circle_clip = true; - ImGui::Begin("Controls"); + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::ColorEdit4("Color", reinterpret_cast(&color)); ImGui::SliderFloat("Scale", &scale, 0, 6); ImGui::Checkbox("Circle clip", &add_circle_clip); @@ -1500,14 +1422,7 @@ TEST_P(AiksTest, SolidStrokesRenderCorrectly) { TEST_P(AiksTest, GradientStrokesRenderCorrectly) { // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2 - bool first_frame = true; auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({480, 100}); - ImGui::SetNextWindowPos({100, 550}); - } - static float scale = 3; static bool add_circle_clip = true; const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; @@ -1517,7 +1432,7 @@ TEST_P(AiksTest, GradientStrokesRenderCorrectly) { static int selected_tile_mode = 0; static float alpha = 1; - ImGui::Begin("Controls"); + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::SliderFloat("Scale", &scale, 0, 6); ImGui::Checkbox("Circle clip", &add_circle_clip); ImGui::SliderFloat("Alpha", &alpha, 0, 1); diff --git a/impeller/compiler/shader_lib/impeller/constants.glsl b/impeller/compiler/shader_lib/impeller/constants.glsl index 0ead4981f6411..fe9367a926eae 100644 --- a/impeller/compiler/shader_lib/impeller/constants.glsl +++ b/impeller/compiler/shader_lib/impeller/constants.glsl @@ -13,4 +13,7 @@ const float k1Over2Pi = 0.1591549430918; // sqrt(2 * pi) const float kSqrtTwoPi = 2.50662827463; +// sqrt(2) / 2 == 1 / sqrt(2) +const float kHalfSqrtTwo = 0.70710678118; + #endif diff --git a/impeller/compiler/shader_lib/impeller/gaussian.glsl b/impeller/compiler/shader_lib/impeller/gaussian.glsl new file mode 100644 index 0000000000000..19dce44745ced --- /dev/null +++ b/impeller/compiler/shader_lib/impeller/gaussian.glsl @@ -0,0 +1,35 @@ +// 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. + +#ifndef GAUSSIAN_GLSL_ +#define GAUSSIAN_GLSL_ + +#include + +float IPGaussian(float x, float sigma) { + float variance = sigma * sigma; + return exp(-0.5 * x * x / variance) / (kSqrtTwoPi * sigma); +} + +/// Abramowitz and Stegun erf approximation. +float IPErf(float x) { + float a = abs(x); + // 0.278393*x + 0.230389*x^2 + 0.078108*x^4 + 1 + float b = (0.278393 + (0.230389 + 0.078108 * a * a) * a) * a + 1.0; + return sign(x) * (1 - 1 / (b * b * b * b)); +} + +vec2 IPVec2Erf(vec2 x) { + return vec2(IPErf(x.x), IPErf(x.y)); +} + +/// Indefinite integral of the Gaussian function (with constant range 0->1). +float IPGaussianIntegral(float x, float sigma) { + // ( 1 + erf( x * (sqrt(2) / (2 * sigma) ) ) / 2 + // Because this sigmoid is always > 1, we remap it (n * 1.07 - 0.07) + // so that it always fades to zero before it reaches the blur radius. + return 0.535 * IPErf(x * (kHalfSqrtTwo / sigma)) + 0.465; +} + +#endif diff --git a/impeller/display_list/display_list_dispatcher.cc b/impeller/display_list/display_list_dispatcher.cc index 853db4b5288eb..3ecea0a0d83d0 100644 --- a/impeller/display_list/display_list_dispatcher.cc +++ b/impeller/display_list/display_list_dispatcher.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include "display_list/display_list_blend_mode.h" #include "display_list/display_list_color_filter.h" @@ -436,11 +437,28 @@ void DisplayListDispatcher::setColorSource( auto runtime_stage = runtime_effect_color_source->runtime_effect()->runtime_stage(); auto uniform_data = runtime_effect_color_source->uniform_data(); + auto samplers = runtime_effect_color_source->samplers(); - paint_.color_source = [runtime_stage, uniform_data]() { + std::vector texture_inputs; + + for (auto& sampler : samplers) { + auto* image = sampler->asImage(); + if (!sampler->asImage()) { + UNIMPLEMENTED; + return; + } + FML_DCHECK(image->image()->impeller_texture()); + texture_inputs.push_back({ + .sampler_descriptor = ToSamplerDescriptor(image->sampling()), + .texture = image->image()->impeller_texture(), + }); + } + + paint_.color_source = [runtime_stage, uniform_data, texture_inputs]() { auto contents = std::make_shared(); contents->SetRuntimeStage(runtime_stage); contents->SetUniformData(uniform_data); + contents->SetTextureInputs(texture_inputs); return contents; }; return; diff --git a/impeller/display_list/display_list_unittests.cc b/impeller/display_list/display_list_unittests.cc index b5762bbbd45e5..fb637ed509019 100644 --- a/impeller/display_list/display_list_unittests.cc +++ b/impeller/display_list/display_list_unittests.cc @@ -97,14 +97,7 @@ TEST_P(DisplayListTest, CanDrawCapsAndJoins) { } TEST_P(DisplayListTest, CanDrawArc) { - bool first_frame = true; auto callback = [&]() { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({400, 100}); - ImGui::SetNextWindowPos({300, 550}); - } - static float start_angle = 45; static float sweep_angle = 270; static bool use_center = true; @@ -283,17 +276,10 @@ TEST_P(DisplayListTest, CanDrawWithColorFilterImageFilter) { TEST_P(DisplayListTest, CanDrawWithImageBlurFilter) { auto texture = CreateTextureForFixture("embarcadero.jpg"); - bool first_frame = true; auto callback = [&]() { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({400, 100}); - ImGui::SetNextWindowPos({300, 550}); - } - static float sigma[] = {10, 10}; - ImGui::Begin("Controls"); + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::SliderFloat2("Sigma", sigma, 0, 100); ImGui::End(); @@ -361,14 +347,8 @@ TEST_P(DisplayListTest, CanClampTheResultingColorOfColorMatrixFilter) { TEST_P(DisplayListTest, SaveLayerWithColorMatrixFiltersAndAlphaDrawCorrectly) { auto texture = CreateTextureForFixture("boston.jpg"); - bool first_frame = true; enum class Type { kUseAsImageFilter, kUseAsColorFilter, kDisableFilter }; auto callback = [&]() { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowPos({10, 10}); - } - static float alpha = 0.5; static int selected_type = 0; const char* names[] = {"Use as image filter", "Use as color filter", @@ -426,14 +406,8 @@ TEST_P(DisplayListTest, SaveLayerWithColorMatrixFiltersAndAlphaDrawCorrectly) { TEST_P(DisplayListTest, SaveLayerWithBlendFiltersAndAlphaDrawCorrectly) { auto texture = CreateTextureForFixture("boston.jpg"); - bool first_frame = true; enum class Type { kUseAsImageFilter, kUseAsColorFilter, kDisableFilter }; auto callback = [&]() { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowPos({10, 10}); - } - static float alpha = 0.5; static int selected_type = 0; const char* names[] = {"Use as image filter", "Use as color filter", @@ -479,13 +453,7 @@ TEST_P(DisplayListTest, SaveLayerWithBlendFiltersAndAlphaDrawCorrectly) { TEST_P(DisplayListTest, CanDrawBackdropFilter) { auto texture = CreateTextureForFixture("embarcadero.jpg"); - bool first_frame = true; auto callback = [&]() { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowPos({10, 10}); - } - static float sigma[] = {10, 10}; static float ctm_scale = 1; static bool use_bounds = true; @@ -775,13 +743,7 @@ TEST_P(DisplayListTest, CanDrawZeroWidthLine) { TEST_P(DisplayListTest, CanDrawWithMatrixFilter) { auto boston = CreateTextureForFixture("boston.jpg"); - bool first_frame = true; auto callback = [&]() { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowPos({10, 10}); - } - static int selected_matrix_type = 0; const char* matrix_type_names[] = {"Matrix", "Local Matrix"}; diff --git a/impeller/entity/contents/runtime_effect_contents.cc b/impeller/entity/contents/runtime_effect_contents.cc index e454b9fbc4eb5..0bce8521769bf 100644 --- a/impeller/entity/contents/runtime_effect_contents.cc +++ b/impeller/entity/contents/runtime_effect_contents.cc @@ -16,6 +16,7 @@ #include "impeller/renderer/formats.h" #include "impeller/renderer/pipeline_library.h" #include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" #include "impeller/renderer/shader_function.h" #include "impeller/renderer/shader_types.h" @@ -31,6 +32,11 @@ void RuntimeEffectContents::SetUniformData( uniform_data_ = std::move(uniform_data); } +void RuntimeEffectContents::SetTextureInputs( + std::vector texture_inputs) { + texture_inputs_ = std::move(texture_inputs); +} + bool RuntimeEffectContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { @@ -136,21 +142,59 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, /// size_t buffer_index = 0; + size_t sampler_index = 0; for (auto uniform : runtime_stage_->GetUniforms()) { // TODO(113715): Populate this metadata once GLES is able to handle // non-struct uniform names. ShaderMetadata metadata; - size_t alignment = - std::max(uniform.bit_width / 8, DefaultUniformAlignment()); - auto buffer_view = pass.GetTransientsBuffer().Emplace( - uniform_data_->data() + uniform.location * sizeof(float), - uniform.GetSize(), alignment); - - ShaderUniformSlot slot; - slot.name = uniform.name.c_str(); - slot.ext_res_0 = buffer_index; - cmd.BindResource(ShaderStage::kFragment, slot, metadata, buffer_view); + switch (uniform.type) { + case kSampledImage: { + FML_DCHECK(sampler_index < texture_inputs_.size()); + auto& input = texture_inputs_[sampler_index]; + + auto sampler = + context->GetSamplerLibrary()->GetSampler(input.sampler_descriptor); + + SampledImageSlot image_slot; + image_slot.name = uniform.name.c_str(); + image_slot.texture_index = sampler_index; + image_slot.sampler_index = sampler_index; + cmd.BindResource(ShaderStage::kFragment, image_slot, metadata, + input.texture, sampler); + + sampler_index++; + break; + } + case kFloat: { + size_t alignment = + std::max(uniform.bit_width / 8, DefaultUniformAlignment()); + auto buffer_view = pass.GetTransientsBuffer().Emplace( + uniform_data_->data() + uniform.location * sizeof(float), + uniform.GetSize(), alignment); + + ShaderUniformSlot uniform_slot; + uniform_slot.name = uniform.name.c_str(); + uniform_slot.ext_res_0 = buffer_index; + cmd.BindResource(ShaderStage::kFragment, uniform_slot, metadata, + buffer_view); + 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; + } buffer_index++; } diff --git a/impeller/entity/contents/runtime_effect_contents.h b/impeller/entity/contents/runtime_effect_contents.h index 3abcc16a1a312..bda791bc064f5 100644 --- a/impeller/entity/contents/runtime_effect_contents.h +++ b/impeller/entity/contents/runtime_effect_contents.h @@ -4,18 +4,27 @@ #include #include +#include #include "impeller/entity/contents/color_source_contents.h" +#include "impeller/renderer/sampler_descriptor.h" #include "impeller/runtime_stage/runtime_stage.h" namespace impeller { class RuntimeEffectContents final : public ColorSourceContents { public: + struct TextureInput { + SamplerDescriptor sampler_descriptor; + std::shared_ptr texture; + }; + void SetRuntimeStage(std::shared_ptr runtime_stage); void SetUniformData(std::shared_ptr> uniform_data); + void SetTextureInputs(std::vector texture_inputs); + // |Contents| bool Render(const ContentContext& renderer, const Entity& entity, @@ -24,6 +33,7 @@ class RuntimeEffectContents final : public ColorSourceContents { private: std::shared_ptr runtime_stage_; std::shared_ptr> uniform_data_; + std::vector texture_inputs_; }; } // namespace impeller diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 7d57ad8b5e444..b915a5bb12d55 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -264,22 +264,14 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { const Point padding(300, 250); const Point margin(140, 180); - bool first_frame = true; auto callback = [&](ContentContext& context, RenderPass& pass) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({300, 100}); - ImGui::SetNextWindowPos( - {0 * padding.x + margin.x, 1.7f * padding.y + margin.y}); - } - // Slightly above sqrt(2) by default, so that right angles are just below // the limit and acute angles are over the limit (causing them to get // beveled). static Scalar miter_limit = 1.41421357; static Scalar width = 30; - ImGui::Begin("Controls"); + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); { ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30); ImGui::SliderFloat("Stroke width", &width, 0, 100); @@ -741,14 +733,7 @@ TEST_P(EntityTest, BlendingModeOptions) { }; } - bool first_frame = true; auto callback = [&](ContentContext& context, RenderPass& pass) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({350, 200}); - ImGui::SetNextWindowPos({200, 450}); - } - auto world_matrix = Matrix::MakeScale(GetContentScale()); auto draw_rect = [&context, &pass, &world_matrix]( Rect rect, Color color, BlendMode blend_mode) -> bool { @@ -792,7 +777,7 @@ TEST_P(EntityTest, BlendingModeOptions) { return pass.AddCommand(std::move(cmd)); }; - ImGui::Begin("Controls"); + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); static Color color1(1, 0, 0, 0.5), color2(0, 1, 0, 0.5); ImGui::ColorEdit4("Color 1", reinterpret_cast(&color1)); ImGui::ColorEdit4("Color 2", reinterpret_cast(&color2)); @@ -879,13 +864,7 @@ TEST_P(EntityTest, GaussianBlurFilter) { auto boston = CreateTextureForFixture("boston.jpg"); ASSERT_TRUE(boston); - bool first_frame = true; auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowPos({10, 10}); - } - const char* input_type_names[] = {"Texture", "Solid Color"}; const char* blur_type_names[] = {"Image blur", "Mask blur"}; const char* pass_variation_names[] = {"Two pass", "Directional"}; @@ -1035,13 +1014,7 @@ TEST_P(EntityTest, MorphologyFilter) { auto boston = CreateTextureForFixture("boston.jpg"); ASSERT_TRUE(boston); - bool first_frame = true; auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowPos({10, 10}); - } - const char* morphology_type_names[] = {"Dilate", "Erode"}; const FilterContents::MorphType morphology_types[] = { FilterContents::MorphType::kDilate, FilterContents::MorphType::kErode}; @@ -1563,13 +1536,7 @@ TEST_P(EntityTest, ClipContentsShouldRenderIsCorrect) { } TEST_P(EntityTest, RRectShadowTest) { - bool first_frame = true; auto callback = [&](ContentContext& context, RenderPass& pass) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowPos({10, 10}); - } - static Color color = Color::Red(); static float corner_radius = 100; static float blur_radius = 100; @@ -1651,14 +1618,7 @@ TEST_P(EntityTest, ColorMatrixFilterEditable) { auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg"); ASSERT_TRUE(bay_bridge); - bool first_frame = true; auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - // If this is the first frame, set the ImGui's initial size and postion. - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowPos({10, 10}); - } - // UI state. static FilterContents::ColorMatrix color_matrix = { 1, 0, 0, 0, 0, // @@ -2086,7 +2046,7 @@ TEST_P(EntityTest, RuntimeEffect) { contents->SetGeometry(Geometry::MakeCover()); auto runtime_stage = - LoadFixtureRuntimeStage("runtime_stage_example.frag.iplr"); + OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); contents->SetRuntimeStage(runtime_stage); struct FragUniforms { diff --git a/impeller/entity/shaders/border_mask_blur.frag b/impeller/entity/shaders/border_mask_blur.frag index d35280f8dfecf..e9b56fa3cfe0a 100644 --- a/impeller/entity/shaders/border_mask_blur.frag +++ b/impeller/entity/shaders/border_mask_blur.frag @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include #include // Constant time mask blur for image borders. @@ -27,30 +28,12 @@ in float v_outer_blur_factor; out vec4 frag_color; -// Abramowitz and Stegun erf approximation. -float erf(float x) { - float a = abs(x); - // 0.278393*x + 0.230389*x^2 + 0.078108*x^4 + 1 - float b = (0.278393 + (0.230389 + 0.078108 * a * a) * a) * a + 1.0; - return sign(x) * (1 - 1 / (b * b * b * b)); -} - -const float kHalfSqrtTwo = 0.70710678118; - -// Indefinite integral of the Gaussian function (with constant range 0->1). -float GaussianIntegral(float x, float sigma) { - // ( 1 + erf( x * (sqrt(2) / (2 * sigma) ) ) / 2 - // Because this sigmoid is always > 1, we remap it (n * 1.07 - 0.07) - // so that it always fades to zero before it reaches the blur radius. - return 0.535 * erf(x * (kHalfSqrtTwo / sigma)) + 0.465; -} - float BoxBlurMask(vec2 uv) { // LTRB - return GaussianIntegral(uv.x, v_sigma_uv.x) * // - GaussianIntegral(uv.y, v_sigma_uv.y) * // - GaussianIntegral(1 - uv.x, v_sigma_uv.x) * // - GaussianIntegral(1 - uv.y, v_sigma_uv.y); + return IPGaussianIntegral(uv.x, v_sigma_uv.x) * // + IPGaussianIntegral(uv.y, v_sigma_uv.y) * // + IPGaussianIntegral(1 - uv.x, v_sigma_uv.x) * // + IPGaussianIntegral(1 - uv.y, v_sigma_uv.y); } void main() { diff --git a/impeller/entity/shaders/gaussian_blur.frag b/impeller/entity/shaders/gaussian_blur.frag index ec099a6965bc4..06bd0f389ff47 100644 --- a/impeller/entity/shaders/gaussian_blur.frag +++ b/impeller/entity/shaders/gaussian_blur.frag @@ -14,6 +14,7 @@ // level of log2(min_radius). #include +#include #include uniform sampler2D texture_sampler; @@ -46,18 +47,13 @@ in vec2 v_src_texture_coords; out vec4 frag_color; -float Gaussian(float x) { - float variance = frag_info.blur_sigma * frag_info.blur_sigma; - return exp(-0.5 * x * x / variance) / (kSqrtTwoPi * frag_info.blur_sigma); -} - void main() { vec4 total_color = vec4(0); float gaussian_integral = 0; vec2 blur_uv_offset = frag_info.blur_direction / frag_info.texture_size; for (float i = -frag_info.blur_radius; i <= frag_info.blur_radius; i++) { - float gaussian = Gaussian(i); + float gaussian = IPGaussian(i, frag_info.blur_sigma); gaussian_integral += gaussian; total_color += gaussian * diff --git a/impeller/geometry/geometry_unittests.cc b/impeller/geometry/geometry_unittests.cc index f88df791d6bff..42da1d65b324c 100644 --- a/impeller/geometry/geometry_unittests.cc +++ b/impeller/geometry/geometry_unittests.cc @@ -1616,32 +1616,94 @@ TEST(GeometryTest, VerticesConstructorAndGetters) { } TEST(GeometryTest, MatrixPrinting) { - std::stringstream stream; - - Matrix m; - - stream << m; - - ASSERT_EQ(stream.str(), R"(( + { + std::stringstream stream; + Matrix m; + stream << m; + ASSERT_EQ(stream.str(), R"(( 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, ))"); + } - stream.str(""); - stream.clear(); - - m = Matrix::MakeTranslation(Vector3(10, 20, 30)); - - stream << m; + { + std::stringstream stream; + Matrix m = Matrix::MakeTranslation(Vector3(10, 20, 30)); + stream << m; - ASSERT_EQ(stream.str(), R"(( + ASSERT_EQ(stream.str(), R"(( 1.000000, 0.000000, 0.000000, 10.000000, 0.000000, 1.000000, 0.000000, 20.000000, 0.000000, 0.000000, 1.000000, 30.000000, 0.000000, 0.000000, 0.000000, 1.000000, ))"); + } +} + +TEST(GeometryTest, PointPrinting) { + { + std::stringstream stream; + Point m; + stream << m; + ASSERT_EQ(stream.str(), "(0, 0)"); + } + + { + std::stringstream stream; + Point m(13, 37); + stream << m; + ASSERT_EQ(stream.str(), "(13, 37)"); + } +} + +TEST(GeometryTest, Vector3Printing) { + { + std::stringstream stream; + Vector3 m; + stream << m; + ASSERT_EQ(stream.str(), "(0, 0, 0)"); + } + + { + std::stringstream stream; + Vector3 m(1, 2, 3); + stream << m; + ASSERT_EQ(stream.str(), "(1, 2, 3)"); + } +} + +TEST(GeometryTest, Vector4Printing) { + { + std::stringstream stream; + Vector4 m; + stream << m; + ASSERT_EQ(stream.str(), "(0, 0, 0, 1)"); + } + + { + std::stringstream stream; + Vector4 m(1, 2, 3, 4); + stream << m; + ASSERT_EQ(stream.str(), "(1, 2, 3, 4)"); + } +} + +TEST(GeometryTest, ColorPrinting) { + { + std::stringstream stream; + Color m; + stream << m; + ASSERT_EQ(stream.str(), "(0, 0, 0, 0)"); + } + + { + std::stringstream stream; + Color m(1, 2, 3, 4); + stream << m; + ASSERT_EQ(stream.str(), "(1, 2, 3, 4)"); + } } TEST(GeometryTest, Gradient) { diff --git a/impeller/geometry/vector.h b/impeller/geometry/vector.h index 96821e3f1984c..e87c22f61e4a9 100644 --- a/impeller/geometry/vector.h +++ b/impeller/geometry/vector.h @@ -247,3 +247,17 @@ static_assert(sizeof(Vector3) == 3 * sizeof(Scalar)); static_assert(sizeof(Vector4) == 4 * sizeof(Scalar)); } // namespace impeller + +namespace std { + +inline std::ostream& operator<<(std::ostream& out, const impeller::Vector3& p) { + out << "(" << p.x << ", " << p.y << ", " << p.z << ")"; + return out; +} + +inline std::ostream& operator<<(std::ostream& out, const impeller::Vector4& p) { + out << "(" << p.x << ", " << p.y << ", " << p.z << ", " << p.w << ")"; + return out; +} + +} // namespace std diff --git a/impeller/playground/playground.cc b/impeller/playground/playground.cc index 312713c264385..92eae2852fee4 100644 --- a/impeller/playground/playground.cc +++ b/impeller/playground/playground.cc @@ -213,6 +213,8 @@ bool Playground::OpenPlaygroundHere( fml::ScopedCleanupClosure shutdown_imgui_impeller( []() { ImGui_ImplImpeller_Shutdown(); }); + ImGui::SetNextWindowPos({10, 10}); + ::glfwSetWindowSize(window, GetWindowSize().width, GetWindowSize().height); ::glfwSetWindowPos(window, 200, 100); ::glfwShowWindow(window); @@ -437,22 +439,6 @@ std::shared_ptr Playground::CreateTextureCubeForFixture( return texture; } -std::shared_ptr Playground::LoadFixtureRuntimeStage( - const char* fixture_name) const { - if (fixture_name == nullptr) { - return nullptr; - } - - auto runtime_stage = - std::make_shared(OpenAssetAsMapping(fixture_name)); - - if (!runtime_stage->IsValid()) { - VALIDATION_LOG << "Could not load valid runtime stage."; - return nullptr; - } - return runtime_stage; -} - void Playground::SetWindowSize(ISize size) { window_size_ = size; } diff --git a/impeller/playground/playground.h b/impeller/playground/playground.h index 255c87d7b4784..82e1edf2946eb 100644 --- a/impeller/playground/playground.h +++ b/impeller/playground/playground.h @@ -64,9 +64,6 @@ class Playground { std::shared_ptr CreateTextureCubeForFixture( std::array fixture_names) const; - std::shared_ptr LoadFixtureRuntimeStage( - const char* fixture_name) const; - static bool SupportsBackend(PlaygroundBackend backend); virtual std::unique_ptr OpenAssetAsMapping( diff --git a/impeller/playground/playground_test.cc b/impeller/playground/playground_test.cc index 1627cf9a711f5..024c4d52174df 100644 --- a/impeller/playground/playground_test.cc +++ b/impeller/playground/playground_test.cc @@ -34,6 +34,19 @@ std::unique_ptr PlaygroundTest::OpenAssetAsMapping( return flutter::testing::OpenFixtureAsMapping(asset_name); } +std::shared_ptr PlaygroundTest::OpenAssetAsRuntimeStage( + const char* asset_name) const { + auto fixture = flutter::testing::OpenFixtureAsMapping(asset_name); + if (!fixture || fixture->GetSize() == 0) { + return nullptr; + } + auto stage = std::make_unique(std::move(fixture)); + if (!stage->IsValid()) { + return nullptr; + } + return stage; +} + static std::string FormatWindowTitle(const std::string& test_name) { std::stringstream stream; stream << "Impeller Playground for '" << test_name diff --git a/impeller/playground/playground_test.h b/impeller/playground/playground_test.h index 2e10faebffcd6..351cbc0c9545f 100644 --- a/impeller/playground/playground_test.h +++ b/impeller/playground/playground_test.h @@ -27,6 +27,9 @@ class PlaygroundTest : public Playground, std::unique_ptr OpenAssetAsMapping( std::string asset_name) const override; + std::shared_ptr OpenAssetAsRuntimeStage( + const char* asset_name) const; + // |Playground| std::string GetWindowTitle() const override; diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc index bdb63c62e4602..32b4f5623bfde 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -165,18 +165,11 @@ TEST_P(RendererTest, CanRenderPerspectiveCube) { ASSERT_TRUE(sampler); Vector3 euler_angles; - bool first_frame = true; SinglePassCallback callback = [&](RenderPass& pass) { - if (first_frame) { - first_frame = false; - ImGui::SetNextWindowSize({400, 80}); - ImGui::SetNextWindowPos({20, 20}); - } - static Degrees fov_y(60); static Scalar distance = 10; - ImGui::Begin("Controls"); + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::SliderFloat("Field of view", &fov_y.degrees, 0, 180); ImGui::SliderFloat("Camera distance", &distance, 0, 30); ImGui::End(); @@ -598,10 +591,6 @@ TEST_P(RendererTest, CanGenerateMipmaps) { bool first_frame = true; Renderer::RenderCallback callback = [&](RenderTarget& render_target) { - if (first_frame) { - ImGui::SetNextWindowPos({10, 10}); - } - const char* mip_filter_names[] = {"None", "Nearest", "Linear"}; const MipFilter mip_filters[] = {MipFilter::kNone, MipFilter::kNearest, MipFilter::kLinear}; diff --git a/impeller/runtime_stage/runtime_stage_playground.cc b/impeller/runtime_stage/runtime_stage_playground.cc index 096030843315a..de0d3cf66f99e 100644 --- a/impeller/runtime_stage/runtime_stage_playground.cc +++ b/impeller/runtime_stage/runtime_stage_playground.cc @@ -17,19 +17,6 @@ RuntimeStagePlayground::RuntimeStagePlayground() = default; RuntimeStagePlayground::~RuntimeStagePlayground() = default; -std::unique_ptr RuntimeStagePlayground::CreateStageFromFixture( - const std::string& fixture_name) const { - auto fixture = flutter::testing::OpenFixtureAsMapping(fixture_name); - if (!fixture || fixture->GetSize() == 0) { - return nullptr; - } - auto stage = std::make_unique(std::move(fixture)); - if (!stage->IsValid()) { - return nullptr; - } - return stage; -} - bool RuntimeStagePlayground::RegisterStage(const RuntimeStage& stage) { std::promise registration; auto future = registration.get_future(); diff --git a/impeller/runtime_stage/runtime_stage_playground.h b/impeller/runtime_stage/runtime_stage_playground.h index 0b4168b2220c0..3a1821006c7f0 100644 --- a/impeller/runtime_stage/runtime_stage_playground.h +++ b/impeller/runtime_stage/runtime_stage_playground.h @@ -16,9 +16,6 @@ class RuntimeStagePlayground : public PlaygroundTest { ~RuntimeStagePlayground(); - std::unique_ptr CreateStageFromFixture( - const std::string& fixture_name) const; - bool RegisterStage(const RuntimeStage& stage); private: diff --git a/impeller/runtime_stage/runtime_stage_unittests.cc b/impeller/runtime_stage/runtime_stage_unittests.cc index fd454cfe2c14a..99efecefa88c4 100644 --- a/impeller/runtime_stage/runtime_stage_unittests.cc +++ b/impeller/runtime_stage/runtime_stage_unittests.cc @@ -216,7 +216,7 @@ TEST_P(RuntimeStageTest, CanCreatePipelineFromRuntimeStage) { if (GetParam() != PlaygroundBackend::kMetal) { GTEST_SKIP_("Skipped: https://github.com/flutter/flutter/issues/105538"); } - auto stage = CreateStageFromFixture("ink_sparkle.frag.iplr"); + auto stage = OpenAssetAsRuntimeStage("ink_sparkle.frag.iplr"); ASSERT_NE(stage, nullptr); ASSERT_TRUE(RegisterStage(*stage)); auto library = GetContext()->GetShaderLibrary(); diff --git a/lib/ui/semantics.dart b/lib/ui/semantics.dart index e18aff4d63f62..9bf8d2be76d8c 100644 --- a/lib/ui/semantics.dart +++ b/lib/ui/semantics.dart @@ -285,7 +285,9 @@ class SemanticsAction { // // When changes are made to this class, the equivalent APIs in // `lib/ui/semantics/semantics_node.h` and in each of the embedders *must* be -// updated. +// updated. If the change affects the visibility of a [SemanticsNode] to +// accessibility services, `flutter_test/controller.dart#SemanticsController._importantFlags` +// must be updated as well. class SemanticsFlag { const SemanticsFlag._(this.index) : assert(index != null); @@ -324,7 +326,10 @@ class SemanticsFlag { // value in testing/dart/semantics_test.dart, or tests will fail. Also, // please update the Flag enum in // flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java, - // and the SemanticsFlag class in lib/web_ui/lib/semantics.dart. + // and the SemanticsFlag class in lib/web_ui/lib/semantics.dart. If the new flag + // affects the visibility of a [SemanticsNode] to accessibility services, + // `flutter_test/controller.dart#SemanticsController._importantFlags` + // must be updated as well. /// The semantics node has the quality of either being "checked" or "unchecked". /// diff --git a/lib/web_ui/dev/build.dart b/lib/web_ui/dev/build.dart index b36d3035e2899..215d59a2dbf95 100644 --- a/lib/web_ui/dev/build.dart +++ b/lib/web_ui/dev/build.dart @@ -23,14 +23,14 @@ class BuildCommand extends Command with ArgUtils { ); argParser.addFlag( 'build-canvaskit', - help: 'Build CanvasKit locally instead of getting it from CIPD. Disabled ' + help: 'Build CanvasKit locally instead of getting it from CIPD. Enabled ' 'by default.', defaultsTo: true ); argParser.addFlag( 'host', - help: 'Build the host build instead of the wasm build, which is currently' - 'needed for `flutter run --local-engine` to work' + help: 'Build the host build instead of the wasm build, which is ' + 'currently needed for `flutter run --local-engine` to work.' ); } diff --git a/shell/common/animator.cc b/shell/common/animator.cc index dacf8fad50d6b..6468f0f45f627 100644 --- a/shell/common/animator.cc +++ b/shell/common/animator.cc @@ -104,10 +104,6 @@ void Animator::BeginFrame( // We have acquired a valid continuation from the pipeline and are ready // to service potential frame. FML_DCHECK(producer_continuation_); - fml::tracing::TraceEventAsyncComplete( - "flutter", "VsyncSchedulingOverhead", - frame_timings_recorder_->GetVsyncStartTime(), - frame_timings_recorder_->GetBuildStartTime()); const fml::TimePoint frame_target_time = frame_timings_recorder_->GetVsyncTargetTime(); dart_frame_deadline_ = FxlToDartOrEarlier(frame_target_time); @@ -195,6 +191,10 @@ bool Animator::CanReuseLastLayerTree() { void Animator::DrawLastLayerTree( std::unique_ptr frame_timings_recorder) { + // This method is very cheap, but this makes it explicitly clear in trace + // files. + TRACE_EVENT0("flutter", "Animator::DrawLastLayerTree"); + pending_frame_semaphore_.Signal(); // In this case BeginFrame doesn't get called, we need to // adjust frame timings to update build start and end times, @@ -208,6 +208,12 @@ void Animator::DrawLastLayerTree( void Animator::RequestFrame(bool regenerate_layer_tree) { if (regenerate_layer_tree) { + // This event will be closed by BeginFrame. BeginFrame will only be called + // if regenerating the layer tree. If a frame has been requested to update + // an external texture, this will be false and no BeginFrame call will + // happen. + TRACE_EVENT_ASYNC_BEGIN0("flutter", "Frame Request Pending", + frame_request_number_); regenerate_layer_tree_ = true; } @@ -225,13 +231,10 @@ void Animator::RequestFrame(bool regenerate_layer_tree) { // To support that, we need edge triggered wakes on VSync. task_runners_.GetUITaskRunner()->PostTask( - [self = weak_factory_.GetWeakPtr(), - frame_request_number = frame_request_number_]() { + [self = weak_factory_.GetWeakPtr()]() { if (!self) { return; } - TRACE_EVENT_ASYNC_BEGIN0("flutter", "Frame Request Pending", - frame_request_number); self->AwaitVSync(); }); frame_scheduled_ = true; diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index ba0d90455990c..72b78221dd625 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -885,7 +885,8 @@ && shouldSetCollectionInfo(semanticsNode)) { // Scopes routes are not focusable, only need to set the content // for non-scopes-routes semantics nodes. if (semanticsNode.hasFlag(Flag.IS_TEXT_FIELD)) { - result.setText(semanticsNode.getValueLabelHint()); + result.setText(semanticsNode.getValue()); + result.setHintText(semanticsNode.getTextFieldHint()); } else if (!semanticsNode.hasFlag(Flag.SCOPES_ROUTE)) { CharSequence content = semanticsNode.getValueLabelHint(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { @@ -2773,18 +2774,47 @@ private float max(float a, float b, float c, float d) { return Math.max(a, Math.max(b, Math.max(c, d))); } - private CharSequence getValueLabelHint() { - CharSequence[] array; + private CharSequence getValue() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return value; + } else { + return createSpannableString(value, valueAttributes); + } + } + + private CharSequence getLabel() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return label; + } else { + return createSpannableString(label, labelAttributes); + } + } + + private CharSequence getHint() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - array = new CharSequence[] {value, label, hint}; + return hint; } else { - array = - new CharSequence[] { - createSpannableString(value, valueAttributes), - createSpannableString(label, labelAttributes), - createSpannableString(hint, hintAttributes), - }; + return createSpannableString(hint, hintAttributes); } + } + + private CharSequence getValueLabelHint() { + CharSequence[] array = new CharSequence[] {getValue(), getLabel(), getHint()}; + CharSequence result = null; + for (CharSequence word : array) { + if (word != null && word.length() > 0) { + if (result == null || result.length() == 0) { + result = word; + } else { + result = TextUtils.concat(result, ", ", word); + } + } + } + return result; + } + + private CharSequence getTextFieldHint() { + CharSequence[] array = new CharSequence[] {getLabel(), getHint()}; CharSequence result = null; for (CharSequence word : array) { if (word != null && word.length() > 0) { diff --git a/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java b/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java index 2f3091f9baa4c..47204fee28960 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java @@ -230,30 +230,6 @@ public void ignoreAccessibilityEvents() { assertFalse(eventSent); } - @Test - @Config( - shadows = { - ShadowViewGroup.class, - }) - public void sendAccessibilityEvents() { - final FlutterMutatorView wrapperView = new FlutterMutatorView(ctx); - - final View embeddedView = mock(View.class); - wrapperView.addView(embeddedView); - - when(embeddedView.getImportantForAccessibility()) - .thenReturn(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - boolean eventSent = - wrapperView.requestSendAccessibilityEvent(embeddedView, mock(AccessibilityEvent.class)); - assertTrue(eventSent); - - when(embeddedView.getImportantForAccessibility()) - .thenReturn(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); - eventSent = - wrapperView.requestSendAccessibilityEvent(embeddedView, mock(AccessibilityEvent.class)); - assertTrue(eventSent); - } - @Implements(ViewGroup.class) public static class ShadowViewGroup extends org.robolectric.shadows.ShadowView { @Implementation diff --git a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java index a88c0323a7b33..0d4a46b10088c 100644 --- a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java +++ b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java @@ -77,11 +77,13 @@ public void itDescribesNonTextFieldsWithAContentDescription() { } @Test - public void itDescribesTextFieldsWithText() { + public void itDescribesTextFieldsWithTextAndHint() { AccessibilityBridge accessibilityBridge = setUpBridge(); TestSemanticsNode testSemanticsNode = new TestSemanticsNode(); - testSemanticsNode.label = "Hello, World"; + testSemanticsNode.value = "Hello, World"; + testSemanticsNode.label = "some label"; + testSemanticsNode.hint = "some hint"; testSemanticsNode.addFlag(AccessibilityBridge.Flag.IS_TEXT_FIELD); TestSemanticsUpdate testSemanticsUpdate = testSemanticsNode.toUpdate(); testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge); @@ -89,6 +91,7 @@ public void itDescribesTextFieldsWithText() { assertEquals(nodeInfo.getContentDescription(), null); assertEquals(nodeInfo.getText().toString(), "Hello, World"); + assertEquals(nodeInfo.getHintText().toString(), "some label, some hint"); } @Test diff --git a/shell/platform/windows/direct_manipulation.cc b/shell/platform/windows/direct_manipulation.cc index 02e0167fbf4e2..41ddf07b5acec 100644 --- a/shell/platform/windows/direct_manipulation.cc +++ b/shell/platform/windows/direct_manipulation.cc @@ -4,6 +4,8 @@ #include "flutter/fml/logging.h" +#include + #include "flutter/shell/platform/windows/direct_manipulation.h" #include "flutter/shell/platform/windows/window.h" #include "flutter/shell/platform/windows/window_binding_handler_delegate.h" @@ -24,6 +26,10 @@ namespace flutter { +int32_t DirectManipulationEventHandler::GetDeviceId() { + return (int32_t) reinterpret_cast(this); +} + STDMETHODIMP DirectManipulationEventHandler::QueryInterface(REFIID iid, void** ppv) { if ((iid == IID_IUnknown) || @@ -43,26 +49,39 @@ HRESULT DirectManipulationEventHandler::OnViewportStatusChanged( IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current, DIRECTMANIPULATION_STATUS previous) { + during_inertia_ = current == DIRECTMANIPULATION_INERTIA; if (during_synthesized_reset_ && previous == DIRECTMANIPULATION_RUNNING) { during_synthesized_reset_ = false; } else if (current == DIRECTMANIPULATION_RUNNING) { if (!during_synthesized_reset_) { // Not a false event. if (owner_->binding_handler_delegate) { - owner_->binding_handler_delegate->OnPointerPanZoomStart( - (int32_t) reinterpret_cast(this)); + owner_->binding_handler_delegate->OnPointerPanZoomStart(GetDeviceId()); } } - } else if (previous == DIRECTMANIPULATION_RUNNING) { + } + if (previous == DIRECTMANIPULATION_RUNNING) { + // Reset deltas to ensure only inertia values will be compared later. + last_pan_delta_x_ = 0.0; + last_pan_delta_y_ = 0.0; if (owner_->binding_handler_delegate) { - owner_->binding_handler_delegate->OnPointerPanZoomEnd( - (int32_t) reinterpret_cast(this)); + owner_->binding_handler_delegate->OnPointerPanZoomEnd(GetDeviceId()); + } + } else if (previous == DIRECTMANIPULATION_INERTIA) { + if (owner_->binding_handler_delegate && + (std::max)(std::abs(last_pan_delta_x_), std::abs(last_pan_delta_y_)) > + 0.01) { + owner_->binding_handler_delegate->OnScrollInertiaCancel(GetDeviceId()); } // Need to reset the content transform to its original position // so that we are ready for the next gesture. // Use during_synthesized_reset_ flag to prevent sending reset also to the // framework. during_synthesized_reset_ = true; + last_pan_x_ = 0.0; + last_pan_y_ = 0.0; + last_pan_delta_x_ = 0.0; + last_pan_delta_y_ = 0.0; RECT rect; HRESULT hr = viewport->GetViewportRect(&rect); if (FAILED(hr)) { @@ -104,9 +123,13 @@ HRESULT DirectManipulationEventHandler::OnContentUpdated( float scale = c - (c - transform[0]); float pan_x = transform[4]; float pan_y = transform[5]; - if (owner_->binding_handler_delegate) { + last_pan_delta_x_ = pan_x - last_pan_x_; + last_pan_delta_y_ = pan_y - last_pan_y_; + last_pan_x_ = pan_x; + last_pan_y_ = pan_y; + if (owner_->binding_handler_delegate && !during_inertia_) { owner_->binding_handler_delegate->OnPointerPanZoomUpdate( - (int32_t) reinterpret_cast(this), pan_x, pan_y, scale, 0); + GetDeviceId(), pan_x, pan_y, scale, 0); } } return S_OK; @@ -144,7 +167,8 @@ int DirectManipulationOwner::Init(unsigned int width, unsigned int height) { DIRECTMANIPULATION_CONFIGURATION_INTERACTION | DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X | DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y | - DIRECTMANIPULATION_CONFIGURATION_SCALING; + DIRECTMANIPULATION_CONFIGURATION_SCALING | + DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA; RETURN_IF_FAILED(viewport_->ActivateConfiguration(configuration)); RETURN_IF_FAILED(viewport_->SetViewportOptions( DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE)); diff --git a/shell/platform/windows/direct_manipulation.h b/shell/platform/windows/direct_manipulation.h index 40317333f6916..e9aef840fdafa 100644 --- a/shell/platform/windows/direct_manipulation.h +++ b/shell/platform/windows/direct_manipulation.h @@ -106,12 +106,23 @@ class DirectManipulationEventHandler DIRECTMANIPULATION_INTERACTION_TYPE interaction) override; private: + // Unique identifier to associate with all gesture event updates. + int32_t GetDeviceId(); // Parent object, used to store the target for gesture event updates. DirectManipulationOwner* owner_; // We need to reset some parts of DirectManipulation after each gesture // A flag is needed to ensure that false events created as the reset occurs // are not sent to the flutter framework. bool during_synthesized_reset_ = false; + // Store whether current events are from synthetic inertia rather than user + // input. + bool during_inertia_ = false; + // Store the difference between the last pan offsets to determine if inertia + // has been cancelled in the middle of an animation. + float last_pan_x_ = 0.0; + float last_pan_y_ = 0.0; + float last_pan_delta_x_ = 0.0; + float last_pan_delta_y_ = 0.0; }; } // namespace flutter diff --git a/shell/platform/windows/direct_manipulation_unittests.cc b/shell/platform/windows/direct_manipulation_unittests.cc index 5975103e849b8..1d03a0396ddde 100644 --- a/shell/platform/windows/direct_manipulation_unittests.cc +++ b/shell/platform/windows/direct_manipulation_unittests.cc @@ -259,5 +259,110 @@ TEST(DirectManipulationTest, TestRounding) { DIRECTMANIPULATION_INERTIA); } +TEST(DirectManipulationTest, TestInertiaCancelSentForUserCancel) { + MockIDirectManipulationContent content; + MockWindowBindingHandlerDelegate delegate; + MockIDirectManipulationViewport viewport; + const int DISPLAY_WIDTH = 800; + const int DISPLAY_HEIGHT = 600; + auto owner = std::make_unique(nullptr); + owner->SetBindingHandlerDelegate(&delegate); + auto handler = + fml::MakeRefCounted(owner.get()); + int32_t device_id = (int32_t) reinterpret_cast(handler.get()); + // No need to mock the actual gesture, just start at the end. + EXPECT_CALL(viewport, GetViewportRect(_)) + .WillOnce(::testing::Invoke([DISPLAY_WIDTH, DISPLAY_HEIGHT](RECT* rect) { + rect->left = 0; + rect->top = 0; + rect->right = DISPLAY_WIDTH; + rect->bottom = DISPLAY_HEIGHT; + return S_OK; + })); + EXPECT_CALL(viewport, ZoomToRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, false)) + .WillOnce(::testing::Return(S_OK)); + EXPECT_CALL(delegate, OnPointerPanZoomEnd(device_id)); + handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport, + DIRECTMANIPULATION_INERTIA, + DIRECTMANIPULATION_RUNNING); + // Have pan_y change by 10 between inertia updates. + EXPECT_CALL(content, GetContentTransform(_, 6)) + .WillOnce(::testing::Invoke([](float* transform, DWORD size) { + transform[0] = 1; + transform[4] = 0; + transform[5] = 100; + return S_OK; + })); + handler->OnContentUpdated((IDirectManipulationViewport*)&viewport, + (IDirectManipulationContent*)&content); + EXPECT_CALL(content, GetContentTransform(_, 6)) + .WillOnce(::testing::Invoke([](float* transform, DWORD size) { + transform[0] = 1; + transform[4] = 0; + transform[5] = 110; + return S_OK; + })); + handler->OnContentUpdated((IDirectManipulationViewport*)&viewport, + (IDirectManipulationContent*)&content); + // This looks like an interruption in the middle of synthetic inertia because + // of user input. + EXPECT_CALL(delegate, OnScrollInertiaCancel(device_id)); + handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport, + DIRECTMANIPULATION_READY, + DIRECTMANIPULATION_INERTIA); +} + +TEST(DirectManipulationTest, TestInertiaCamcelNotSentAtInertiaEnd) { + MockIDirectManipulationContent content; + MockWindowBindingHandlerDelegate delegate; + MockIDirectManipulationViewport viewport; + const int DISPLAY_WIDTH = 800; + const int DISPLAY_HEIGHT = 600; + auto owner = std::make_unique(nullptr); + owner->SetBindingHandlerDelegate(&delegate); + auto handler = + fml::MakeRefCounted(owner.get()); + int32_t device_id = (int32_t) reinterpret_cast(handler.get()); + // No need to mock the actual gesture, just start at the end. + EXPECT_CALL(viewport, GetViewportRect(_)) + .WillOnce(::testing::Invoke([DISPLAY_WIDTH, DISPLAY_HEIGHT](RECT* rect) { + rect->left = 0; + rect->top = 0; + rect->right = DISPLAY_WIDTH; + rect->bottom = DISPLAY_HEIGHT; + return S_OK; + })); + EXPECT_CALL(viewport, ZoomToRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, false)) + .WillOnce(::testing::Return(S_OK)); + EXPECT_CALL(delegate, OnPointerPanZoomEnd(device_id)); + handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport, + DIRECTMANIPULATION_INERTIA, + DIRECTMANIPULATION_RUNNING); + // Have no change in pan between events. + EXPECT_CALL(content, GetContentTransform(_, 6)) + .WillOnce(::testing::Invoke([](float* transform, DWORD size) { + transform[0] = 1; + transform[4] = 0; + transform[5] = 140; + return S_OK; + })); + handler->OnContentUpdated((IDirectManipulationViewport*)&viewport, + (IDirectManipulationContent*)&content); + EXPECT_CALL(content, GetContentTransform(_, 6)) + .WillOnce(::testing::Invoke([](float* transform, DWORD size) { + transform[0] = 1; + transform[4] = 0; + transform[5] = 140; + return S_OK; + })); + handler->OnContentUpdated((IDirectManipulationViewport*)&viewport, + (IDirectManipulationContent*)&content); + // OnScrollInertiaCancel should not be called. + EXPECT_CALL(delegate, OnScrollInertiaCancel(device_id)).Times(0); + handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport, + DIRECTMANIPULATION_READY, + DIRECTMANIPULATION_INERTIA); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index aefff5d522492..e212b6cf6140a 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -264,6 +264,11 @@ void FlutterWindowsView::OnScroll(double x, device_id); } +void FlutterWindowsView::OnScrollInertiaCancel(int32_t device_id) { + PointerLocation point = binding_handler_->GetPrimaryPointerLocation(); + SendScrollInertiaCancel(device_id, point.x, point.y); +} + void FlutterWindowsView::OnUpdateSemanticsEnabled(bool enabled) { engine_->UpdateSemanticsEnabled(enabled); } @@ -500,6 +505,21 @@ void FlutterWindowsView::SendScroll(double x, SendPointerEventWithData(event, state); } +void FlutterWindowsView::SendScrollInertiaCancel(int32_t device_id, + double x, + double y) { + auto state = + GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id); + + FlutterPointerEvent event = {}; + event.x = x; + event.y = y; + event.signal_kind = + FlutterPointerSignalKind::kFlutterPointerSignalKindScrollInertiaCancel; + SetEventPhaseFromCursorButtonState(&event, state); + SendPointerEventWithData(event, state); +} + void FlutterWindowsView::SendPointerEventWithData( const FlutterPointerEvent& event_data, PointerState* state) { diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index 45dc6fdb6a40c..6075903309479 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -180,6 +180,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, FlutterPointerDeviceKind device_kind, int32_t device_id) override; + // |WindowBindingHandlerDelegate| + void OnScrollInertiaCancel(int32_t device_id) override; + // |WindowBindingHandlerDelegate| virtual void OnUpdateSemanticsEnabled(bool enabled) override; @@ -331,6 +334,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, FlutterPointerDeviceKind device_kind, int32_t device_id); + // Reports scroll inertia cancel events to Flutter engine. + void SendScrollInertiaCancel(int32_t device_id, double x, double y); + // Creates a PointerState object unless it already exists. PointerState* GetOrCreatePointerState(FlutterPointerDeviceKind device_kind, int32_t device_id); diff --git a/shell/platform/windows/testing/mock_window_binding_handler_delegate.h b/shell/platform/windows/testing/mock_window_binding_handler_delegate.h index a7ad72cbe2623..03534b2e5ecfd 100644 --- a/shell/platform/windows/testing/mock_window_binding_handler_delegate.h +++ b/shell/platform/windows/testing/mock_window_binding_handler_delegate.h @@ -60,6 +60,7 @@ class MockWindowBindingHandlerDelegate : public WindowBindingHandlerDelegate { int, FlutterPointerDeviceKind, int32_t)); + MOCK_METHOD1(OnScrollInertiaCancel, void(int32_t)); MOCK_METHOD0(OnPlatformBrightnessChanged, void()); MOCK_METHOD1(UpdateHighContrastEnabled, void(bool enabled)); }; diff --git a/shell/platform/windows/window_binding_handler_delegate.h b/shell/platform/windows/window_binding_handler_delegate.h index 38569fda296bb..485fe4384f3cc 100644 --- a/shell/platform/windows/window_binding_handler_delegate.h +++ b/shell/platform/windows/window_binding_handler_delegate.h @@ -122,6 +122,10 @@ class WindowBindingHandlerDelegate { FlutterPointerDeviceKind device_kind, int32_t device_id) = 0; + // Notifies delegate that scroll inertia should be cancelled. + // Typically called by DirectManipulationEventHandler + virtual void OnScrollInertiaCancel(int32_t device_id) = 0; + // Notifies delegate that the Flutter semantics tree should be enabled or // disabled. virtual void OnUpdateSemanticsEnabled(bool enabled) = 0;