diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 6d3f396ad616f..01528ab325b36 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -24,8 +24,6 @@ FILE: ../../../flutter/common/task_runners.cc FILE: ../../../flutter/common/task_runners.h FILE: ../../../flutter/flow/compositor_context.cc FILE: ../../../flutter/flow/compositor_context.h -FILE: ../../../flutter/flow/debug_print.cc -FILE: ../../../flutter/flow/debug_print.h FILE: ../../../flutter/flow/embedded_views.cc FILE: ../../../flutter/flow/embedded_views.h FILE: ../../../flutter/flow/instrumentation.cc @@ -847,10 +845,15 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.h FILE: ../../../flutter/shell/platform/embedder/embedder_thread_host.cc FILE: ../../../flutter/shell/platform/embedder/embedder_thread_host.h FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor.png +FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor_root_surface_xformation.png FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor_software.png FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor_with_platform_layer_on_bottom.png FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor_with_root_layer_only.png +FILE: ../../../flutter/shell/platform/embedder/fixtures/gradient.png +FILE: ../../../flutter/shell/platform/embedder/fixtures/gradient_xform.png FILE: ../../../flutter/shell/platform/embedder/fixtures/main.dart +FILE: ../../../flutter/shell/platform/embedder/fixtures/scene_without_custom_compositor.png +FILE: ../../../flutter/shell/platform/embedder/fixtures/scene_without_custom_compositor_with_xform.png FILE: ../../../flutter/shell/platform/embedder/platform_view_embedder.cc FILE: ../../../flutter/shell/platform/embedder/platform_view_embedder.h FILE: ../../../flutter/shell/platform/embedder/vsync_waiter_embedder.cc diff --git a/flow/BUILD.gn b/flow/BUILD.gn index 133cddca4fcd7..90975c05e6096 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -12,8 +12,6 @@ source_set("flow") { sources = [ "compositor_context.cc", "compositor_context.h", - "debug_print.cc", - "debug_print.h", "embedded_views.cc", "embedded_views.h", "instrumentation.cc", diff --git a/flow/debug_print.cc b/flow/debug_print.cc deleted file mode 100644 index 965c3c2c78614..0000000000000 --- a/flow/debug_print.cc +++ /dev/null @@ -1,84 +0,0 @@ -// 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 "flutter/flow/debug_print.h" - -#include - -#include "third_party/skia/include/core/SkString.h" - -std::ostream& operator<<(std::ostream& os, - const flutter::MatrixDecomposition& m) { - if (!m.IsValid()) { - os << "Invalid Matrix!" << std::endl; - return os; - } - - os << "Translation (x, y, z): " << m.translation() << std::endl; - os << "Scale (z, y, z): " << m.scale() << std::endl; - os << "Shear (zy, yz, zx): " << m.shear() << std::endl; - os << "Perspective (x, y, z, w): " << m.perspective() << std::endl; - os << "Rotation (x, y, z, w): " << m.rotation() << std::endl; - - return os; -} - -std::ostream& operator<<(std::ostream& os, const SkMatrix& m) { - SkString string; - string.printf("[%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f]", - m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); - os << string.c_str(); - return os; -} - -std::ostream& operator<<(std::ostream& os, const SkMatrix44& m) { - os << m.get(0, 0) << ", " << m.get(0, 1) << ", " << m.get(0, 2) << ", " - << m.get(0, 3) << std::endl; - os << m.get(1, 0) << ", " << m.get(1, 1) << ", " << m.get(1, 2) << ", " - << m.get(1, 3) << std::endl; - os << m.get(2, 0) << ", " << m.get(2, 1) << ", " << m.get(2, 2) << ", " - << m.get(2, 3) << std::endl; - os << m.get(3, 0) << ", " << m.get(3, 1) << ", " << m.get(3, 2) << ", " - << m.get(3, 3); - return os; -} - -std::ostream& operator<<(std::ostream& os, const SkVector3& v) { - os << v.x() << ", " << v.y() << ", " << v.z(); - return os; -} - -std::ostream& operator<<(std::ostream& os, const SkVector4& v) { - os << v.fData[0] << ", " << v.fData[1] << ", " << v.fData[2] << ", " - << v.fData[3]; - return os; -} - -std::ostream& operator<<(std::ostream& os, const SkRect& r) { - os << "LTRB: " << r.fLeft << ", " << r.fTop << ", " << r.fRight << ", " - << r.fBottom; - return os; -} - -std::ostream& operator<<(std::ostream& os, const SkRRect& r) { - os << "LTRB: " << r.rect().fLeft << ", " << r.rect().fTop << ", " - << r.rect().fRight << ", " << r.rect().fBottom; - return os; -} - -std::ostream& operator<<(std::ostream& os, const SkPoint& r) { - os << "XY: " << r.fX << ", " << r.fY; - return os; -} - -std::ostream& operator<<(std::ostream& os, - const flutter::PictureRasterCacheKey& k) { - os << "Picture: " << k.id() << " matrix: " << k.matrix(); - return os; -} - -std::ostream& operator<<(std::ostream& os, const SkISize& size) { - os << size.width() << ", " << size.height(); - return os; -} diff --git a/flow/debug_print.h b/flow/debug_print.h deleted file mode 100644 index f8e0d239c8c86..0000000000000 --- a/flow/debug_print.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_FLOW_DEBUG_PRINT_H_ -#define FLUTTER_FLOW_DEBUG_PRINT_H_ - -#include "flutter/flow/matrix_decomposition.h" -#include "flutter/flow/raster_cache_key.h" -#include "flutter/fml/macros.h" -#include "third_party/skia/include/core/SkMatrix.h" -#include "third_party/skia/include/core/SkMatrix44.h" -#include "third_party/skia/include/core/SkPoint3.h" -#include "third_party/skia/include/core/SkRRect.h" - -#define DEF_PRINTER(x) std::ostream& operator<<(std::ostream&, const x&); - -DEF_PRINTER(flutter::MatrixDecomposition); -DEF_PRINTER(flutter::PictureRasterCacheKey); -DEF_PRINTER(SkISize); -DEF_PRINTER(SkMatrix); -DEF_PRINTER(SkMatrix44); -DEF_PRINTER(SkPoint); -DEF_PRINTER(SkRect); -DEF_PRINTER(SkRRect); -DEF_PRINTER(SkVector3); -DEF_PRINTER(SkVector4); - -#endif // FLUTTER_FLOW_DEBUG_PRINT_H_ diff --git a/lib/ui/painting/image_decoder_unittests.cc b/lib/ui/painting/image_decoder_unittests.cc index 7045a248cd979..8786150d0a864 100644 --- a/lib/ui/painting/image_decoder_unittests.cc +++ b/lib/ui/painting/image_decoder_unittests.cc @@ -19,7 +19,8 @@ class TestIOManager final : public IOManager { public: TestIOManager(fml::RefPtr task_runner, bool has_gpu_context = true) - : gl_context_(has_gpu_context ? gl_surface_.CreateGrContext() : nullptr), + : gl_surface_(SkISize::Make(1, 1)), + gl_context_(has_gpu_context ? gl_surface_.CreateGrContext() : nullptr), weak_gl_context_factory_( has_gpu_context ? std::make_unique>( gl_context_.get()) diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 87f41b5a8fb3c..3ede4058be9a6 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -234,7 +234,8 @@ void ShellTest::AddNativeCallback(std::string name, ShellTestPlatformView::ShellTestPlatformView(PlatformView::Delegate& delegate, TaskRunners task_runners) - : PlatformView(delegate, std::move(task_runners)) {} + : PlatformView(delegate, std::move(task_runners)), + gl_surface_(SkISize::Make(800, 600)) {} ShellTestPlatformView::~ShellTestPlatformView() = default; diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index 7c7095757687e..b1fd6ab492b55 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -85,9 +85,14 @@ test_fixtures("fixtures") { dart_main = "fixtures/main.dart" fixtures = [ "fixtures/compositor.png", + "fixtures/gradient.png", + "fixtures/gradient_xform.png", "fixtures/compositor_software.png", "fixtures/compositor_with_platform_layer_on_bottom.png", "fixtures/compositor_with_root_layer_only.png", + "fixtures/compositor_root_surface_xformation.png", + "fixtures/scene_without_custom_compositor.png", + "fixtures/scene_without_custom_compositor_with_xform.png", ] } @@ -119,6 +124,7 @@ if (current_toolchain == host_toolchain) { "$flutter_root/runtime", "$flutter_root/testing:dart", "$flutter_root/testing:opengl", + "$flutter_root/testing:skia", "//third_party/skia", "//third_party/tonic", ] diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index bc412d903bdc6..43599c52fc1ec 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -171,6 +171,13 @@ InferOpenGLPlatformViewCreationCallback( transformation.pers2 // ); }; + + // If there is an external view embedder, ask it to apply the surface + // transformation to its surfaces as well. + if (external_view_embedder) { + external_view_embedder->SetSurfaceTransformationCallback( + gl_surface_transformation_callback); + } } flutter::GPUSurfaceGLDelegate::GLProcResolver gl_proc_resolver = nullptr; @@ -296,13 +303,13 @@ static sk_sp MakeSkSurfaceFromBackingStore( SkSurfaceProps::InitType::kLegacyFontHost_InitType); auto surface = SkSurface::MakeFromBackendTexture( - context, // context - backend_texture, // back-end texture - kTopLeft_GrSurfaceOrigin, // surface origin - 1, // sample count - kN32_SkColorType, // color type - SkColorSpace::MakeSRGB(), // color space - &surface_properties, // surface properties + context, // context + backend_texture, // back-end texture + kBottomLeft_GrSurfaceOrigin, // surface origin + 1, // sample count + kN32_SkColorType, // color type + SkColorSpace::MakeSRGB(), // color space + &surface_properties, // surface properties static_cast( texture->destruction_callback), // release proc texture->user_data // release context @@ -337,12 +344,12 @@ static sk_sp MakeSkSurfaceFromBackingStore( SkSurfaceProps::InitType::kLegacyFontHost_InitType); auto surface = SkSurface::MakeFromBackendRenderTarget( - context, // context - backend_render_target, // backend render target - kTopLeft_GrSurfaceOrigin, // surface origin - kN32_SkColorType, // color type - SkColorSpace::MakeSRGB(), // color space - &surface_properties, // surface properties + context, // context + backend_render_target, // backend render target + kBottomLeft_GrSurfaceOrigin, // surface origin + kN32_SkColorType, // color type + SkColorSpace::MakeSRGB(), // color space + &surface_properties, // surface properties static_cast( framebuffer->destruction_callback), // release proc framebuffer->user_data // release context diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 0f889393f5273..ee03b66cf10ef 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -282,6 +282,14 @@ typedef struct { bool fbo_reset_after_present; /// The transformation to apply to the render target before any rendering /// operations. This callback is optional. + /// @attention When using a custom compositor, the layer offset and sizes + /// will be affected by this transformation. It will be + /// embedder responsibility to render contents at the + /// transformed offset and size. This is useful for embedders + /// that want to render transformed contents directly into + /// hardware overlay planes without having to apply extra + /// transformations to layer contents (which may necessitate + /// an expensive off-screen render pass). TransformationCallback surface_transformation; ProcResolver gl_proc_resolver; /// When the embedder specifies that a texture has a frame available, the diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index 064468f85c435..0ef6584541b25 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -21,6 +21,19 @@ EmbedderExternalViewEmbedder::EmbedderExternalViewEmbedder( EmbedderExternalViewEmbedder::~EmbedderExternalViewEmbedder() = default; +void EmbedderExternalViewEmbedder::SetSurfaceTransformationCallback( + SurfaceTransformationCallback surface_transformation_callback) { + surface_transformation_callback_ = surface_transformation_callback; +} + +SkMatrix EmbedderExternalViewEmbedder::GetSurfaceTransformation() const { + if (!surface_transformation_callback_) { + return SkMatrix{}; + } + + return surface_transformation_callback_(); +} + void EmbedderExternalViewEmbedder::Reset() { pending_recorders_.clear(); pending_canvas_spies_.clear(); @@ -33,22 +46,34 @@ void EmbedderExternalViewEmbedder::CancelFrame() { Reset(); } -static FlutterBackingStoreConfig MakeBackingStoreConfig(const SkISize& size) { +static FlutterBackingStoreConfig MakeBackingStoreConfig( + const SkISize& backing_store_size) { FlutterBackingStoreConfig config = {}; config.struct_size = sizeof(config); - config.size.width = size.width(); - config.size.height = size.height(); + config.size.width = backing_store_size.width(); + config.size.height = backing_store_size.height(); return config; } +static SkISize TransformedSurfaceSize(const SkISize& size, + const SkMatrix& transformation) { + const auto source_rect = SkRect::MakeWH(size.width(), size.height()); + const auto transformed_rect = transformation.mapRect(source_rect); + return SkISize::Make(transformed_rect.width(), transformed_rect.height()); +} + // |ExternalViewEmbedder| void EmbedderExternalViewEmbedder::BeginFrame(SkISize frame_size, GrContext* context) { Reset(); pending_frame_size_ = frame_size; + pending_surface_transformation_ = GetSurfaceTransformation(); + + const auto surface_size = TransformedSurfaceSize( + pending_frame_size_, pending_surface_transformation_); // Decide if we want to discard the previous root render target. if (root_render_target_) { @@ -61,7 +86,7 @@ void EmbedderExternalViewEmbedder::BeginFrame(SkISize frame_size, } else { auto last_surface_size = SkISize::Make(surface->width(), surface->height()); - if (pending_frame_size_ != last_surface_size) { + if (surface_size != last_surface_size) { root_render_target_ = nullptr; } } @@ -72,7 +97,19 @@ void EmbedderExternalViewEmbedder::BeginFrame(SkISize frame_size, // canvas. if (!root_render_target_) { root_render_target_ = create_render_target_callback_( - context, MakeBackingStoreConfig(pending_frame_size_)); + context, MakeBackingStoreConfig(surface_size)); + } + + // Install the root surface transformation on the root canvas at the beginning + // of each frame. + if (root_render_target_) { + auto surface = root_render_target_->GetRenderSurface(); + if (surface) { + auto canvas = surface->getCanvas(); + if (canvas) { + canvas->setMatrix(pending_surface_transformation_); + } + } } } @@ -115,19 +152,26 @@ SkCanvas* EmbedderExternalViewEmbedder::CompositeEmbeddedView(int view_id) { return found->second->GetSpyingCanvas(); } -static FlutterLayer MakeLayer(const SkISize& frame_size, - const FlutterBackingStore* store) { +static FlutterLayer MakeBackingStoreLayer( + const SkISize& frame_size, + const FlutterBackingStore* store, + const SkMatrix& surface_transformation) { FlutterLayer layer = {}; layer.struct_size = sizeof(layer); layer.type = kFlutterLayerContentTypeBackingStore; layer.backing_store = store; - layer.offset.x = 0.0; - layer.offset.y = 0.0; + const auto layer_bounds = + SkRect::MakeWH(frame_size.width(), frame_size.height()); + + const auto transformed_layer_bounds = + surface_transformation.mapRect(layer_bounds); - layer.size.width = frame_size.width(); - layer.size.height = frame_size.height(); + layer.offset.x = transformed_layer_bounds.x(); + layer.offset.y = transformed_layer_bounds.y(); + layer.size.width = transformed_layer_bounds.width(); + layer.size.height = transformed_layer_bounds.height(); return layer; } @@ -143,19 +187,29 @@ static FlutterPlatformView MakePlatformView( return view; } -static FlutterLayer MakeLayer(const EmbeddedViewParams& params, - const FlutterPlatformView& platform_view) { +static FlutterLayer MakePlatformViewLayer( + const EmbeddedViewParams& params, + const FlutterPlatformView& platform_view, + const SkMatrix& surface_transformation) { FlutterLayer layer = {}; layer.struct_size = sizeof(layer); layer.type = kFlutterLayerContentTypePlatformView; layer.platform_view = &platform_view; - layer.offset.x = params.offsetPixels.x(); - layer.offset.y = params.offsetPixels.y(); + const auto layer_bounds = SkRect::MakeXYWH(params.offsetPixels.x(), // + params.offsetPixels.y(), // + params.sizePoints.width(), // + params.sizePoints.height() // + ); - layer.size.width = params.sizePoints.width(); - layer.size.height = params.sizePoints.height(); + const auto transformed_layer_bounds = + surface_transformation.mapRect(layer_bounds); + + layer.offset.x = transformed_layer_bounds.x(); + layer.offset.y = transformed_layer_bounds.y(); + layer.size.width = transformed_layer_bounds.width(); + layer.size.height = transformed_layer_bounds.height(); return layer; } @@ -180,13 +234,14 @@ bool EmbedderExternalViewEmbedder::SubmitFrame(GrContext* context) { { // The root surface is expressed as a layer. - EmbeddedViewParams params; - params.offsetPixels = SkPoint::Make(0, 0); - params.sizePoints = pending_frame_size_; - presented_layers.push_back( - MakeLayer(pending_frame_size_, root_render_target_->GetBackingStore())); + presented_layers.push_back(MakeBackingStoreLayer( + pending_frame_size_, root_render_target_->GetBackingStore(), + pending_surface_transformation_)); } + const auto surface_size = TransformedSurfaceSize( + pending_frame_size_, pending_surface_transformation_); + for (const auto& view_id : composition_order_) { FML_DCHECK(pending_recorders_.count(view_id) == 1); FML_DCHECK(pending_canvas_spies_.count(view_id) == 1); @@ -208,15 +263,16 @@ bool EmbedderExternalViewEmbedder::SubmitFrame(GrContext* context) { // struct. It is safe to deallocate when the embedder callback is done. presented_platform_views[view_id] = MakePlatformView(view_id); presented_layers.push_back( - MakeLayer(params, presented_platform_views.at(view_id))); + MakePlatformViewLayer(params, presented_platform_views.at(view_id), + pending_surface_transformation_)); if (!pending_canvas_spies_.at(view_id)->DidDrawIntoCanvas()) { // Nothing was drawn into the overlay canvas, we don't need to composite // it. continue; } - const auto backing_store_config = - MakeBackingStoreConfig(pending_frame_size_); + + const auto backing_store_config = MakeBackingStoreConfig(surface_size); RegistryKey registry_key(view_id, backing_store_config); @@ -249,13 +305,15 @@ bool EmbedderExternalViewEmbedder::SubmitFrame(GrContext* context) { return false; } + render_canvas->setMatrix(pending_surface_transformation_); render_canvas->clear(SK_ColorTRANSPARENT); render_canvas->drawPicture(picture); render_canvas->flush(); // Indicate a layer for the backing store containing contents rendered by // Flutter. - presented_layers.push_back( - MakeLayer(pending_frame_size_, render_target->GetBackingStore())); + presented_layers.push_back(MakeBackingStoreLayer( + pending_frame_size_, render_target->GetBackingStore(), + pending_surface_transformation_)); } { diff --git a/shell/platform/embedder/embedder_external_view_embedder.h b/shell/platform/embedder/embedder_external_view_embedder.h index d34ba2e1f00bb..65b001cee040e 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.h +++ b/shell/platform/embedder/embedder_external_view_embedder.h @@ -34,6 +34,7 @@ class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder { const FlutterBackingStoreConfig& config)>; using PresentCallback = std::function& layers)>; + using SurfaceTransformationCallback = std::function; //---------------------------------------------------------------------------- /// @brief Creates an external view embedder used by the generic embedder @@ -56,6 +57,17 @@ class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder { /// ~EmbedderExternalViewEmbedder() override; + //---------------------------------------------------------------------------- + /// @brief Sets the surface transformation callback used by the external + /// view embedder to ask the platform for the per frame root + /// surface transformation. + /// + /// @param[in] surface_transformation_callback The surface transformation + /// callback + /// + void SetSurfaceTransformationCallback( + SurfaceTransformationCallback surface_transformation_callback); + private: // |ExternalViewEmbedder| void CancelFrame() override; @@ -108,12 +120,14 @@ class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder { const CreateRenderTargetCallback create_render_target_callback_; const PresentCallback present_callback_; + SurfaceTransformationCallback surface_transformation_callback_; using Registry = std::unordered_map, RegistryKey::Hash, RegistryKey::Equal>; SkISize pending_frame_size_ = SkISize::Make(0, 0); + SkMatrix pending_surface_transformation_; std::map> pending_recorders_; std::map> pending_canvas_spies_; @@ -124,6 +138,8 @@ class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder { void Reset(); + SkMatrix GetSurfaceTransformation() const; + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderExternalViewEmbedder); }; diff --git a/shell/platform/embedder/fixtures/compositor.png b/shell/platform/embedder/fixtures/compositor.png index a03c3cd4ff5a5..038efaab9d8a8 100644 Binary files a/shell/platform/embedder/fixtures/compositor.png and b/shell/platform/embedder/fixtures/compositor.png differ diff --git a/shell/platform/embedder/fixtures/compositor_root_surface_xformation.png b/shell/platform/embedder/fixtures/compositor_root_surface_xformation.png new file mode 100644 index 0000000000000..8191fa524b45b Binary files /dev/null and b/shell/platform/embedder/fixtures/compositor_root_surface_xformation.png differ diff --git a/shell/platform/embedder/fixtures/compositor_software.png b/shell/platform/embedder/fixtures/compositor_software.png index a8abf81b2af6c..f2efa1343cc2c 100644 Binary files a/shell/platform/embedder/fixtures/compositor_software.png and b/shell/platform/embedder/fixtures/compositor_software.png differ diff --git a/shell/platform/embedder/fixtures/gradient.png b/shell/platform/embedder/fixtures/gradient.png new file mode 100644 index 0000000000000..27b69ecbb85c3 Binary files /dev/null and b/shell/platform/embedder/fixtures/gradient.png differ diff --git a/shell/platform/embedder/fixtures/gradient_xform.png b/shell/platform/embedder/fixtures/gradient_xform.png new file mode 100644 index 0000000000000..714b04e8e8cc2 Binary files /dev/null and b/shell/platform/embedder/fixtures/gradient_xform.png differ diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index 9b1d4a49b68cf..41260e6327355 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -300,3 +300,107 @@ void can_composite_platform_views_with_platform_layer_on_bottom() { signalNativeTest(); // Signal 1 window.scheduleFrame(); } + +@pragma('vm:entry-point') +void can_render_scene_without_custom_compositor() { + window.onBeginFrame = (Duration duration) { + Color red = Color.fromARGB(127, 255, 0, 0); + Color green = Color.fromARGB(127, 0, 255, 0); + Color blue = Color.fromARGB(127, 0, 0, 255); + Color magenta = Color.fromARGB(127, 255, 0, 255); + Color gray = Color.fromARGB(127, 127, 127, 127); + + Size size = Size(50.0, 150.0); + + SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0.0, 0.0); + + builder.addPicture(Offset(10.0, 10.0), CreateColoredBox(red, size)); // red - flutter + + builder.addPicture(Offset(20.0, 20.0), CreateColoredBox(green, size)); // green - flutter + + builder.addPicture(Offset(30.0, 30.0), CreateColoredBox(blue, size)); // blue - flutter + + builder.addPicture(Offset(40.0, 40.0), CreateColoredBox(magenta, size)); // magenta - flutter + + builder.addPicture(Offset(50.0, 50.0), CreateColoredBox(gray, size)); // gray - flutter + + builder.pop(); + + window.render(builder.build()); + }; + window.scheduleFrame(); +} + +Picture CreateGradientBox(Size size) { + Paint paint = Paint(); + List rainbow = [ + Color.fromARGB(255, 255, 0, 0), // red + Color.fromARGB(255, 255, 165, 0), // orange + Color.fromARGB(255, 255, 255, 0), // yellow + Color.fromARGB(255, 0, 255, 0), // green + Color.fromARGB(255, 0, 0, 255), // blue + Color.fromARGB(255, 75, 0, 130), // indigo + Color.fromARGB(255, 238,130,238), // violet + ]; + List stops = [ + (1.0 / 7.0), + (2.0 / 7.0), + (3.0 / 7.0), + (4.0 / 7.0), + (5.0 / 7.0), + (6.0 / 7.0), + (7.0 / 7.0), + ]; + paint.shader = Gradient.linear( + Offset(0.0, 0.0), + Offset(size.width, size.height), + rainbow, stops); + PictureRecorder baseRecorder = PictureRecorder(); + Canvas canvas = Canvas(baseRecorder); + canvas.drawRect(Rect.fromLTRB(0.0, 0.0, size.width, size.height), paint); + return baseRecorder.endRecording(); +} + +@pragma('vm:entry-point') +void render_gradient() { + window.onBeginFrame = (Duration duration) { + Size size = Size(800.0, 600.0); + + SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0.0, 0.0); + + builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(size)); // gradient - flutter + + builder.pop(); + + window.render(builder.build()); + }; + window.scheduleFrame(); +} + +@pragma('vm:entry-point') +void render_gradient_on_non_root_backing_store() { + window.onBeginFrame = (Duration duration) { + Size size = Size(800.0, 600.0); + Color red = Color.fromARGB(127, 255, 0, 0); + + SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0.0, 0.0); + + // Even though this is occluded, add something so it is not elided. + builder.addPicture(Offset(10.0, 10.0), CreateColoredBox(red, size)); // red - flutter + + builder.addPlatformView(1, width: 100, height:200); // undefined - platform + + builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(size)); // gradient - flutter + + builder.pop(); + + window.render(builder.build()); + }; + window.scheduleFrame(); +} diff --git a/shell/platform/embedder/fixtures/scene_without_custom_compositor.png b/shell/platform/embedder/fixtures/scene_without_custom_compositor.png new file mode 100644 index 0000000000000..aa3ee979b179d Binary files /dev/null and b/shell/platform/embedder/fixtures/scene_without_custom_compositor.png differ diff --git a/shell/platform/embedder/fixtures/scene_without_custom_compositor_with_xform.png b/shell/platform/embedder/fixtures/scene_without_custom_compositor_with_xform.png new file mode 100644 index 0000000000000..c80d8046cef01 Binary files /dev/null and b/shell/platform/embedder/fixtures/scene_without_custom_compositor_with_xform.png differ diff --git a/shell/platform/embedder/tests/embedder_a11y_unittests.cc b/shell/platform/embedder/tests/embedder_a11y_unittests.cc index 739fab38c7097..f9255734f6323 100644 --- a/shell/platform/embedder/tests/embedder_a11y_unittests.cc +++ b/shell/platform/embedder/tests/embedder_a11y_unittests.cc @@ -65,6 +65,7 @@ TEST_F(Embedder11yTest, A11yTreeIsConsistent) { }))); EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("a11y_main"); auto engine = builder.LaunchEngine(); diff --git a/shell/platform/embedder/tests/embedder_assertions.h b/shell/platform/embedder/tests/embedder_assertions.h index bdc2a597368e4..a4918f56bca13 100644 --- a/shell/platform/embedder/tests/embedder_assertions.h +++ b/shell/platform/embedder/tests/embedder_assertions.h @@ -247,4 +247,18 @@ inline FlutterSize FlutterSizeMake(double width, double height) { return size; } +inline FlutterTransformation FlutterTransformationMake(const SkMatrix& matrix) { + FlutterTransformation transformation = {}; + transformation.scaleX = matrix[SkMatrix::kMScaleX]; + transformation.skewX = matrix[SkMatrix::kMSkewX]; + transformation.transX = matrix[SkMatrix::kMTransX]; + transformation.skewY = matrix[SkMatrix::kMSkewY]; + transformation.scaleY = matrix[SkMatrix::kMScaleY]; + transformation.transY = matrix[SkMatrix::kMTransY]; + transformation.pers0 = matrix[SkMatrix::kMPersp0]; + transformation.pers1 = matrix[SkMatrix::kMPersp1]; + transformation.pers2 = matrix[SkMatrix::kMPersp2]; + return transformation; +} + #endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_ASSERTIONS_H_ diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index 0f38a78ea604f..c2ae44e516642 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -46,6 +46,12 @@ EmbedderConfigBuilder::EmbedderConfigBuilder( return reinterpret_cast(context)->GLGetProcAddress( name); }; + opengl_renderer_config_.fbo_reset_after_present = true; + opengl_renderer_config_.surface_transformation = + [](void* context) -> FlutterTransformation { + return reinterpret_cast(context) + ->GetRootSurfaceTransformation(); + }; software_renderer_config_.struct_size = sizeof(FlutterSoftwareRendererConfig); software_renderer_config_.surface_present_callback = @@ -70,7 +76,6 @@ EmbedderConfigBuilder::EmbedderConfigBuilder( AddCommandLineArgument("embedder_unittest"); if (preference == InitializationPreference::kInitialize) { - SetSoftwareRendererConfig(); SetAssetsPath(); SetSnapshots(); SetIsolateCreateCallbackHook(); @@ -85,15 +90,20 @@ FlutterProjectArgs& EmbedderConfigBuilder::GetProjectArgs() { return project_args_; } -void EmbedderConfigBuilder::SetSoftwareRendererConfig() { +void EmbedderConfigBuilder::SetSoftwareRendererConfig(SkISize surface_size) { renderer_config_.type = FlutterRendererType::kSoftware; renderer_config_.software = software_renderer_config_; + + // TODO(chinmaygarde): The compositor still uses a GL surface for operation. + // Once this is no longer the case, don't setup the GL surface when using the + // software renderer config. + context_.SetupOpenGLSurface(surface_size); } -void EmbedderConfigBuilder::SetOpenGLRendererConfig() { +void EmbedderConfigBuilder::SetOpenGLRendererConfig(SkISize surface_size) { renderer_config_.type = FlutterRendererType::kOpenGL; renderer_config_.open_gl = opengl_renderer_config_; - context_.SetupOpenGLSurface(); + context_.SetupOpenGLSurface(surface_size); } void EmbedderConfigBuilder::SetAssetsPath() { diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index 9dba0534369c2..561821705c462 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -43,9 +43,9 @@ class EmbedderConfigBuilder { FlutterProjectArgs& GetProjectArgs(); - void SetSoftwareRendererConfig(); + void SetSoftwareRendererConfig(SkISize surface_size = SkISize::Make(1, 1)); - void SetOpenGLRendererConfig(); + void SetOpenGLRendererConfig(SkISize surface_size); void SetAssetsPath(); diff --git a/shell/platform/embedder/tests/embedder_test_compositor.cc b/shell/platform/embedder/tests/embedder_test_compositor.cc index 7ff381ed763ee..a30dfcc9ce551 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor.cc +++ b/shell/platform/embedder/tests/embedder_test_compositor.cc @@ -11,8 +11,10 @@ namespace flutter { namespace testing { -EmbedderTestCompositor::EmbedderTestCompositor(sk_sp context) - : context_(context) { +EmbedderTestCompositor::EmbedderTestCompositor(SkISize surface_size, + sk_sp context) + : surface_size_(surface_size), context_(context) { + FML_CHECK(!surface_size_.isEmpty()) << "Surface size must not be empty"; FML_CHECK(context_); } @@ -61,8 +63,7 @@ bool EmbedderTestCompositor::UpdateOffscrenComposition( size_t layers_count) { last_composition_ = nullptr; - auto surface_size = SkISize::Make(800, 600); - const auto image_info = SkImageInfo::MakeN32Premul(surface_size); + const auto image_info = SkImageInfo::MakeN32Premul(surface_size_); auto surface = type_ == RenderTargetType::kSoftwareBuffer ? SkSurface::MakeRaster(image_info) @@ -169,16 +170,16 @@ bool EmbedderTestCompositor::CreateFramebufferRenderSurface( const auto image_info = SkImageInfo::MakeN32Premul(config->size.width, config->size.height); - auto surface = - SkSurface::MakeRenderTarget(context_.get(), // context - SkBudgeted::kNo, // budgeted - image_info, // image info - 1, // sample count - kTopLeft_GrSurfaceOrigin, // surface origin - nullptr, // surface properties - false // mipmaps + auto surface = SkSurface::MakeRenderTarget( + context_.get(), // context + SkBudgeted::kNo, // budgeted + image_info, // image info + 1, // sample count + kBottomLeft_GrSurfaceOrigin, // surface origin + nullptr, // surface properties + false // mipmaps - ); + ); if (!surface) { FML_LOG(ERROR) << "Could not create render target for compositor layer."; @@ -219,16 +220,16 @@ bool EmbedderTestCompositor::CreateTextureRenderSurface( const auto image_info = SkImageInfo::MakeN32Premul(config->size.width, config->size.height); - auto surface = - SkSurface::MakeRenderTarget(context_.get(), // context - SkBudgeted::kNo, // budgeted - image_info, // image info - 1, // sample count - kTopLeft_GrSurfaceOrigin, // surface origin - nullptr, // surface properties - false // mipmaps + auto surface = SkSurface::MakeRenderTarget( + context_.get(), // context + SkBudgeted::kNo, // budgeted + image_info, // image info + 1, // sample count + kBottomLeft_GrSurfaceOrigin, // surface origin + nullptr, // surface properties + false // mipmaps - ); + ); if (!surface) { FML_LOG(ERROR) << "Could not create render target for compositor layer."; diff --git a/shell/platform/embedder/tests/embedder_test_compositor.h b/shell/platform/embedder/tests/embedder_test_compositor.h index 65c24cbbf9f71..ae6c768973a1c 100644 --- a/shell/platform/embedder/tests/embedder_test_compositor.h +++ b/shell/platform/embedder/tests/embedder_test_compositor.h @@ -22,7 +22,7 @@ class EmbedderTestCompositor { kSoftwareBuffer, }; - EmbedderTestCompositor(sk_sp context); + EmbedderTestCompositor(SkISize surface_size, sk_sp context); ~EmbedderTestCompositor(); @@ -59,6 +59,7 @@ class EmbedderTestCompositor { size_t GetBackingStoresCount() const; private: + const SkISize surface_size_; sk_sp context_; RenderTargetType type_ = RenderTargetType::kOpenGLFramebuffer; PlatformViewRendererCallback platform_view_renderer_callback_; diff --git a/shell/platform/embedder/tests/embedder_test_context.cc b/shell/platform/embedder/tests/embedder_test_context.cc index fd5867925b1e4..9403fef8126ec 100644 --- a/shell/platform/embedder/tests/embedder_test_context.cc +++ b/shell/platform/embedder/tests/embedder_test_context.cc @@ -5,6 +5,7 @@ #include "flutter/shell/platform/embedder/tests/embedder_test_context.h" #include "flutter/runtime/dart_vm.h" +#include "flutter/shell/platform/embedder/tests/embedder_assertions.h" #include "third_party/skia/include/core/SkSurface.h" namespace flutter { @@ -59,6 +60,10 @@ const fml::Mapping* EmbedderTestContext::GetIsolateSnapshotInstructions() return isolate_snapshot_instructions_.get(); } +void EmbedderTestContext::SetRootSurfaceTransformation(SkMatrix matrix) { + root_surface_transformation_ = matrix; +} + void EmbedderTestContext::AddIsolateCreateCallback(fml::closure closure) { if (closure) { isolate_create_callbacks_.push_back(closure); @@ -126,10 +131,9 @@ EmbedderTestContext::GetUpdateSemanticsCustomActionCallbackHook() { }; } -void EmbedderTestContext::SetupOpenGLSurface() { - if (!gl_surface_) { - gl_surface_ = std::make_unique(); - } +void EmbedderTestContext::SetupOpenGLSurface(SkISize surface_size) { + FML_CHECK(!gl_surface_); + gl_surface_ = std::make_unique(surface_size); } bool EmbedderTestContext::GLMakeCurrent() { @@ -143,16 +147,11 @@ bool EmbedderTestContext::GLClearCurrent() { } bool EmbedderTestContext::GLPresent() { - gl_surface_present_count_++; FML_CHECK(gl_surface_) << "GL surface must be initialized."; + gl_surface_present_count_++; - if (next_scene_callback_) { - auto raster_snapshot = gl_surface_->GetRasterSurfaceSnapshot(); - FML_CHECK(raster_snapshot); - auto callback = next_scene_callback_; - next_scene_callback_ = nullptr; - callback(std::move(raster_snapshot)); - } + FireRootSurfacePresentCallbackIfPresent( + [&]() { return gl_surface_->GetRasterSurfaceSnapshot(); }); if (!gl_surface_->Present()) { return false; @@ -176,18 +175,21 @@ void* EmbedderTestContext::GLGetProcAddress(const char* name) { return gl_surface_->GetProcAddress(name); } +FlutterTransformation EmbedderTestContext::GetRootSurfaceTransformation() { + return FlutterTransformationMake(root_surface_transformation_); +} + void EmbedderTestContext::SetupCompositor() { - if (compositor_) { - return; - } - SetupOpenGLSurface(); - compositor_ = - std::make_unique(gl_surface_->GetGrContext()); + FML_CHECK(!compositor_) << "Already ssetup a compositor in this context."; + FML_CHECK(gl_surface_) + << "Setup the GL surface before setting up a compositor."; + compositor_ = std::make_unique( + gl_surface_->GetSurfaceSize(), gl_surface_->GetGrContext()); } EmbedderTestCompositor& EmbedderTestContext::GetCompositor() { FML_CHECK(compositor_) - << "Accessed the compositor on a context where one was not setup. Used " + << "Accessed the compositor on a context where one was not setup. Use " "the config builder to setup a context with a custom compositor."; return *compositor_; } @@ -203,8 +205,10 @@ void EmbedderTestContext::SetNextSceneCallback( bool EmbedderTestContext::SofwarePresent(sk_sp image) { software_surface_present_count_++; - software_surface_ = std::move(image); - return software_surface_ != nullptr; + + FireRootSurfacePresentCallbackIfPresent([image] { return image; }); + + return true; } size_t EmbedderTestContext::GetGLSurfacePresentCount() const { @@ -215,5 +219,15 @@ size_t EmbedderTestContext::GetSoftwareSurfacePresentCount() const { return software_surface_present_count_; } +void EmbedderTestContext::FireRootSurfacePresentCallbackIfPresent( + std::function(void)> image_callback) { + if (!next_scene_callback_) { + return; + } + auto callback = next_scene_callback_; + next_scene_callback_ = nullptr; + callback(image_callback()); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_test_context.h b/shell/platform/embedder/tests/embedder_test_context.h index 164134c22e0c3..9d37f0926ee03 100644 --- a/shell/platform/embedder/tests/embedder_test_context.h +++ b/shell/platform/embedder/tests/embedder_test_context.h @@ -42,6 +42,8 @@ class EmbedderTestContext { const fml::Mapping* GetIsolateSnapshotInstructions() const; + void SetRootSurfaceTransformation(SkMatrix matrix); + void AddIsolateCreateCallback(fml::closure closure); void AddNativeCallback(const char* name, Dart_NativeFunction function); @@ -54,8 +56,6 @@ class EmbedderTestContext { void SetPlatformMessageCallback( std::function callback); - void SetupCompositor(); - EmbedderTestCompositor& GetCompositor(); using NextSceneCallback = std::function image)>; @@ -80,9 +80,9 @@ class EmbedderTestContext { SemanticsActionCallback update_semantics_custom_action_callback_; std::function platform_message_callback_; std::unique_ptr gl_surface_; - sk_sp software_surface_; std::unique_ptr compositor_; NextSceneCallback next_scene_callback_; + SkMatrix root_surface_transformation_; size_t gl_surface_present_count_ = 0; size_t software_surface_present_count_ = 0; @@ -94,11 +94,13 @@ class EmbedderTestContext { static FlutterUpdateSemanticsCustomActionCallback GetUpdateSemanticsCustomActionCallbackHook(); + void SetupCompositor(); + void FireIsolateCreateCallbacks(); void SetNativeResolver(); - void SetupOpenGLSurface(); + void SetupOpenGLSurface(SkISize surface_size); bool GLMakeCurrent(); @@ -112,10 +114,15 @@ class EmbedderTestContext { void* GLGetProcAddress(const char* name); + FlutterTransformation GetRootSurfaceTransformation(); + void PlatformMessageCallback(const FlutterPlatformMessage* message); bool SofwarePresent(sk_sp image); + void FireRootSurfacePresentCallbackIfPresent( + std::function(void)> image_callback); + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestContext); }; diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 7e299fb6aa0a0..5304a959281d5 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -19,6 +19,7 @@ #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/testing/assertions_skia.h" #include "flutter/testing/testing.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/tonic/converter/dart_converter.h" @@ -41,6 +42,7 @@ TEST_F(EmbedderTest, CanLaunchAndShutdownWithValidProjectArgs) { fml::AutoResetWaitableEvent latch; context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); // Wait for the root isolate to launch. @@ -50,6 +52,7 @@ TEST_F(EmbedderTest, CanLaunchAndShutdownWithValidProjectArgs) { TEST_F(EmbedderTest, CanLaunchAndShutdownMultipleTimes) { EmbedderConfigBuilder builder(GetEmbedderContext()); + builder.SetSoftwareRendererConfig(); for (size_t i = 0; i < 3; ++i) { auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -65,6 +68,7 @@ TEST_F(EmbedderTest, CanInvokeCustomEntrypoint) { }; context.AddNativeCallback("SayHiFromCustomEntrypoint", entrypoint); EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("customEntrypoint"); auto engine = builder.LaunchEngine(); latch.Wait(); @@ -103,6 +107,7 @@ TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { })); EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("customEntrypoint1"); auto engine = builder.LaunchEngine(); latch1.Wait(); @@ -192,6 +197,7 @@ TEST_F(EmbedderTest, CanSpecifyCustomTaskRunner) { EmbedderConfigBuilder builder(context); const auto task_runner_description = test_task_runner.GetFlutterTaskRunnerDescription(); + builder.SetSoftwareRendererConfig(); builder.SetPlatformTaskRunner(&task_runner_description); builder.SetDartEntrypoint("invokePlatformTaskRunner"); std::scoped_lock lock(engine_mutex); @@ -231,7 +237,7 @@ TEST(EmbedderTestNoFixture, CanGetCurrentTimeInNanoseconds) { TEST_F(EmbedderTest, CanCreateOpenGLRenderingEngine) { EmbedderConfigBuilder builder(GetEmbedderContext()); - builder.SetOpenGLRendererConfig(); + builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); } @@ -246,6 +252,7 @@ TEST_F(EmbedderTest, IsolateServiceIdSent) { thread.GetTaskRunner()->PostTask([&]() { EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("main"); builder.SetPlatformMessageCallback( [&](const FlutterPlatformMessage* message) { @@ -281,6 +288,7 @@ TEST_F(EmbedderTest, IsolateServiceIdSent) { TEST_F(EmbedderTest, CanCreateAndCollectCallbacks) { auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("platform_messages_response"); context.AddNativeCallback( "SignalNativeTest", @@ -318,6 +326,7 @@ TEST_F(EmbedderTest, PlatformMessagesCanReceiveResponse) { captures.thread_id = std::this_thread::get_id(); auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("platform_messages_response"); fml::AutoResetWaitableEvent ready; @@ -373,7 +382,7 @@ TEST_F(EmbedderTest, PlatformMessagesCanReceiveResponse) { TEST_F(EmbedderTest, PlatformMessagesCanBeSentWithoutResponseHandles) { auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - + builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("platform_messages_no_response"); const std::string message_data = "Hello but don't call me back."; @@ -418,7 +427,7 @@ TEST_F(EmbedderTest, PlatformMessagesCanBeSentWithoutResponseHandles) { TEST_F(EmbedderTest, NullPlatformMessagesCanBeSent) { auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - + builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("null_platform_messages"); fml::AutoResetWaitableEvent ready, message; @@ -460,7 +469,7 @@ TEST_F(EmbedderTest, NullPlatformMessagesCanBeSent) { TEST_F(EmbedderTest, InvalidPlatformMessages) { auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - + builder.SetSoftwareRendererConfig(); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -484,7 +493,7 @@ TEST_F(EmbedderTest, InvalidPlatformMessages) { TEST_F(EmbedderTest, VMShutsDownWhenNoEnginesInProcess) { auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); - + builder.SetSoftwareRendererConfig(); const auto launch_count = DartVM::GetVMLaunchCount(); { @@ -510,6 +519,7 @@ TEST_F(EmbedderTest, VMAndIsolateSnapshotSizesAreRedundantInAOTMode) { } auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); // The fixture sets this up correctly. Intentionally mess up the args. builder.GetProjectArgs().vm_snapshot_data_size = 0; @@ -530,6 +540,7 @@ TEST_F(EmbedderTest, MustPreventEngineLaunchWhenRequiredCompositorArgsAreAbsent) { auto& context = GetEmbedderContext(); 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; @@ -545,7 +556,10 @@ TEST_F(EmbedderTest, TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLFramebuffer) { auto& context = GetEmbedderContext(); - context.SetupCompositor(); + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint("can_composite_platform_views"); context.GetCompositor().SetRenderTargetType( EmbedderTestCompositor::RenderTargetType::kOpenGLFramebuffer); @@ -607,10 +621,6 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLFramebuffer) { latch.CountDown(); }); - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(); - builder.SetCompositor(); - builder.SetDartEntrypoint("can_composite_platform_views"); context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY( @@ -637,7 +647,10 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLFramebuffer) { TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLTexture) { auto& context = GetEmbedderContext(); - context.SetupCompositor(); + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint("can_composite_platform_views"); context.GetCompositor().SetRenderTargetType( EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); @@ -699,10 +712,6 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLTexture) { latch.CountDown(); }); - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(); - builder.SetCompositor(); - builder.SetDartEntrypoint("can_composite_platform_views"); context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY( @@ -729,7 +738,10 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLTexture) { TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToSoftwareBuffer) { auto& context = GetEmbedderContext(); - context.SetupCompositor(); + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint("can_composite_platform_views"); context.GetCompositor().SetRenderTargetType( EmbedderTestCompositor::RenderTargetType::kSoftwareBuffer); @@ -791,10 +803,6 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToSoftwareBuffer) { latch.CountDown(); }); - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(); - builder.SetCompositor(); - builder.SetDartEntrypoint("can_composite_platform_views"); context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY( @@ -944,7 +952,10 @@ static bool ImageMatchesFixture(const std::string& fixture_file_name, TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownScene) { auto& context = GetEmbedderContext(); - context.SetupCompositor(); + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); context.GetCompositor().SetRenderTargetType( EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); @@ -1083,10 +1094,6 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownScene) { return surface->makeImageSnapshot(); }); - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(); - builder.SetCompositor(); - builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY( @@ -1120,7 +1127,10 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownSceneWithSoftwareCompositor) { auto& context = GetEmbedderContext(); - context.SetupCompositor(); + EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); context.GetCompositor().SetRenderTargetType( EmbedderTestCompositor::RenderTargetType::kSoftwareBuffer); @@ -1261,10 +1271,6 @@ TEST_F(EmbedderTest, return surface->makeImageSnapshot(); }); - EmbedderConfigBuilder builder(context); - builder.SetSoftwareRendererConfig(); - builder.SetCompositor(); - builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY( @@ -1297,6 +1303,12 @@ TEST_F(EmbedderTest, TEST_F(EmbedderTest, CustomCompositorMustWorkWithCustomTaskRunner) { auto& context = GetEmbedderContext(); + 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; @@ -1311,8 +1323,6 @@ TEST_F(EmbedderTest, CustomCompositorMustWorkWithCustomTaskRunner) { ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); }); - context.SetupCompositor(); - context.GetCompositor().SetRenderTargetType( EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); @@ -1376,12 +1386,8 @@ TEST_F(EmbedderTest, CustomCompositorMustWorkWithCustomTaskRunner) { const auto task_runner_description = test_task_runner.GetFlutterTaskRunnerDescription(); - EmbedderConfigBuilder builder(context); - builder.SetPlatformTaskRunner(&task_runner_description); - builder.SetOpenGLRendererConfig(); - builder.SetCompositor(); - builder.SetDartEntrypoint("can_composite_platform_views"); + context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY( @@ -1422,7 +1428,11 @@ TEST_F(EmbedderTest, CustomCompositorMustWorkWithCustomTaskRunner) { TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithRootLayerOnly) { auto& context = GetEmbedderContext(); - context.SetupCompositor(); + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint( + "can_composite_platform_views_with_root_layer_only"); context.GetCompositor().SetRenderTargetType( EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); @@ -1459,11 +1469,6 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithRootLayerOnly) { latch.CountDown(); }); - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(); - builder.SetCompositor(); - builder.SetDartEntrypoint( - "can_composite_platform_views_with_root_layer_only"); context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY( @@ -1493,7 +1498,11 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithRootLayerOnly) { TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithPlatformLayerOnBottom) { auto& context = GetEmbedderContext(); - context.SetupCompositor(); + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint( + "can_composite_platform_views_with_platform_layer_on_bottom"); context.GetCompositor().SetRenderTargetType( EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); @@ -1572,11 +1581,6 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithPlatformLayerOnBottom) { return surface->makeImageSnapshot(); }); - EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(); - builder.SetCompositor(); - builder.SetDartEntrypoint( - "can_composite_platform_views_with_platform_layer_on_bottom"); context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY( @@ -1601,5 +1605,644 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithPlatformLayerOnBottom) { ASSERT_EQ(context.GetCompositor().GetBackingStoresCount(), 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(); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); + builder.SetCompositor(); + builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); + + context.GetCompositor().SetRenderTargetType( + EmbedderTestCompositor::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(6); + + sk_sp scene_image; + context.SetNextSceneCallback([&](sk_sp scene) { + scene_image = std::move(scene); + latch.CountDown(); + }); + + 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 = {}; + 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 = {}; + 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, GrContext* context) -> sk_sp { + 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; + 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(); + + EmbedderConfigBuilder builder(context); + + builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + + fml::CountDownLatch latch(1); + + sk_sp renderered_scene; + context.SetNextSceneCallback([&](auto image) { + renderered_scene = std::move(image); + latch.CountDown(); + }); + + 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; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); + + ASSERT_NE(renderered_scene, nullptr); + + ASSERT_TRUE(ImageMatchesFixture("scene_without_custom_compositor.png", + renderered_scene)); +} + +TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositorWithTransformation) { + auto& context = GetEmbedderContext(); + + 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)); + + fml::CountDownLatch latch(1); + + sk_sp renderered_scene; + context.SetNextSceneCallback([&](auto image) { + renderered_scene = std::move(image); + latch.CountDown(); + }); + + 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; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); + + ASSERT_NE(renderered_scene, nullptr); + + ASSERT_TRUE(ImageMatchesFixture( + "scene_without_custom_compositor_with_xform.png", renderered_scene)); +} + +TEST_F(EmbedderTest, CanRenderGradientWithoutCompositor) { + auto& context = GetEmbedderContext(); + + EmbedderConfigBuilder builder(context); + + builder.SetDartEntrypoint("render_gradient"); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + fml::CountDownLatch latch(1); + + sk_sp renderered_scene; + context.SetNextSceneCallback([&](auto image) { + renderered_scene = std::move(image); + latch.CountDown(); + }); + + 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; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); + + ASSERT_NE(renderered_scene, nullptr); + + ASSERT_TRUE(ImageMatchesFixture("gradient.png", renderered_scene)); +} + +TEST_F(EmbedderTest, CanRenderGradientWithoutCompositorWithXform) { + auto& context = GetEmbedderContext(); + + 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); + + fml::CountDownLatch latch(1); + + sk_sp renderered_scene; + context.SetNextSceneCallback([&](auto image) { + renderered_scene = std::move(image); + latch.CountDown(); + }); + + 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; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); + + ASSERT_NE(renderered_scene, nullptr); + + ASSERT_TRUE(ImageMatchesFixture("gradient_xform.png", renderered_scene)); +} + +TEST_F(EmbedderTest, CanRenderGradientWithCompositor) { + auto& context = GetEmbedderContext(); + + EmbedderConfigBuilder builder(context); + + builder.SetDartEntrypoint("render_gradient"); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + fml::CountDownLatch latch(1); + + sk_sp renderered_scene; + context.SetNextSceneCallback([&](auto image) { + renderered_scene = std::move(image); + latch.CountDown(); + }); + + 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; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); + + ASSERT_NE(renderered_scene, nullptr); + + ASSERT_TRUE(ImageMatchesFixture("gradient.png", renderered_scene)); +} + +TEST_F(EmbedderTest, CanRenderGradientWithCompositorWithXform) { + auto& context = GetEmbedderContext(); + + // 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(); + fml::CountDownLatch latch(1); + + sk_sp renderered_scene; + context.SetNextSceneCallback([&](auto image) { + renderered_scene = std::move(image); + latch.CountDown(); + }); + + 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; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); + + ASSERT_NE(renderered_scene, nullptr); + + ASSERT_TRUE(ImageMatchesFixture("gradient_xform.png", renderered_scene)); +} + +TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayer) { + auto& context = GetEmbedderContext(); + + EmbedderConfigBuilder builder(context); + + builder.SetDartEntrypoint("render_gradient_on_non_root_backing_store"); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + fml::CountDownLatch latch(1); + + 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(800.0, 600.0); + layer.offset = FlutterPointMake(0.0, 0.0); + + ASSERT_EQ(*layers[0], layer); + } + + // Layer 1 + { + FlutterPlatformView 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.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); + } + }); + + context.GetCompositor().SetPlatformViewRendererCallback( + [&](const FlutterLayer& layer, GrContext* context) -> sk_sp { + 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(); + }); + + sk_sp renderered_scene; + context.SetNextSceneCallback([&](auto image) { + renderered_scene = std::move(image); + latch.CountDown(); + }); + + 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; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); + + ASSERT_NE(renderered_scene, nullptr); + + ASSERT_TRUE(ImageMatchesFixture("gradient.png", renderered_scene)); +} + +TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayerWithXform) { + auto& context = GetEmbedderContext(); + + // 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(); + fml::CountDownLatch latch(1); + + 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 = {}; + 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, GrContext* context) -> sk_sp { + 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(); + }); + + sk_sp renderered_scene; + context.SetNextSceneCallback([&](auto image) { + renderered_scene = std::move(image); + latch.CountDown(); + }); + + 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; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); + + ASSERT_NE(renderered_scene, nullptr); + + ASSERT_TRUE(ImageMatchesFixture("gradient_xform.png", renderered_scene)); +} + } // namespace testing } // namespace flutter diff --git a/testing/BUILD.gn b/testing/BUILD.gn index b613afee0348c..037e7f6012fe6 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -47,6 +47,19 @@ source_set("dart") { ] } +source_set("skia") { + testonly = true + + sources = [ + "$flutter_root/testing/assertions_skia.h", + ] + + public_deps = [ + ":testing_lib", + "//third_party/skia", + ] +} + if (current_toolchain == host_toolchain) { source_set("opengl") { testonly = true @@ -59,8 +72,8 @@ if (current_toolchain == host_toolchain) { ] deps = [ + ":skia", "$flutter_root/fml", - "//third_party/skia", "//third_party/swiftshader_flutter:swiftshader", ] } diff --git a/testing/assertions_skia.h b/testing/assertions_skia.h new file mode 100644 index 0000000000000..2b501189a23ae --- /dev/null +++ b/testing/assertions_skia.h @@ -0,0 +1,79 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_TESTING_ASSERTIONS_SKIA_H_ +#define FLUTTER_TESTING_ASSERTIONS_SKIA_H_ + +#include + +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkMatrix44.h" +#include "third_party/skia/include/core/SkPoint3.h" +#include "third_party/skia/include/core/SkRRect.h" + +//------------------------------------------------------------------------------ +// Printing +//------------------------------------------------------------------------------ + +inline std::ostream& operator<<(std::ostream& os, const SkMatrix& m) { + os << std::endl; + os << "Scale X: " << m[SkMatrix::kMScaleX] << ", "; + os << "Skew X: " << m[SkMatrix::kMSkewX] << ", "; + os << "Trans X: " << m[SkMatrix::kMTransX] << std::endl; + os << "Skew Y: " << m[SkMatrix::kMSkewY] << ", "; + os << "Scale Y: " << m[SkMatrix::kMScaleY] << ", "; + os << "Trans Y: " << m[SkMatrix::kMTransY] << std::endl; + os << "Persp X: " << m[SkMatrix::kMPersp0] << ", "; + os << "Persp Y: " << m[SkMatrix::kMPersp1] << ", "; + os << "Persp Z: " << m[SkMatrix::kMPersp2]; + os << std::endl; + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const SkMatrix44& m) { + os << m.get(0, 0) << ", " << m.get(0, 1) << ", " << m.get(0, 2) << ", " + << m.get(0, 3) << std::endl; + os << m.get(1, 0) << ", " << m.get(1, 1) << ", " << m.get(1, 2) << ", " + << m.get(1, 3) << std::endl; + os << m.get(2, 0) << ", " << m.get(2, 1) << ", " << m.get(2, 2) << ", " + << m.get(2, 3) << std::endl; + os << m.get(3, 0) << ", " << m.get(3, 1) << ", " << m.get(3, 2) << ", " + << m.get(3, 3); + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const SkVector3& v) { + os << v.x() << ", " << v.y() << ", " << v.z(); + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const SkVector4& v) { + os << v.fData[0] << ", " << v.fData[1] << ", " << v.fData[2] << ", " + << v.fData[3]; + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const SkRect& r) { + os << "LTRB: " << r.fLeft << ", " << r.fTop << ", " << r.fRight << ", " + << r.fBottom; + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const SkRRect& r) { + os << "LTRB: " << r.rect().fLeft << ", " << r.rect().fTop << ", " + << r.rect().fRight << ", " << r.rect().fBottom; + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const SkPoint& r) { + os << "XY: " << r.fX << ", " << r.fY; + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const SkISize& size) { + os << size.width() << ", " << size.height(); + return os; +} + +#endif // FLUTTER_TESTING_ASSERTIONS_SKIA_H_ diff --git a/testing/test_gl_surface.cc b/testing/test_gl_surface.cc index e856ba452e429..90a9fb1f70f10 100644 --- a/testing/test_gl_surface.cc +++ b/testing/test_gl_surface.cc @@ -10,6 +10,7 @@ #include #include +#include "flutter/fml/build_config.h" #include "flutter/fml/logging.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/gl/GrGLAssembleInterface.h" @@ -79,10 +80,8 @@ static std::string GetEGLError() { return stream.str(); } -constexpr size_t kTestGLSurfaceWidth = 800; -constexpr size_t kTestGLSurfaceHeight = 600; - -TestGLSurface::TestGLSurface() { +TestGLSurface::TestGLSurface(SkISize surface_size) + : surface_size_(surface_size) { display_ = ::eglGetDisplay(EGL_DEFAULT_DISPLAY); FML_CHECK(display_ != EGL_NO_DISPLAY); @@ -113,24 +112,31 @@ TestGLSurface::TestGLSurface() { FML_CHECK(num_config == 1) << GetEGLError(); { - const EGLint surface_attributes[] = { - EGL_WIDTH, kTestGLSurfaceWidth, // - EGL_HEIGHT, kTestGLSurfaceHeight, // + const EGLint onscreen_surface_attributes[] = { + EGL_WIDTH, surface_size_.width(), // + EGL_HEIGHT, surface_size_.height(), // EGL_NONE, }; - onscreen_surface_ = - ::eglCreatePbufferSurface(display_, // display connection - config, // config - surface_attributes // surface attributes - ); + onscreen_surface_ = ::eglCreatePbufferSurface( + display_, // display connection + config, // config + onscreen_surface_attributes // surface attributes + ); FML_CHECK(onscreen_surface_ != EGL_NO_SURFACE) << GetEGLError(); + } - offscreen_surface_ = - ::eglCreatePbufferSurface(display_, // display connection - config, // config - surface_attributes // surface attributes - ); + { + const EGLint offscreen_surface_attributes[] = { + EGL_WIDTH, 1, // + EGL_HEIGHT, 1, // + EGL_NONE, + }; + offscreen_surface_ = ::eglCreatePbufferSurface( + display_, // display connection + config, // config + offscreen_surface_attributes // surface attributes + ); FML_CHECK(offscreen_surface_ != EGL_NO_SURFACE) << GetEGLError(); } @@ -178,8 +184,8 @@ TestGLSurface::~TestGLSurface() { FML_CHECK(result == EGL_TRUE); } -SkISize TestGLSurface::GetSize() const { - return SkISize::Make(kTestGLSurfaceWidth, kTestGLSurfaceHeight); +const SkISize& TestGLSurface::GetSurfaceSize() const { + return surface_size_; } bool TestGLSurface::MakeCurrent() { @@ -289,25 +295,29 @@ sk_sp TestGLSurface::CreateGrContext() { } sk_sp TestGLSurface::GetOnscreenSurface() { + FML_CHECK(::eglGetCurrentContext() != EGL_NO_CONTEXT); + GrGLFramebufferInfo framebuffer_info = {}; framebuffer_info.fFBOID = GetFramebuffer(); +#if OS_MACOSX framebuffer_info.fFormat = GR_GL_RGBA8; - - const auto size = GetSize(); +#else + framebuffer_info.fFormat = GR_GL_BGRA8; +#endif GrBackendRenderTarget backend_render_target( - size.width(), // width - size.height(), // height - 1, // sample count - 8, // stencil bits - framebuffer_info // framebuffer info + surface_size_.width(), // width + surface_size_.height(), // height + 1, // sample count + 8, // stencil bits + framebuffer_info // framebuffer info ); SkSurfaceProps surface_properties( SkSurfaceProps::InitType::kLegacyFontHost_InitType); auto surface = SkSurface::MakeFromBackendRenderTarget( - GetGrContext().get(), // context + GetGrContext().get(), // context backend_render_target, // backend render target kBottomLeft_GrSurfaceOrigin, // surface origin kN32_SkColorType, // color type diff --git a/testing/test_gl_surface.h b/testing/test_gl_surface.h index 6ba27194cf2a3..f75b0afee2c8c 100644 --- a/testing/test_gl_surface.h +++ b/testing/test_gl_surface.h @@ -15,11 +15,11 @@ namespace testing { class TestGLSurface { public: - TestGLSurface(); + TestGLSurface(SkISize surface_size); ~TestGLSurface(); - SkISize GetSize() const; + const SkISize& GetSurfaceSize() const; bool MakeCurrent(); @@ -50,6 +50,7 @@ class TestGLSurface { using EGLContext = void*; using EGLSurface = void*; + const SkISize surface_size_; EGLDisplay display_; EGLContext onscreen_context_; EGLContext offscreen_context_;