| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "tests/embedder_test_context.h" |
| #define FML_USED_ON_EMBEDDER |
| |
| #include <atomic> |
| #include <string> |
| #include <vector> |
| |
| #include "vulkan/vulkan.h" |
| |
| #include "GLES3/gl3.h" |
| #include "flutter/flow/raster_cache.h" |
| #include "flutter/fml/file.h" |
| #include "flutter/fml/make_copyable.h" |
| #include "flutter/fml/mapping.h" |
| #include "flutter/fml/message_loop.h" |
| #include "flutter/fml/message_loop_task_queues.h" |
| #include "flutter/fml/native_library.h" |
| #include "flutter/fml/paths.h" |
| #include "flutter/fml/synchronization/count_down_latch.h" |
| #include "flutter/fml/synchronization/waitable_event.h" |
| #include "flutter/fml/task_source.h" |
| #include "flutter/fml/thread.h" |
| #include "flutter/lib/ui/painting/image.h" |
| #include "flutter/runtime/dart_vm.h" |
| #include "flutter/shell/platform/embedder/tests/embedder_assertions.h" |
| #include "flutter/shell/platform/embedder/tests/embedder_config_builder.h" |
| #include "flutter/shell/platform/embedder/tests/embedder_test.h" |
| #include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" |
| #include "flutter/shell/platform/embedder/tests/embedder_unittests_util.h" |
| #include "flutter/testing/assertions_skia.h" |
| #include "flutter/testing/test_gl_surface.h" |
| #include "flutter/testing/testing.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "third_party/tonic/converter/dart_converter.h" |
| |
| // CREATE_NATIVE_ENTRY is leaky by design |
| // NOLINTBEGIN(clang-analyzer-core.StackAddressEscape) |
| |
| namespace flutter { |
| namespace testing { |
| |
| using EmbedderTest = testing::EmbedderTest; |
| |
| TEST_F(EmbedderTest, CanGetVulkanEmbedderContext) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kVulkanContext); |
| EmbedderConfigBuilder builder(context); |
| } |
| |
| TEST_F(EmbedderTest, CanCreateOpenGLRenderingEngine) { |
| EmbedderConfigBuilder builder( |
| GetEmbedderContext(EmbedderTestContextType::kOpenGLContext)); |
| builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// If an incorrectly configured compositor is set on the engine, the engine |
| /// must fail to launch instead of failing to render a frame at a later point in |
| /// time. |
| /// |
| TEST_F(EmbedderTest, |
| MustPreventEngineLaunchWhenRequiredCompositorArgsAreAbsent) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); |
| builder.SetCompositor(); |
| builder.GetCompositor().create_backing_store_callback = nullptr; |
| builder.GetCompositor().collect_backing_store_callback = nullptr; |
| builder.GetCompositor().present_layers_callback = nullptr; |
| auto engine = builder.LaunchEngine(); |
| ASSERT_FALSE(engine.is_valid()); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Must be able to render to a custom compositor whose render targets are fully |
| /// complete OpenGL textures. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLFramebuffer) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); |
| |
| fml::CountDownLatch latch(3); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(123.0, 456.0); |
| layer.offset = FlutterPointMake(1.0, 2.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Layers in a hierarchy containing a platform view should not be cached. The |
| /// other layers in the hierarchy should be, however. |
| TEST_F(EmbedderTest, RasterCacheDisabledWithPlatformViews) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views_with_opacity"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); |
| |
| fml::CountDownLatch setup(3); |
| fml::CountDownLatch verify(1); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(123.0, 456.0); |
| layer.offset = FlutterPointMake(1.0, 2.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| setup.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&setup](Dart_NativeArguments args) { setup.CountDown(); })); |
| |
| UniqueEngine engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| setup.Wait(); |
| const flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); |
| shell.GetTaskRunners().GetRasterTaskRunner()->PostTask([&] { |
| const flutter::RasterCache& raster_cache = |
| shell.GetRasterizer()->compositor_context()->raster_cache(); |
| // 3 layers total, but one of them had the platform view. So the cache |
| // should only have 2 entries. |
| ASSERT_EQ(raster_cache.GetCachedEntriesCount(), 2u); |
| verify.CountDown(); |
| }); |
| |
| verify.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// The RasterCache should normally be enabled. |
| /// |
| TEST_F(EmbedderTest, RasterCacheEnabled) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_with_opacity"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); |
| |
| fml::CountDownLatch setup(3); |
| fml::CountDownLatch verify(1); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 1u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| setup.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&setup](Dart_NativeArguments args) { setup.CountDown(); })); |
| |
| UniqueEngine engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| setup.Wait(); |
| const flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); |
| shell.GetTaskRunners().GetRasterTaskRunner()->PostTask([&] { |
| const flutter::RasterCache& raster_cache = |
| shell.GetRasterizer()->compositor_context()->raster_cache(); |
| ASSERT_EQ(raster_cache.GetCachedEntriesCount(), 1u); |
| verify.CountDown(); |
| }); |
| |
| verify.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Must be able to render using a custom compositor whose render targets for |
| /// the individual layers are OpenGL textures. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLTexture) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(3); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(123.0, 456.0); |
| layer.offset = FlutterPointMake(1.0, 2.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Must be able to render using a custom compositor whose render target for the |
| /// individual layers are software buffers. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToSoftwareBuffer) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kSoftwareBuffer); |
| |
| fml::CountDownLatch latch(3); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeSoftware; |
| backing_store.did_update = true; |
| ASSERT_FLOAT_EQ( |
| backing_store.software.row_bytes * backing_store.software.height, |
| 800 * 4 * 600.0); |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(123.0, 456.0); |
| layer.offset = FlutterPointMake(1.0, 2.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeSoftware; |
| backing_store.did_update = true; |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test the layer structure and pixels rendered when using a custom compositor. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownScene) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(5); |
| |
| auto scene_image = context.GetNextSceneImage(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 5u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(50.0, 150.0); |
| layer.offset = FlutterPointMake(20.0, 20.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| // Layer 3 |
| { |
| FlutterPlatformView platform_view = *layers[3]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 2; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(50.0, 150.0); |
| layer.offset = FlutterPointMake(40.0, 40.0); |
| |
| ASSERT_EQ(*layers[3], layer); |
| } |
| |
| // Layer 4 |
| { |
| FlutterBackingStore backing_store = *layers[4]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[4], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [&](const FlutterLayer& layer, |
| GrDirectContext* context) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface(layer, context); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 1: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorGREEN); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| case 2: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorMAGENTA); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE(ImageMatchesFixture("compositor.png", scene_image)); |
| |
| // There should no present calls on the root surface. |
| ASSERT_EQ(context.GetSurfacePresentCount(), 0u); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Custom compositor must play nicely with a custom task runner. The raster |
| /// thread merging mechanism must not interfere with the custom compositor. |
| /// |
| TEST_F(EmbedderTest, CustomCompositorMustWorkWithCustomTaskRunner) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views"); |
| |
| auto platform_task_runner = CreateNewThread("test_platform_thread"); |
| static std::mutex engine_mutex; |
| UniqueEngine engine; |
| fml::AutoResetWaitableEvent sync_latch; |
| |
| EmbedderTestTaskRunner test_task_runner( |
| platform_task_runner, [&](FlutterTask task) { |
| std::scoped_lock lock(engine_mutex); |
| if (!engine.is_valid()) { |
| return; |
| } |
| ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); |
| }); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(3); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0, 0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(123.0, 456.0); |
| layer.offset = FlutterPointMake(1.0, 2.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.struct_size = sizeof(backing_store); |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| const auto task_runner_description = |
| test_task_runner.GetFlutterTaskRunnerDescription(); |
| |
| builder.SetPlatformTaskRunner(&task_runner_description); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| platform_task_runner->PostTask([&]() { |
| std::scoped_lock lock(engine_mutex); |
| engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| sync_latch.Signal(); |
| }); |
| sync_latch.Wait(); |
| |
| latch.Wait(); |
| |
| platform_task_runner->PostTask([&]() { |
| std::scoped_lock lock(engine_mutex); |
| engine.reset(); |
| sync_latch.Signal(); |
| }); |
| sync_latch.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test the layer structure and pixels rendered when using a custom compositor |
| /// and a single layer. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithRootLayerOnly) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint( |
| "can_composite_platform_views_with_root_layer_only"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(3); |
| |
| auto scene_image = context.GetNextSceneImage(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 1u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE( |
| ImageMatchesFixture("compositor_with_root_layer_only.png", scene_image)); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test the layer structure and pixels rendered when using a custom compositor |
| /// and ensure that a redundant layer is not added. |
| /// |
| TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithPlatformLayerOnBottom) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint( |
| "can_composite_platform_views_with_platform_layer_on_bottom"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(3); |
| |
| auto scene_image = context.GetNextSceneImage(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(50.0, 150.0); |
| layer.offset = FlutterPointMake(20.0, 20.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [&](const FlutterLayer& layer, |
| GrDirectContext* context) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface(layer, context); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 1: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorGREEN); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE(ImageMatchesFixture( |
| "compositor_with_platform_layer_on_bottom.png", scene_image)); |
| |
| ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 1u); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Test the layer structure and pixels rendered when using a custom compositor |
| /// with a root surface transformation. |
| /// |
| TEST_F(EmbedderTest, |
| CompositorMustBeAbleToRenderKnownSceneWithRootSurfaceTransformation) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| // This must match the transformation provided in the |
| // |CanRenderGradientWithoutCompositorWithXform| test to ensure that |
| // transforms are consistent respected. |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| fml::CountDownLatch latch(5); |
| |
| auto scene_image = context.GetNextSceneImage(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 5u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(150.0, 50.0); |
| layer.offset = FlutterPointMake(20.0, 730.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| // Layer 3 |
| { |
| FlutterPlatformView platform_view = *layers[3]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 2; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(150.0, 50.0); |
| layer.offset = FlutterPointMake(40.0, 710.0); |
| |
| ASSERT_EQ(*layers[3], layer); |
| } |
| |
| // Layer 4 |
| { |
| FlutterBackingStore backing_store = *layers[4]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[4], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [&](const FlutterLayer& layer, |
| GrDirectContext* context) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface(layer, context); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 1: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorGREEN); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| case 2: { |
| SkPaint paint; |
| // See dart test for total order. |
| paint.setColor(SK_ColorMAGENTA); |
| paint.setAlpha(127); |
| const auto& rect = |
| SkRect::MakeWH(layer.size.width, layer.size.height); |
| canvas->drawRect(rect, paint); |
| latch.CountDown(); |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.CountDown(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| // Flutter still thinks it is 800 x 600. Only the root surface is rotated. |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE(ImageMatchesFixture("compositor_root_surface_xformation.png", |
| scene_image)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositor) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| |
| auto rendered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("scene_without_custom_compositor.png", |
| rendered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositorWithTransformation) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); |
| |
| auto rendered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| |
| // Flutter still thinks it is 800 x 600. |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture( |
| "scene_without_custom_compositor_with_xform.png", rendered_scene)); |
| } |
| |
| TEST_P(EmbedderTestMultiBackend, CanRenderGradientWithoutCompositor) { |
| EmbedderTestContextType backend = GetParam(); |
| auto& context = GetEmbedderContext(backend); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.SetRendererConfig(backend, SkISize::Make(800, 600)); |
| |
| auto rendered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture( |
| FixtureNameForBackend(backend, "gradient.png"), rendered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderGradientWithoutCompositorWithXform) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| const auto surface_size = SkISize::Make(600, 800); |
| |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.SetOpenGLRendererConfig(surface_size); |
| |
| auto rendered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| // Flutter still thinks it is 800 x 600. |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("gradient_xform.png", rendered_scene)); |
| } |
| |
| TEST_P(EmbedderTestMultiBackend, CanRenderGradientWithCompositor) { |
| EmbedderTestContextType backend = GetParam(); |
| auto& context = GetEmbedderContext(backend); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.SetRendererConfig(backend, SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetRenderTargetType(GetRenderTargetFromBackend(backend, true)); |
| |
| auto rendered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture( |
| FixtureNameForBackend(backend, "gradient.png"), rendered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderGradientWithCompositorWithXform) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| // This must match the transformation provided in the |
| // |CanRenderGradientWithoutCompositorWithXform| test to ensure that |
| // transforms are consistent respected. |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); |
| builder.SetCompositor(); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); |
| |
| auto rendered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| // Flutter still thinks it is 800 x 600. |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("gradient_xform.png", rendered_scene)); |
| } |
| |
| TEST_P(EmbedderTestMultiBackend, |
| CanRenderGradientWithCompositorOnNonRootLayer) { |
| EmbedderTestContextType backend = GetParam(); |
| auto& context = GetEmbedderContext(backend); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("render_gradient_on_non_root_backing_store"); |
| builder.SetRendererConfig(backend, SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetRenderTargetType(GetRenderTargetFromBackend(backend, true)); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.did_update = true; |
| ConfigureBackingStore(backing_store, backend, true); |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(100.0, 200.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.did_update = true; |
| ConfigureBackingStore(backing_store, backend, true); |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [&](const FlutterLayer& layer, |
| GrDirectContext* context) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface(layer, context); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 1: { |
| FML_CHECK(layer.size.width == 100); |
| FML_CHECK(layer.size.height == 200); |
| // This is occluded anyway. We just want to make sure we see this. |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| auto rendered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture( |
| FixtureNameForBackend(backend, "gradient.png"), rendered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayerWithXform) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| // This must match the transformation provided in the |
| // |CanRenderGradientWithoutCompositorWithXform| test to ensure that |
| // transforms are consistent respected. |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| builder.SetDartEntrypoint("render_gradient_on_non_root_backing_store"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); |
| builder.SetCompositor(); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| // Layer Root |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(200.0, 100.0); |
| layer.offset = FlutterPointMake(0.0, 700.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| }); |
| |
| context.GetCompositor().SetPlatformViewRendererCallback( |
| [&](const FlutterLayer& layer, |
| GrDirectContext* context) -> sk_sp<SkImage> { |
| auto surface = CreateRenderSurface(layer, context); |
| auto canvas = surface->getCanvas(); |
| FML_CHECK(canvas != nullptr); |
| |
| switch (layer.platform_view->identifier) { |
| case 1: { |
| FML_CHECK(layer.size.width == 200); |
| FML_CHECK(layer.size.height == 100); |
| // This is occluded anyway. We just want to make sure we see this. |
| } break; |
| default: |
| // Asked to render an unknown platform view. |
| FML_CHECK(false) |
| << "Test was asked to composite an unknown platform view."; |
| } |
| |
| return surface->makeImageSnapshot(); |
| }); |
| |
| auto rendered_scene = context.GetNextSceneImage(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| // Flutter still thinks it is 800 x 600. |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("gradient_xform.png", rendered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, VerifyB141980393) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| |
| // The Flutter application is 800 x 600 but rendering on a surface that is 600 |
| // x 800 achieved using a root surface transformation. |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| const auto flutter_application_rect = SkRect::MakeWH(800, 600); |
| const auto root_surface_rect = |
| root_surface_transformation.mapRect(flutter_application_rect); |
| |
| ASSERT_DOUBLE_EQ(root_surface_rect.width(), 600.0); |
| ASSERT_DOUBLE_EQ(root_surface_rect.height(), 800.0); |
| |
| // Configure the fixture for the surface transformation. |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| // Configure the Flutter project args for the root surface transformation. |
| builder.SetOpenGLRendererConfig( |
| SkISize::Make(root_surface_rect.width(), root_surface_rect.height())); |
| |
| // Use a compositor instead of rendering directly to the surface. |
| builder.SetCompositor(); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); |
| |
| builder.SetDartEntrypoint("verify_b141980393"); |
| |
| fml::AutoResetWaitableEvent latch; |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 1u); |
| |
| // Layer Root |
| { |
| FlutterPlatformView platform_view = *layers[0]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 1337; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| |
| // From the Dart side. These dimensions match those specified in Dart |
| // code and are free of root surface transformations. |
| const double unxformed_top_margin = 31.0; |
| const double unxformed_bottom_margin = 37.0; |
| const auto unxformed_platform_view_rect = SkRect::MakeXYWH( |
| 0.0, // x |
| unxformed_top_margin, // y (top margin) |
| 800, // width |
| 600 - unxformed_top_margin - unxformed_bottom_margin // height |
| ); |
| |
| // The platform views are in the coordinate space of the root surface |
| // with top-left origin. The embedder has specified a transformation |
| // to this surface which it must account for in the coordinates it |
| // receives here. |
| const auto xformed_platform_view_rect = |
| root_surface_transformation.mapRect(unxformed_platform_view_rect); |
| |
| // Spell out the value that we are going to be checking below for |
| // clarity. |
| ASSERT_EQ(xformed_platform_view_rect, |
| SkRect::MakeXYWH(31.0, // x |
| 0.0, // y |
| 532.0, // width |
| 800.0 // height |
| )); |
| |
| // Verify that the engine is giving us the right size and offset. |
| layer.offset = FlutterPointMake(xformed_platform_view_rect.x(), |
| xformed_platform_view_rect.y()); |
| layer.size = FlutterSizeMake(xformed_platform_view_rect.width(), |
| xformed_platform_view_rect.height()); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| latch.Signal(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| |
| // The Flutter application is 800 x 600 rendering on a surface 600 x 800 |
| // achieved via a root surface transformation. |
| event.width = flutter_application_rect.width(); |
| event.height = flutter_application_rect.height(); |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Asserts that embedders can provide a task runner for the render thread. |
| /// |
| TEST_F(EmbedderTest, CanCreateEmbedderWithCustomRenderTaskRunner) { |
| std::mutex engine_mutex; |
| UniqueEngine engine; |
| fml::AutoResetWaitableEvent task_latch; |
| bool task_executed = false; |
| EmbedderTestTaskRunner render_task_runner( |
| CreateNewThread("custom_render_thread"), [&](FlutterTask task) { |
| std::scoped_lock engine_lock(engine_mutex); |
| if (engine.is_valid()) { |
| ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); |
| task_executed = true; |
| task_latch.Signal(); |
| } |
| }); |
| EmbedderConfigBuilder builder( |
| GetEmbedderContext(EmbedderTestContextType::kOpenGLContext)); |
| builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetRenderTaskRunner( |
| &render_task_runner.GetFlutterTaskRunnerDescription()); |
| |
| { |
| std::scoped_lock lock(engine_mutex); |
| engine = builder.InitializeEngine(); |
| } |
| |
| ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| task_latch.Wait(); |
| ASSERT_TRUE(task_executed); |
| ASSERT_EQ(FlutterEngineDeinitialize(engine.get()), kSuccess); |
| |
| { |
| std::scoped_lock engine_lock(engine_mutex); |
| engine.reset(); |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| /// Asserts that the render task runner can be the same as the platform task |
| /// runner. |
| /// |
| TEST_P(EmbedderTestMultiBackend, |
| CanCreateEmbedderWithCustomRenderTaskRunnerTheSameAsPlatformTaskRunner) { |
| // A new thread needs to be created for the platform thread because the test |
| // can't wait for assertions to be completed on the same thread that services |
| // platform task runner tasks. |
| auto platform_task_runner = CreateNewThread("platform_thread"); |
| |
| static std::mutex engine_mutex; |
| static UniqueEngine engine; |
| fml::AutoResetWaitableEvent task_latch; |
| bool task_executed = false; |
| EmbedderTestTaskRunner common_task_runner( |
| platform_task_runner, [&](FlutterTask task) { |
| std::scoped_lock engine_lock(engine_mutex); |
| if (engine.is_valid()) { |
| ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); |
| task_executed = true; |
| task_latch.Signal(); |
| } |
| }); |
| |
| platform_task_runner->PostTask([&]() { |
| EmbedderTestContextType backend = GetParam(); |
| EmbedderConfigBuilder builder(GetEmbedderContext(backend)); |
| builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); |
| builder.SetRendererConfig(backend, SkISize::Make(800, 600)); |
| builder.SetRenderTaskRunner( |
| &common_task_runner.GetFlutterTaskRunnerDescription()); |
| builder.SetPlatformTaskRunner( |
| &common_task_runner.GetFlutterTaskRunnerDescription()); |
| |
| { |
| std::scoped_lock lock(engine_mutex); |
| engine = builder.InitializeEngine(); |
| } |
| |
| ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| }); |
| |
| task_latch.Wait(); |
| |
| // Don't use the task latch because that may be called multiple time |
| // (including during the shutdown process). |
| fml::AutoResetWaitableEvent shutdown_latch; |
| |
| platform_task_runner->PostTask([&]() { |
| ASSERT_TRUE(task_executed); |
| ASSERT_EQ(FlutterEngineDeinitialize(engine.get()), kSuccess); |
| |
| { |
| std::scoped_lock engine_lock(engine_mutex); |
| engine.reset(); |
| } |
| shutdown_latch.Signal(); |
| }); |
| |
| shutdown_latch.Wait(); |
| |
| { |
| std::scoped_lock engine_lock(engine_mutex); |
| // Engine should have been killed by this point. |
| ASSERT_FALSE(engine.is_valid()); |
| } |
| } |
| |
| TEST_P(EmbedderTestMultiBackend, |
| CompositorMustBeAbleToRenderKnownScenePixelRatioOnSurface) { |
| EmbedderTestContextType backend = GetParam(); |
| auto& context = GetEmbedderContext(backend); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetRendererConfig(backend, SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_display_platform_view_with_pixel_ratio"); |
| |
| builder.SetRenderTargetType(GetRenderTargetFromBackend(backend, false)); |
| |
| fml::CountDownLatch latch(1); |
| |
| auto rendered_scene = context.GetNextSceneImage(); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| // Layer 0 (Root) |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.did_update = true; |
| ConfigureBackingStore(backing_store, backend, false); |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(800.0, 560.0); |
| layer.offset = FlutterPointMake(0.0, 40.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.did_update = true; |
| ConfigureBackingStore(backing_store, backend, false); |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 400 * 2.0; |
| event.height = 300 * 2.0; |
| event.pixel_ratio = 2.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE(ImageMatchesFixture( |
| FixtureNameForBackend(backend, "dpr_noxform.png"), rendered_scene)); |
| } |
| |
| TEST_F( |
| EmbedderTest, |
| CompositorMustBeAbleToRenderKnownScenePixelRatioOnSurfaceWithRootSurfaceXformation) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("can_display_platform_view_with_pixel_ratio"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| auto rendered_scene = context.GetNextSceneImage(); |
| fml::CountDownLatch latch(1); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 3u); |
| |
| // Layer 0 (Root) |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(560.0, 800.0); |
| layer.offset = FlutterPointMake(40.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterBackingStore backing_store = *layers[2]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[2], layer); |
| } |
| |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 400 * 2.0; |
| event.height = 300 * 2.0; |
| event.pixel_ratio = 2.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| |
| ASSERT_TRUE(ImageMatchesFixture("dpr_xform.png", rendered_scene)); |
| } |
| |
| TEST_F(EmbedderTest, |
| PushingMutlipleFramesSetsUpNewRecordingCanvasWithCustomCompositor) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("push_frames_over_and_over"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 1024).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 1024; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| constexpr size_t frames_expected = 10; |
| fml::CountDownLatch frame_latch(frames_expected); |
| std::atomic_size_t frames_seen = 0; |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| frames_seen++; |
| frame_latch.CountDown(); |
| })); |
| frame_latch.Wait(); |
| |
| ASSERT_GE(frames_seen, frames_expected); |
| |
| FlutterEngineShutdown(engine.release()); |
| } |
| |
| TEST_F(EmbedderTest, |
| PushingMutlipleFramesSetsUpNewRecordingCanvasWithoutCustomCompositor) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); |
| builder.SetDartEntrypoint("push_frames_over_and_over"); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 1024).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 1024; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| constexpr size_t frames_expected = 10; |
| fml::CountDownLatch frame_latch(frames_expected); |
| std::atomic_size_t frames_seen = 0; |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| frames_seen++; |
| frame_latch.CountDown(); |
| })); |
| frame_latch.Wait(); |
| |
| ASSERT_GE(frames_seen, frames_expected); |
| |
| FlutterEngineShutdown(engine.release()); |
| } |
| |
| TEST_P(EmbedderTestMultiBackend, PlatformViewMutatorsAreValid) { |
| EmbedderTestContextType backend = GetParam(); |
| auto& context = GetEmbedderContext(backend); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetRendererConfig(backend, SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("platform_view_mutators"); |
| |
| builder.SetRenderTargetType(GetRenderTargetFromBackend(backend, false)); |
| |
| fml::CountDownLatch latch(1); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| // Layer 0 (Root) |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.did_update = true; |
| ConfigureBackingStore(backing_store, backend, false); |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| platform_view.mutations_count = 3; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| |
| // There are no ordering guarantees. |
| for (size_t i = 0; i < platform_view.mutations_count; i++) { |
| FlutterPlatformViewMutation mutation = *platform_view.mutations[i]; |
| switch (mutation.type) { |
| case kFlutterPlatformViewMutationTypeClipRoundedRect: |
| mutation.clip_rounded_rect = |
| FlutterRoundedRectMake(SkRRect::MakeRectXY( |
| SkRect::MakeLTRB(10.0, 10.0, 800.0 - 10.0, |
| 600.0 - 10.0), |
| 14.0, 14.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeClipRect: |
| mutation.type = kFlutterPlatformViewMutationTypeClipRect; |
| mutation.clip_rect = FlutterRectMake( |
| SkRect::MakeXYWH(10.0, 10.0, 800.0 - 20.0, 600.0 - 20.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeOpacity: |
| mutation.type = kFlutterPlatformViewMutationTypeOpacity; |
| mutation.opacity = 128.0 / 255.0; |
| break; |
| case kFlutterPlatformViewMutationTypeTransformation: |
| FML_CHECK(false) |
| << "There should be no transformation in the test."; |
| break; |
| } |
| |
| ASSERT_EQ(*platform_view.mutations[i], mutation); |
| } |
| } |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, PlatformViewMutatorsAreValidWithPixelRatio) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("platform_view_mutators_with_pixel_ratio"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(1); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| // Layer 0 (Root) |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| platform_view.mutations_count = 3; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(800.0, 600.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| |
| // There are no ordering guarantees. |
| for (size_t i = 0; i < platform_view.mutations_count; i++) { |
| FlutterPlatformViewMutation mutation = *platform_view.mutations[i]; |
| switch (mutation.type) { |
| case kFlutterPlatformViewMutationTypeClipRoundedRect: |
| mutation.clip_rounded_rect = |
| FlutterRoundedRectMake(SkRRect::MakeRectXY( |
| SkRect::MakeLTRB(5.0, 5.0, 400.0 - 5.0, 300.0 - 5.0), |
| 7.0, 7.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeClipRect: |
| mutation.type = kFlutterPlatformViewMutationTypeClipRect; |
| mutation.clip_rect = FlutterRectMake( |
| SkRect::MakeXYWH(5.0, 5.0, 400.0 - 10.0, 300.0 - 10.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeOpacity: |
| mutation.type = kFlutterPlatformViewMutationTypeOpacity; |
| mutation.opacity = 128.0 / 255.0; |
| break; |
| case kFlutterPlatformViewMutationTypeTransformation: |
| mutation.type = kFlutterPlatformViewMutationTypeTransformation; |
| mutation.transformation = |
| FlutterTransformationMake(SkMatrix::Scale(2.0, 2.0)); |
| break; |
| } |
| |
| ASSERT_EQ(*platform_view.mutations[i], mutation); |
| } |
| } |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 2.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, |
| PlatformViewMutatorsAreValidWithPixelRatioAndRootSurfaceTransformation) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("platform_view_mutators_with_pixel_ratio"); |
| |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| static const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| fml::CountDownLatch latch(1); |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| // Layer 0 (Root) |
| { |
| FlutterBackingStore backing_store = *layers[0]->backing_store; |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.did_update = true; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypeBackingStore; |
| layer.backing_store = &backing_store; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 2 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| platform_view.mutations_count = 4; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(600.0, 800.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| |
| // There are no ordering guarantees. |
| for (size_t i = 0; i < platform_view.mutations_count; i++) { |
| FlutterPlatformViewMutation mutation = *platform_view.mutations[i]; |
| switch (mutation.type) { |
| case kFlutterPlatformViewMutationTypeClipRoundedRect: |
| mutation.clip_rounded_rect = |
| FlutterRoundedRectMake(SkRRect::MakeRectXY( |
| SkRect::MakeLTRB(5.0, 5.0, 400.0 - 5.0, 300.0 - 5.0), |
| 7.0, 7.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeClipRect: |
| mutation.type = kFlutterPlatformViewMutationTypeClipRect; |
| mutation.clip_rect = FlutterRectMake( |
| SkRect::MakeXYWH(5.0, 5.0, 400.0 - 10.0, 300.0 - 10.0)); |
| break; |
| case kFlutterPlatformViewMutationTypeOpacity: |
| mutation.type = kFlutterPlatformViewMutationTypeOpacity; |
| mutation.opacity = 128.0 / 255.0; |
| break; |
| case kFlutterPlatformViewMutationTypeTransformation: |
| mutation.type = kFlutterPlatformViewMutationTypeTransformation; |
| mutation.transformation = |
| FlutterTransformationMake(root_surface_transformation); |
| |
| break; |
| } |
| |
| ASSERT_EQ(*platform_view.mutations[i], mutation); |
| } |
| } |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 2.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, EmptySceneIsAcceptable) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("empty_scene"); |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, SceneWithNoRootContainerIsAcceptable) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); |
| builder.SetDartEntrypoint("scene_with_no_container"); |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| latch.Wait(); |
| } |
| |
| // Verifies that https://skia-review.googlesource.com/c/skia/+/259174 is pulled |
| // into the engine. |
| TEST_F(EmbedderTest, ArcEndCapsAreDrawnCorrectly) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 1024)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("arc_end_caps_correct"); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); |
| |
| const auto root_surface_transformation = SkMatrix() |
| .preScale(1.0, -1.0) |
| .preTranslate(1024.0, -800.0) |
| .preRotate(90.0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| auto scene_image = context.GetNextSceneImage(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 1024; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| ASSERT_TRUE(ImageMatchesFixture("arc_end_caps.png", scene_image)); |
| } |
| |
| TEST_F(EmbedderTest, ClipsAreCorrectlyCalculated) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(400, 300)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("scene_builder_with_clips"); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 400).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| fml::AutoResetWaitableEvent latch; |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| { |
| FlutterPlatformView platform_view = *layers[0]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(300.0, 400.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| |
| bool clip_assertions_checked = false; |
| |
| // The total transformation on the stack upto the platform view. |
| const auto total_xformation = |
| GetTotalMutationTransformationMatrix(layers[0]->platform_view); |
| |
| FilterMutationsByType( |
| layers[0]->platform_view, |
| kFlutterPlatformViewMutationTypeClipRect, |
| [&](const auto& mutation) { |
| FlutterRect clip = mutation.clip_rect; |
| |
| // The test is only set up to supply one clip. Make sure it is |
| // the one we expect. |
| const auto rect_to_compare = |
| SkRect::MakeLTRB(10.0, 10.0, 390, 290); |
| ASSERT_EQ(clip, FlutterRectMake(rect_to_compare)); |
| |
| // This maps the clip from device space into surface space. |
| SkRect mapped; |
| ASSERT_TRUE(total_xformation.mapRect(&mapped, rect_to_compare)); |
| ASSERT_EQ(mapped, SkRect::MakeLTRB(10, 10, 290, 390)); |
| clip_assertions_checked = true; |
| }); |
| |
| ASSERT_TRUE(clip_assertions_checked); |
| } |
| |
| latch.Signal(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 400; |
| event.height = 300; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, ComplexClipsAreCorrectlyCalculated) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(1024, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("scene_builder_with_complex_clips"); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 1024).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| fml::AutoResetWaitableEvent latch; |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| { |
| FlutterPlatformView platform_view = *layers[0]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(600.0, 1024.0); |
| layer.offset = FlutterPointMake(0.0, -256.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| |
| const auto** mutations = platform_view.mutations; |
| |
| ASSERT_EQ(mutations[0]->type, |
| kFlutterPlatformViewMutationTypeTransformation); |
| ASSERT_EQ(SkMatrixMake(mutations[0]->transformation), |
| root_surface_transformation); |
| |
| ASSERT_EQ(mutations[1]->type, |
| kFlutterPlatformViewMutationTypeClipRect); |
| ASSERT_EQ(SkRectMake(mutations[1]->clip_rect), |
| SkRect::MakeLTRB(0.0, 0.0, 1024.0, 600.0)); |
| |
| ASSERT_EQ(mutations[2]->type, |
| kFlutterPlatformViewMutationTypeTransformation); |
| ASSERT_EQ(SkMatrixMake(mutations[2]->transformation), |
| SkMatrix::Translate(512.0, 0.0)); |
| |
| ASSERT_EQ(mutations[3]->type, |
| kFlutterPlatformViewMutationTypeClipRect); |
| ASSERT_EQ(SkRectMake(mutations[3]->clip_rect), |
| SkRect::MakeLTRB(0.0, 0.0, 512.0, 600.0)); |
| |
| ASSERT_EQ(mutations[4]->type, |
| kFlutterPlatformViewMutationTypeTransformation); |
| ASSERT_EQ(SkMatrixMake(mutations[4]->transformation), |
| SkMatrix::Translate(-256.0, 0.0)); |
| |
| ASSERT_EQ(mutations[5]->type, |
| kFlutterPlatformViewMutationTypeClipRect); |
| ASSERT_EQ(SkRectMake(mutations[5]->clip_rect), |
| SkRect::MakeLTRB(0.0, 0.0, 1024.0, 600.0)); |
| } |
| |
| latch.Signal(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 1024; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, ObjectsCanBePostedViaPorts) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 1024)); |
| builder.SetDartEntrypoint("objects_can_be_posted"); |
| |
| // Synchronously acquire the send port from the Dart end. We will be using |
| // this to send message. The Dart end will just echo those messages back to us |
| // for inspection. |
| FlutterEngineDartPort port = 0; |
| fml::AutoResetWaitableEvent event; |
| context.AddNativeCallback("SignalNativeCount", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| port = tonic::DartConverter<int64_t>::FromDart( |
| Dart_GetNativeArgument(args, 0)); |
| event.Signal(); |
| })); |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| event.Wait(); |
| ASSERT_NE(port, 0); |
| |
| using Trampoline = std::function<void(Dart_Handle message)>; |
| Trampoline trampoline; |
| |
| context.AddNativeCallback("SendObjectToNativeCode", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| FML_CHECK(trampoline); |
| auto trampoline_copy = trampoline; |
| trampoline = nullptr; |
| trampoline_copy(Dart_GetNativeArgument(args, 0)); |
| })); |
| |
| // Check null. |
| { |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeNull; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_TRUE(Dart_IsNull(handle)); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check bool. |
| { |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeBool; |
| object.bool_value = true; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_TRUE(tonic::DartConverter<bool>::FromDart(handle)); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check int32. |
| { |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeInt32; |
| object.int32_value = 1988; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_EQ(tonic::DartConverter<int32_t>::FromDart(handle), 1988); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check int64. |
| { |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeInt64; |
| object.int64_value = 1988; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_EQ(tonic::DartConverter<int64_t>::FromDart(handle), 1988); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check double. |
| { |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeDouble; |
| object.double_value = 1988.0; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_DOUBLE_EQ(tonic::DartConverter<double>::FromDart(handle), 1988.0); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check string. |
| { |
| const char* message = "Hello. My name is Inigo Montoya."; |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeString; |
| object.string_value = message; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_EQ(tonic::DartConverter<std::string>::FromDart(handle), |
| std::string{message}); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| // Check buffer (copied out). |
| { |
| std::vector<uint8_t> message; |
| message.resize(1988); |
| |
| ASSERT_TRUE(MemsetPatternSetOrCheck( |
| message, MemsetPatternOp::kMemsetPatternOpSetBuffer)); |
| |
| FlutterEngineDartBuffer buffer = {}; |
| |
| buffer.struct_size = sizeof(buffer); |
| buffer.user_data = nullptr; |
| buffer.buffer_collect_callback = nullptr; |
| buffer.buffer = message.data(); |
| buffer.buffer_size = message.size(); |
| |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeBuffer; |
| object.buffer_value = &buffer; |
| trampoline = [&](Dart_Handle handle) { |
| intptr_t length = 0; |
| Dart_ListLength(handle, &length); |
| ASSERT_EQ(length, 1988); |
| // TODO(chinmaygarde); The std::vector<uint8_t> specialization for |
| // DartConvertor in tonic is broken which is preventing the buffer |
| // being checked here. Fix tonic and strengthen this check. For now, just |
| // the buffer length is checked. |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| std::vector<uint8_t> message; |
| fml::AutoResetWaitableEvent buffer_released_latch; |
| |
| // Check buffer (caller own buffer with zero copy transfer). |
| { |
| message.resize(1988); |
| |
| ASSERT_TRUE(MemsetPatternSetOrCheck( |
| message, MemsetPatternOp::kMemsetPatternOpSetBuffer)); |
| |
| FlutterEngineDartBuffer buffer = {}; |
| |
| buffer.struct_size = sizeof(buffer); |
| buffer.user_data = &buffer_released_latch; |
| buffer.buffer_collect_callback = +[](void* user_data) { |
| reinterpret_cast<fml::AutoResetWaitableEvent*>(user_data)->Signal(); |
| }; |
| buffer.buffer = message.data(); |
| buffer.buffer_size = message.size(); |
| |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeBuffer; |
| object.buffer_value = &buffer; |
| trampoline = [&](Dart_Handle handle) { |
| intptr_t length = 0; |
| Dart_ListLength(handle, &length); |
| ASSERT_EQ(length, 1988); |
| // TODO(chinmaygarde); The std::vector<uint8_t> specialization for |
| // DartConvertor in tonic is broken which is preventing the buffer |
| // being checked here. Fix tonic and strengthen this check. For now, just |
| // the buffer length is checked. |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| |
| engine.reset(); |
| |
| // We cannot determine when the VM will GC objects that might have external |
| // typed data finalizers. Since we need to ensure that we correctly wired up |
| // finalizers from the embedders, we force the VM to collect all objects but |
| // just shutting it down. |
| buffer_released_latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, CompositorCanPostZeroLayersForPresentation) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("empty_scene_posts_zero_layers_to_compositor"); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| fml::AutoResetWaitableEvent latch; |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 0u); |
| latch.Signal(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 300; |
| event.height = 200; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| latch.Wait(); |
| |
| ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 0u); |
| } |
| |
| TEST_F(EmbedderTest, CompositorCanPostOnlyPlatformViews) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("compositor_can_post_only_platform_views"); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| fml::AutoResetWaitableEvent latch; |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 2u); |
| |
| // Layer 0 |
| { |
| FlutterPlatformView platform_view = *layers[0]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 42; |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(300.0, 200.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[0], layer); |
| } |
| |
| // Layer 1 |
| { |
| FlutterPlatformView platform_view = *layers[1]->platform_view; |
| platform_view.struct_size = sizeof(platform_view); |
| platform_view.identifier = 24; |
| FlutterLayer layer = {}; |
| layer.struct_size = sizeof(layer); |
| layer.type = kFlutterLayerContentTypePlatformView; |
| layer.platform_view = &platform_view; |
| layer.size = FlutterSizeMake(300.0, 200.0); |
| layer.offset = FlutterPointMake(0.0, 0.0); |
| |
| ASSERT_EQ(*layers[1], layer); |
| } |
| latch.Signal(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 300; |
| event.height = 200; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| latch.Wait(); |
| |
| ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 0u); |
| } |
| |
| TEST_F(EmbedderTest, CompositorRenderTargetsAreRecycled) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("render_targets_are_recycled"); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(2); |
| |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| latch.CountDown(); |
| })); |
| |
| context.GetCompositor().SetNextPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 20u); |
| latch.CountDown(); |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 300; |
| event.height = 200; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 10u); |
| ASSERT_EQ(context.GetCompositor().GetBackingStoresCreatedCount(), 10u); |
| ASSERT_EQ(context.GetCompositor().GetBackingStoresCollectedCount(), 0u); |
| // Killing the engine should immediately collect all pending render targets. |
| engine.reset(); |
| ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 0u); |
| ASSERT_EQ(context.GetCompositor().GetBackingStoresCreatedCount(), 10u); |
| ASSERT_EQ(context.GetCompositor().GetBackingStoresCollectedCount(), 10u); |
| } |
| |
| TEST_F(EmbedderTest, CompositorRenderTargetsAreInStableOrder) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("render_targets_are_in_stable_order"); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| fml::CountDownLatch latch(2); |
| |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| latch.CountDown(); |
| })); |
| |
| size_t frame_count = 0; |
| std::vector<void*> first_frame_backing_store_user_data; |
| context.GetCompositor().SetPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 20u); |
| |
| if (first_frame_backing_store_user_data.empty()) { |
| for (size_t i = 0; i < layers_count; ++i) { |
| if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { |
| first_frame_backing_store_user_data.push_back( |
| layers[i]->backing_store->user_data); |
| } |
| } |
| return; |
| } |
| |
| ASSERT_EQ(first_frame_backing_store_user_data.size(), 10u); |
| |
| frame_count++; |
| std::vector<void*> backing_store_user_data; |
| for (size_t i = 0; i < layers_count; ++i) { |
| if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { |
| backing_store_user_data.push_back( |
| layers[i]->backing_store->user_data); |
| } |
| } |
| |
| ASSERT_EQ(backing_store_user_data.size(), 10u); |
| |
| ASSERT_EQ(first_frame_backing_store_user_data, backing_store_user_data); |
| |
| if (frame_count == 20) { |
| latch.CountDown(); |
| } |
| }, |
| false // one shot |
| ); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 300; |
| event.height = 200; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, FrameInfoContainsValidWidthAndHeight) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); |
| builder.SetDartEntrypoint("push_frames_over_and_over"); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 1024).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| static FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 1024; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| static fml::CountDownLatch frame_latch(10); |
| |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| /* Nothing to do. */ |
| })); |
| |
| static_cast<EmbedderTestContextGL&>(context).SetGLGetFBOCallback( |
| [](FlutterFrameInfo frame_info) { |
| // width and height are rotated by 90 deg |
| ASSERT_EQ(frame_info.size.width, event.height); |
| ASSERT_EQ(frame_info.size.height, event.width); |
| |
| frame_latch.CountDown(); |
| }); |
| |
| frame_latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, MustNotRunWithBothFBOCallbacksSet) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); |
| builder.SetOpenGLFBOCallBack(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_FALSE(engine.is_valid()); |
| } |
| |
| TEST_F(EmbedderTest, MustNotRunWithBothPresentCallbacksSet) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); |
| builder.SetOpenGLPresentCallBack(); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_FALSE(engine.is_valid()); |
| } |
| |
| TEST_F(EmbedderTest, MustStillRunWhenPopulateExistingDamageIsNotProvided) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); |
| builder.GetRendererConfig().open_gl.populate_existing_damage = nullptr; |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| } |
| |
| TEST_F(EmbedderTest, MustRunWhenPopulateExistingDamageIsProvided) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); |
| |
| builder.GetRendererConfig().open_gl.populate_existing_damage = |
| [](void* context, const intptr_t id, |
| FlutterDamage* existing_damage) -> void { |
| return reinterpret_cast<EmbedderTestContextGL*>(context) |
| ->GLPopulateExistingDamage(id, existing_damage); |
| }; |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| } |
| |
| TEST_F(EmbedderTest, MustRunWithPopulateExistingDamageAndFBOCallback) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); |
| builder.GetRendererConfig().open_gl.fbo_callback = |
| [](void* context) -> uint32_t { return 0; }; |
| builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = nullptr; |
| builder.GetRendererConfig().open_gl.populate_existing_damage = |
| [](void* context, const intptr_t id, |
| FlutterDamage* existing_damage) -> void { |
| return reinterpret_cast<EmbedderTestContextGL*>(context) |
| ->GLPopulateExistingDamage(id, existing_damage); |
| }; |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| } |
| |
| TEST_F(EmbedderTest, |
| MustNotRunWhenPopulateExistingDamageButNoOtherFBOCallback) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); |
| builder.GetRendererConfig().open_gl.fbo_callback = nullptr; |
| builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = nullptr; |
| builder.GetRendererConfig().open_gl.populate_existing_damage = |
| [](void* context, const intptr_t id, |
| FlutterDamage* existing_damage) -> void { |
| return reinterpret_cast<EmbedderTestContextGL*>(context) |
| ->GLPopulateExistingDamage(id, existing_damage); |
| }; |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_FALSE(engine.is_valid()); |
| } |
| |
| TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); |
| builder.SetDartEntrypoint("push_frames_over_and_over"); |
| |
| const auto root_surface_transformation = |
| SkMatrix().preTranslate(0, 1024).preRotate(-90, 0, 0); |
| |
| context.SetRootSurfaceTransformation(root_surface_transformation); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 1024; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| static fml::CountDownLatch frame_latch(10); |
| |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| /* Nothing to do. */ |
| })); |
| |
| const uint32_t window_fbo_id = |
| static_cast<EmbedderTestContextGL&>(context).GetWindowFBOId(); |
| static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback( |
| [window_fbo_id = window_fbo_id](FlutterPresentInfo present_info) { |
| ASSERT_EQ(present_info.fbo_id, window_fbo_id); |
| |
| frame_latch.CountDown(); |
| }); |
| |
| frame_latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, |
| PresentInfoReceivesFullDamageWhenExistingDamageIsWholeScreen) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.GetRendererConfig().open_gl.populate_existing_damage = |
| [](void* context, const intptr_t id, |
| FlutterDamage* existing_damage) -> void { |
| return reinterpret_cast<EmbedderTestContextGL*>(context) |
| ->GLPopulateExistingDamage(id, existing_damage); |
| }; |
| |
| // Return existing damage as the entire screen on purpose. |
| static_cast<EmbedderTestContextGL&>(context) |
| .SetGLPopulateExistingDamageCallback( |
| [](const intptr_t id, FlutterDamage* existing_damage_ptr) { |
| const size_t num_rects = 1; |
| FlutterRect existing_damage_rects[num_rects] = { |
| FlutterRect{0, 0, 800, 600}}; |
| existing_damage_ptr->num_rects = num_rects; |
| existing_damage_ptr->damage = existing_damage_rects; |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // First frame should be entirely rerendered. |
| static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback( |
| [](FlutterPresentInfo present_info) { |
| const size_t num_rects = 1; |
| ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.frame_damage.damage->left, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->top, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->right, 800); |
| ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); |
| |
| ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.buffer_damage.damage->left, 0); |
| ASSERT_EQ(present_info.buffer_damage.damage->top, 0); |
| ASSERT_EQ(present_info.buffer_damage.damage->right, 800); |
| ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); |
| }); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| // Because it's the same as the first frame, the second frame damage should |
| // be empty but, because there was a full existing buffer damage, the buffer |
| // damage should be the entire screen. |
| static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback( |
| [](FlutterPresentInfo present_info) { |
| const size_t num_rects = 1; |
| ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.frame_damage.damage->left, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->top, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->right, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); |
| |
| ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.buffer_damage.damage->left, 0); |
| ASSERT_EQ(present_info.buffer_damage.damage->top, 0); |
| ASSERT_EQ(present_info.buffer_damage.damage->right, 800); |
| ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); |
| }); |
| |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| } |
| |
| TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.GetRendererConfig().open_gl.populate_existing_damage = |
| [](void* context, const intptr_t id, |
| FlutterDamage* existing_damage) -> void { |
| return reinterpret_cast<EmbedderTestContextGL*>(context) |
| ->GLPopulateExistingDamage(id, existing_damage); |
| }; |
| |
| // Return no existing damage on purpose. |
| static_cast<EmbedderTestContextGL&>(context) |
| .SetGLPopulateExistingDamageCallback( |
| [](const intptr_t id, FlutterDamage* existing_damage_ptr) { |
| const size_t num_rects = 1; |
| FlutterRect existing_damage_rects[num_rects] = { |
| FlutterRect{0, 0, 0, 0}}; |
| existing_damage_ptr->num_rects = num_rects; |
| existing_damage_ptr->damage = existing_damage_rects; |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // First frame should be entirely rerendered. |
| static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback( |
| [](FlutterPresentInfo present_info) { |
| const size_t num_rects = 1; |
| ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.frame_damage.damage->left, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->top, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->right, 800); |
| ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); |
| |
| ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.buffer_damage.damage->left, 0); |
| ASSERT_EQ(present_info.buffer_damage.damage->top, 0); |
| ASSERT_EQ(present_info.buffer_damage.damage->right, 800); |
| ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); |
| }); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| // Because it's the same as the first frame, the second frame should not be |
| // rerendered assuming there is no existing damage. |
| static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback( |
| [](FlutterPresentInfo present_info) { |
| const size_t num_rects = 1; |
| ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.frame_damage.damage->left, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->top, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->right, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); |
| |
| ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.buffer_damage.damage->left, 0); |
| ASSERT_EQ(present_info.buffer_damage.damage->top, 0); |
| ASSERT_EQ(present_info.buffer_damage.damage->right, 0); |
| ASSERT_EQ(present_info.buffer_damage.damage->bottom, 0); |
| }); |
| |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| } |
| |
| TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.GetRendererConfig().open_gl.populate_existing_damage = |
| [](void* context, const intptr_t id, |
| FlutterDamage* existing_damage) -> void { |
| return reinterpret_cast<EmbedderTestContextGL*>(context) |
| ->GLPopulateExistingDamage(id, existing_damage); |
| }; |
| |
| // Return existing damage as only part of the screen on purpose. |
| static_cast<EmbedderTestContextGL&>(context) |
| .SetGLPopulateExistingDamageCallback( |
| [](const intptr_t id, FlutterDamage* existing_damage_ptr) { |
| const size_t num_rects = 1; |
| FlutterRect existing_damage_rects[num_rects] = { |
| FlutterRect{200, 150, 400, 300}}; |
| existing_damage_ptr->num_rects = num_rects; |
| existing_damage_ptr->damage = existing_damage_rects; |
| }); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // First frame should be entirely rerendered. |
| static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback( |
| [](FlutterPresentInfo present_info) { |
| const size_t num_rects = 1; |
| ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.frame_damage.damage->left, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->top, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->right, 800); |
| ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); |
| |
| ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.buffer_damage.damage->left, 0); |
| ASSERT_EQ(present_info.buffer_damage.damage->top, 0); |
| ASSERT_EQ(present_info.buffer_damage.damage->right, 800); |
| ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); |
| }); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| // Because it's the same as the first frame, the second frame damage should be |
| // empty but, because there was a partial existing damage, the buffer damage |
| // should represent that partial damage area. |
| static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback( |
| [](FlutterPresentInfo present_info) { |
| const size_t num_rects = 1; |
| ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.frame_damage.damage->left, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->top, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->right, 0); |
| ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); |
| |
| ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); |
| ASSERT_EQ(present_info.buffer_damage.damage->left, 200); |
| ASSERT_EQ(present_info.buffer_damage.damage->top, 150); |
| ASSERT_EQ(present_info.buffer_damage.damage->right, 400); |
| ASSERT_EQ(present_info.buffer_damage.damage->bottom, 300); |
| }); |
| |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| } |
| |
| TEST_F(EmbedderTest, PopulateExistingDamageReceivesValidID) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.GetRendererConfig().open_gl.populate_existing_damage = |
| [](void* context, const intptr_t id, |
| FlutterDamage* existing_damage) -> void { |
| return reinterpret_cast<EmbedderTestContextGL*>(context) |
| ->GLPopulateExistingDamage(id, existing_damage); |
| }; |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| const uint32_t window_fbo_id = |
| static_cast<EmbedderTestContextGL&>(context).GetWindowFBOId(); |
| static_cast<EmbedderTestContextGL&>(context) |
| .SetGLPopulateExistingDamageCallback( |
| [window_fbo_id = window_fbo_id](intptr_t id, |
| FlutterDamage* existing_damage) { |
| ASSERT_EQ(id, window_fbo_id); |
| }); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| } |
| |
| TEST_F(EmbedderTest, PopulateExistingDamageReceivesInvalidID) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.GetRendererConfig().open_gl.populate_existing_damage = |
| [](void* context, const intptr_t id, |
| FlutterDamage* existing_damage) -> void { |
| return reinterpret_cast<EmbedderTestContextGL*>(context) |
| ->GLPopulateExistingDamage(id, existing_damage); |
| }; |
| |
| // Return a bad FBO ID on purpose. |
| builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = |
| [](void* context, const FlutterFrameInfo* frame_info) -> uint32_t { |
| return 123; |
| }; |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| /* Nothing to do. */ |
| })); |
| |
| const uint32_t window_fbo_id = |
| static_cast<EmbedderTestContextGL&>(context).GetWindowFBOId(); |
| static_cast<EmbedderTestContextGL&>(context) |
| .SetGLPopulateExistingDamageCallback( |
| [window_fbo_id = window_fbo_id](intptr_t id, |
| FlutterDamage* existing_damage) { |
| ASSERT_NE(id, window_fbo_id); |
| }); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| } |
| |
| TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithDisplayId) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("empty_scene"); |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterEngineDisplay display; |
| display.struct_size = sizeof(FlutterEngineDisplay); |
| display.display_id = 1; |
| display.refresh_rate = 20; |
| |
| std::vector<FlutterEngineDisplay> displays = {display}; |
| |
| const FlutterEngineResult result = FlutterEngineNotifyDisplayUpdate( |
| engine.get(), kFlutterEngineDisplaysUpdateTypeStartup, displays.data(), |
| displays.size()); |
| ASSERT_EQ(result, kSuccess); |
| |
| flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); |
| ASSERT_EQ(shell.GetMainDisplayRefreshRate(), display.refresh_rate); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithoutDisplayId) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("empty_scene"); |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterEngineDisplay display; |
| display.struct_size = sizeof(FlutterEngineDisplay); |
| display.single_display = true; |
| display.refresh_rate = 20; |
| |
| std::vector<FlutterEngineDisplay> displays = {display}; |
| |
| const FlutterEngineResult result = FlutterEngineNotifyDisplayUpdate( |
| engine.get(), kFlutterEngineDisplaysUpdateTypeStartup, displays.data(), |
| displays.size()); |
| ASSERT_EQ(result, kSuccess); |
| |
| flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); |
| ASSERT_EQ(shell.GetMainDisplayRefreshRate(), display.refresh_rate); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, SetValidMultiDisplayConfiguration) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("empty_scene"); |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterEngineDisplay display_1; |
| display_1.struct_size = sizeof(FlutterEngineDisplay); |
| display_1.display_id = 1; |
| display_1.single_display = false; |
| display_1.refresh_rate = 20; |
| |
| FlutterEngineDisplay display_2; |
| display_2.struct_size = sizeof(FlutterEngineDisplay); |
| display_2.display_id = 2; |
| display_2.single_display = false; |
| display_2.refresh_rate = 60; |
| |
| std::vector<FlutterEngineDisplay> displays = {display_1, display_2}; |
| |
| const FlutterEngineResult result = FlutterEngineNotifyDisplayUpdate( |
| engine.get(), kFlutterEngineDisplaysUpdateTypeStartup, displays.data(), |
| displays.size()); |
| ASSERT_EQ(result, kSuccess); |
| |
| flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); |
| ASSERT_EQ(shell.GetMainDisplayRefreshRate(), display_1.refresh_rate); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, MultipleDisplaysWithSingleDisplayTrueIsInvalid) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("empty_scene"); |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterEngineDisplay display_1; |
| display_1.struct_size = sizeof(FlutterEngineDisplay); |
| display_1.display_id = 1; |
| display_1.single_display = true; |
| display_1.refresh_rate = 20; |
| |
| FlutterEngineDisplay display_2; |
| display_2.struct_size = sizeof(FlutterEngineDisplay); |
| display_2.display_id = 2; |
| display_2.single_display = true; |
| display_2.refresh_rate = 60; |
| |
| std::vector<FlutterEngineDisplay> displays = {display_1, display_2}; |
| |
| const FlutterEngineResult result = FlutterEngineNotifyDisplayUpdate( |
| engine.get(), kFlutterEngineDisplaysUpdateTypeStartup, displays.data(), |
| displays.size()); |
| ASSERT_NE(result, kSuccess); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, MultipleDisplaysWithSameDisplayIdIsInvalid) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetDartEntrypoint("empty_scene"); |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterEngineDisplay display_1; |
| display_1.struct_size = sizeof(FlutterEngineDisplay); |
| display_1.display_id = 1; |
| display_1.single_display = false; |
| display_1.refresh_rate = 20; |
| |
| FlutterEngineDisplay display_2; |
| display_2.struct_size = sizeof(FlutterEngineDisplay); |
| display_2.display_id = 1; |
| display_2.single_display = false; |
| display_2.refresh_rate = 60; |
| |
| std::vector<FlutterEngineDisplay> displays = {display_1, display_2}; |
| |
| const FlutterEngineResult result = FlutterEngineNotifyDisplayUpdate( |
| engine.get(), kFlutterEngineDisplaysUpdateTypeStartup, displays.data(), |
| displays.size()); |
| ASSERT_NE(result, kSuccess); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, CompositorRenderTargetsNotRecycledWhenAvoidsCacheSet) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); |
| builder.SetCompositor(/*avoid_backing_store_cache=*/true); |
| builder.SetDartEntrypoint("render_targets_are_recycled"); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| |
| const unsigned num_frames = 8; |
| const unsigned num_engine_layers = 10; |
| const unsigned num_backing_stores = num_frames * num_engine_layers; |
| fml::CountDownLatch latch(1 + num_frames); // 1 for native test signal. |
| |
| context.AddNativeCallback("SignalNativeTest", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| latch.CountDown(); |
| })); |
| |
| context.GetCompositor().SetPresentCallback( |
| [&](const FlutterLayer** layers, size_t layers_count) { |
| ASSERT_EQ(layers_count, 20u); |
| latch.CountDown(); |
| }, |
| /*one_shot=*/false); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 300; |
| event.height = 200; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| |
| latch.Wait(); |
| |
| ASSERT_EQ(context.GetCompositor().GetBackingStoresCreatedCount(), |
| num_backing_stores); |
| // Killing the engine should collect all the frames. |
| engine.reset(); |
| ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 0u); |
| } |
| |
| TEST_F(EmbedderTest, SnapshotRenderTargetScalesDownToDriverMax) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| |
| auto max_size = context.GetCompositor().GetGrContext()->maxRenderTargetSize(); |
| |
| context.AddIsolateCreateCallback([&]() { |
| Dart_Handle snapshot_large_scene = Dart_GetField( |
| Dart_RootLibrary(), tonic::ToDart("snapshot_large_scene")); |
| tonic::DartInvoke(snapshot_large_scene, {tonic::ToDart<int64_t>(max_size)}); |
| }); |
| |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeCallback( |
| "SnapshotsCallback", CREATE_NATIVE_ENTRY(([&](Dart_NativeArguments args) { |
| auto get_arg = [&args](int index) { |
| Dart_Handle dart_image = Dart_GetNativeArgument(args, index); |
| Dart_Handle internal_image = |
| Dart_GetField(dart_image, tonic::ToDart("_image")); |
| return tonic::DartConverter<flutter::CanvasImage*>::FromDart( |
| internal_image); |
| }; |
| |
| CanvasImage* big_image = get_arg(0); |
| ASSERT_EQ(big_image->width(), max_size); |
| ASSERT_EQ(big_image->height(), max_size / 2); |
| |
| CanvasImage* small_image = get_arg(1); |
| ASSERT_TRUE(ImageMatchesFixture("snapshot_large_scene.png", |
| small_image->image()->skia_image())); |
| |
| latch.Signal(); |
| }))); |
| |
| UniqueEngine engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, ObjectsPostedViaPortsServicedOnSecondaryTaskHeap) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 1024)); |
| builder.SetDartEntrypoint("objects_can_be_posted"); |
| |
| // Synchronously acquire the send port from the Dart end. We will be using |
| // this to send message. The Dart end will just echo those messages back to us |
| // for inspection. |
| FlutterEngineDartPort port = 0; |
| fml::AutoResetWaitableEvent event; |
| context.AddNativeCallback("SignalNativeCount", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| port = tonic::DartConverter<int64_t>::FromDart( |
| Dart_GetNativeArgument(args, 0)); |
| event.Signal(); |
| })); |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| event.Wait(); |
| ASSERT_NE(port, 0); |
| |
| using Trampoline = std::function<void(Dart_Handle message)>; |
| Trampoline trampoline; |
| |
| context.AddNativeCallback("SendObjectToNativeCode", |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| FML_CHECK(trampoline); |
| auto trampoline_copy = trampoline; |
| trampoline = nullptr; |
| trampoline_copy(Dart_GetNativeArgument(args, 0)); |
| })); |
| |
| // Send a boolean value and assert that it's received by the right heap. |
| { |
| FlutterEngineDartObject object = {}; |
| object.type = kFlutterEngineDartObjectTypeBool; |
| object.bool_value = true; |
| trampoline = [&](Dart_Handle handle) { |
| ASSERT_TRUE(tonic::DartConverter<bool>::FromDart(handle)); |
| auto task_grade = fml::MessageLoopTaskQueues::GetCurrentTaskSourceGrade(); |
| EXPECT_EQ(task_grade, fml::TaskSourceGrade::kDartMicroTasks); |
| event.Signal(); |
| }; |
| ASSERT_EQ(FlutterEnginePostDartObject(engine.get(), port, &object), |
| kSuccess); |
| event.Wait(); |
| } |
| } |
| |
| TEST_F(EmbedderTest, CreateInvalidBackingstoreOpenGLTexture) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); |
| builder.SetDartEntrypoint("invalid_backingstore"); |
| |
| class TestCollectOnce { |
| public: |
| // Collect() should only be called once |
| void Collect() { |
| ASSERT_FALSE(collected_); |
| collected_ = true; |
| } |
| |
| private: |
| bool collected_ = false; |
| }; |
| fml::AutoResetWaitableEvent latch; |
| |
| builder.GetCompositor().create_backing_store_callback = |
| [](const FlutterBackingStoreConfig* config, // |
| FlutterBackingStore* backing_store_out, // |
| void* user_data // |
| ) { |
| backing_store_out->type = kFlutterBackingStoreTypeOpenGL; |
| // Deliberately set this to be invalid |
| backing_store_out->user_data = nullptr; |
| backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| backing_store_out->open_gl.texture.target = 0; |
| backing_store_out->open_gl.texture.name = 0; |
| backing_store_out->open_gl.texture.format = 0; |
| static TestCollectOnce collect_once_user_data; |
| collect_once_user_data = {}; |
| backing_store_out->open_gl.texture.user_data = &collect_once_user_data; |
| backing_store_out->open_gl.texture.destruction_callback = |
| [](void* user_data) { |
| reinterpret_cast<TestCollectOnce*>(user_data)->Collect(); |
| }; |
| return true; |
| }; |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, CreateInvalidBackingstoreOpenGLFramebuffer) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetCompositor(); |
| builder.SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); |
| builder.SetDartEntrypoint("invalid_backingstore"); |
| |
| class TestCollectOnce { |
| public: |
| // Collect() should only be called once |
| void Collect() { |
| ASSERT_FALSE(collected_); |
| collected_ = true; |
| } |
| |
| private: |
| bool collected_ = false; |
| }; |
| fml::AutoResetWaitableEvent latch; |
| |
| builder.GetCompositor().create_backing_store_callback = |
| [](const FlutterBackingStoreConfig* config, // |
| FlutterBackingStore* backing_store_out, // |
| void* user_data // |
| ) { |
| backing_store_out->type = kFlutterBackingStoreTypeOpenGL; |
| // Deliberately set this to be invalid |
| backing_store_out->user_data = nullptr; |
| backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| backing_store_out->open_gl.framebuffer.target = 0; |
| backing_store_out->open_gl.framebuffer.name = 0; |
| static TestCollectOnce collect_once_user_data; |
| collect_once_user_data = {}; |
| backing_store_out->open_gl.framebuffer.user_data = |
| &collect_once_user_data; |
| backing_store_out->open_gl.framebuffer.destruction_callback = |
| [](void* user_data) { |
| reinterpret_cast<TestCollectOnce*>(user_data)->Collect(); |
| }; |
| return true; |
| }; |
| |
| context.AddNativeCallback( |
| "SignalNativeTest", |
| CREATE_NATIVE_ENTRY( |
| [&latch](Dart_NativeArguments args) { latch.Signal(); })); |
| |
| auto engine = builder.LaunchEngine(); |
| |
| // Send a window metrics events so frames may be scheduled. |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = 800; |
| event.height = 600; |
| event.pixel_ratio = 1.0; |
| ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), |
| kSuccess); |
| ASSERT_TRUE(engine.is_valid()); |
| latch.Wait(); |
| } |
| |
| TEST_F(EmbedderTest, ExternalTextureGLRefreshedTooOften) { |
| TestGLSurface surface(SkISize::Make(100, 100)); |
| auto context = surface.GetGrContext(); |
| |
| typedef void (*glGenTexturesProc)(uint32_t n, uint32_t* textures); |
| glGenTexturesProc glGenTextures; |
| |
| glGenTextures = reinterpret_cast<glGenTexturesProc>( |
| surface.GetProcAddress("glGenTextures")); |
| |
| uint32_t name; |
| glGenTextures(1, &name); |
| |
| bool resolve_called = false; |
| |
| EmbedderExternalTextureGL::ExternalTextureCallback callback( |
| [&](int64_t, size_t, size_t) { |
| resolve_called = true; |
| auto res = std::make_unique<FlutterOpenGLTexture>(); |
| res->target = GL_TEXTURE_2D; |
| res->name = name; |
| res->format = GL_RGBA8; |
| res->user_data = nullptr; |
| res->destruction_callback = [](void*) {}; |
| res->width = res->height = 100; |
| return res; |
| }); |
| EmbedderExternalTextureGL texture(1, callback); |
| |
| auto skia_surface = surface.GetOnscreenSurface(); |
| DlSkCanvasAdapter canvas(skia_surface->getCanvas()); |
| |
| Texture* texture_ = &texture; |
| Texture::PaintContext ctx{ |
| .canvas = &canvas, |
| .gr_context = context.get(), |
| }; |
| texture_->Paint(ctx, SkRect::MakeXYWH(0, 0, 100, 100), false, |
| DlImageSampling::kLinear); |
| |
| EXPECT_TRUE(resolve_called); |
| resolve_called = false; |
| |
| texture_->Paint(ctx, SkRect::MakeXYWH(0, 0, 100, 100), false, |
| DlImageSampling::kLinear); |
| |
| EXPECT_FALSE(resolve_called); |
| |
| texture_->MarkNewFrameAvailable(); |
| texture_->Paint(ctx, SkRect::MakeXYWH(0, 0, 100, 100), false, |
| DlImageSampling::kLinear); |
| |
| EXPECT_TRUE(resolve_called); |
| } |
| |
| TEST_F(EmbedderTest, |
| PresentInfoReceivesNoDamageWhenPopulateExistingDamageIsUndefined) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); |
| builder.SetDartEntrypoint("render_gradient"); |
| builder.GetRendererConfig().open_gl.populate_existing_damage = nullptr; |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // No damage should be passed. |
| static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback( |
| [](FlutterPresentInfo present_info) { |
| ASSERT_EQ(present_info.frame_damage.damage, nullptr); |
| ASSERT_EQ(present_info.buffer_damage.damage, nullptr); |
| }); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| EmbedderTestGlVk, |
| EmbedderTestMultiBackend, |
| ::testing::Values(EmbedderTestContextType::kOpenGLContext, |
| EmbedderTestContextType::kVulkanContext)); |
| |
| } // namespace testing |
| } // namespace flutter |
| |
| // NOLINTEND(clang-analyzer-core.StackAddressEscape) |