未验证 提交 ef9e7b1a 编写于 作者: C Chinmay Garde 提交者: GitHub

Revert "Lift restriction that embedders may not trample the render thread...

Revert "Lift restriction that embedders may not trample the render thread OpenGL context in composition callbacks. (#16653)" (#16674)

This reverts commit 110c1c9a.
上级 40e3ab12
......@@ -126,8 +126,6 @@ FILE: ../../../flutter/fml/file_unittest.cc
FILE: ../../../flutter/fml/gpu_thread_merger.cc
FILE: ../../../flutter/fml/gpu_thread_merger.h
FILE: ../../../flutter/fml/gpu_thread_merger_unittests.cc
FILE: ../../../flutter/fml/hash_combine.h
FILE: ../../../flutter/fml/hash_combine_unittests.cc
FILE: ../../../flutter/fml/icu_util.cc
FILE: ../../../flutter/fml/icu_util.h
FILE: ../../../flutter/fml/log_level.h
......@@ -915,20 +913,15 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_engine.cc
FILE: ../../../flutter/shell/platform/embedder/embedder_engine.h
FILE: ../../../flutter/shell/platform/embedder/embedder_external_texture_gl.cc
FILE: ../../../flutter/shell/platform/embedder/embedder_external_texture_gl.h
FILE: ../../../flutter/shell/platform/embedder/embedder_external_view.cc
FILE: ../../../flutter/shell/platform/embedder/embedder_external_view.h
FILE: ../../../flutter/shell/platform/embedder/embedder_external_view_embedder.cc
FILE: ../../../flutter/shell/platform/embedder/embedder_external_view_embedder.h
FILE: ../../../flutter/shell/platform/embedder/embedder_include.c
FILE: ../../../flutter/shell/platform/embedder/embedder_include2.c
FILE: ../../../flutter/shell/platform/embedder/embedder_layers.cc
FILE: ../../../flutter/shell/platform/embedder/embedder_layers.h
FILE: ../../../flutter/shell/platform/embedder/embedder_platform_message_response.cc
FILE: ../../../flutter/shell/platform/embedder/embedder_platform_message_response.h
FILE: ../../../flutter/shell/platform/embedder/embedder_render_target.cc
FILE: ../../../flutter/shell/platform/embedder/embedder_render_target.h
FILE: ../../../flutter/shell/platform/embedder/embedder_render_target_cache.cc
FILE: ../../../flutter/shell/platform/embedder/embedder_render_target_cache.h
FILE: ../../../flutter/shell/platform/embedder/embedder_safe_access.h
FILE: ../../../flutter/shell/platform/embedder/embedder_surface.cc
FILE: ../../../flutter/shell/platform/embedder/embedder_surface.h
......
......@@ -26,7 +26,6 @@ source_set("fml") {
"file.h",
"gpu_thread_merger.cc",
"gpu_thread_merger.h",
"hash_combine.h",
"icu_util.cc",
"icu_util.h",
"log_level.h",
......@@ -233,7 +232,6 @@ executable("fml_unittests") {
"command_line_unittest.cc",
"file_unittest.cc",
"gpu_thread_merger_unittests.cc",
"hash_combine_unittests.cc",
"memory/ref_counted_unittest.cc",
"memory/weak_ptr_unittest.cc",
"message_loop_task_queues_merge_unmerge_unittests.cc",
......
// 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_FML_HASH_COMBINE_H_
#define FLUTTER_FML_HASH_COMBINE_H_
#include <functional>
namespace fml {
template <class Type>
constexpr void HashCombineSeed(std::size_t& seed, Type arg) {
seed ^= std::hash<Type>{}(arg) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
template <class Type, class... Rest>
constexpr void HashCombineSeed(std::size_t& seed,
Type arg,
Rest... other_args) {
HashCombineSeed(seed, arg);
HashCombineSeed(seed, other_args...);
}
constexpr std::size_t HashCombine() {
return 0xdabbad00;
}
template <class... Type>
constexpr std::size_t HashCombine(Type... args) {
std::size_t seed = HashCombine();
HashCombineSeed(seed, args...);
return seed;
}
} // namespace fml
#endif // FLUTTER_FML_HASH_COMBINE_H_
// 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/fml/hash_combine.h"
#include "flutter/testing/testing.h"
namespace fml {
namespace testing {
TEST(HashCombineTest, CanHash) {
ASSERT_EQ(HashCombine(), HashCombine());
ASSERT_EQ(HashCombine("Hello"), HashCombine("Hello"));
ASSERT_NE(HashCombine("Hello"), HashCombine("World"));
ASSERT_EQ(HashCombine("Hello", "World"), HashCombine("Hello", "World"));
ASSERT_NE(HashCombine("World", "Hello"), HashCombine("Hello", "World"));
ASSERT_EQ(HashCombine(12u), HashCombine(12u));
ASSERT_NE(HashCombine(12u), HashCombine(12.0f));
ASSERT_EQ(HashCombine('a'), HashCombine('a'));
}
} // namespace testing
} // namespace fml
......@@ -26,8 +26,8 @@ class CanvasSpy {
public:
CanvasSpy(SkCanvas* target_canvas);
//----------------------------------------------------------------------------
/// @brief Returns true if any non transparent content has been drawn
//------------------------------------------------------------------------------
/// @brief Returns true if any non trasnparent content has been drawn
/// into
/// the spying canvas. Note that this class does tries to detect
/// empty canvases but in some cases may return true even for
......@@ -35,7 +35,7 @@ class CanvasSpy {
/// canvas).
bool DidDrawIntoCanvas();
//----------------------------------------------------------------------------
//------------------------------------------------------------------------------
/// @brief The returned canvas delegate all operations to the target
/// canvas
/// while spying on them.
......
......@@ -29,20 +29,15 @@ template("embedder_source_set") {
"embedder_engine.h",
"embedder_external_texture_gl.cc",
"embedder_external_texture_gl.h",
"embedder_external_view.cc",
"embedder_external_view.h",
"embedder_external_view_embedder.cc",
"embedder_external_view_embedder.h",
"embedder_include.c",
"embedder_include2.c",
"embedder_layers.cc",
"embedder_layers.h",
"embedder_platform_message_response.cc",
"embedder_platform_message_response.h",
"embedder_render_target.cc",
"embedder_render_target.h",
"embedder_render_target_cache.cc",
"embedder_render_target_cache.h",
"embedder_safe_access.h",
"embedder_surface.cc",
"embedder_surface.h",
......
// 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/shell/platform/embedder/embedder_external_view.h"
#include "flutter/fml/trace_event.h"
#include "flutter/shell/common/canvas_spy.h"
namespace flutter {
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());
}
EmbedderExternalView::EmbedderExternalView(
const SkISize& frame_size,
const SkMatrix& surface_transformation)
: EmbedderExternalView(frame_size, surface_transformation, {}, nullptr) {}
EmbedderExternalView::EmbedderExternalView(
const SkISize& frame_size,
const SkMatrix& surface_transformation,
ViewIdentifier view_identifier,
std::unique_ptr<EmbeddedViewParams> params)
: render_surface_size_(
TransformedSurfaceSize(frame_size, surface_transformation)),
surface_transformation_(surface_transformation),
view_identifier_(view_identifier),
embedded_view_params_(std::move(params)),
recorder_(std::make_unique<SkPictureRecorder>()),
canvas_spy_(std::make_unique<CanvasSpy>(
recorder_->beginRecording(frame_size.width(), frame_size.height()))) {
}
EmbedderExternalView::~EmbedderExternalView() = default;
EmbedderExternalView::RenderTargetDescriptor
EmbedderExternalView::CreateRenderTargetDescriptor() const {
return {render_surface_size_};
}
SkCanvas* EmbedderExternalView::GetCanvas() const {
return canvas_spy_->GetSpyingCanvas();
}
SkISize EmbedderExternalView::GetRenderSurfaceSize() const {
return render_surface_size_;
}
bool EmbedderExternalView::IsRootView() const {
return !HasPlatformView();
}
bool EmbedderExternalView::HasPlatformView() const {
return view_identifier_.platform_view_id.has_value();
}
bool EmbedderExternalView::HasEngineRenderedContents() const {
return canvas_spy_->DidDrawIntoCanvas();
}
EmbedderExternalView::ViewIdentifier EmbedderExternalView::GetViewIdentifier()
const {
return view_identifier_;
}
const EmbeddedViewParams* EmbedderExternalView::GetEmbeddedViewParams() const {
return embedded_view_params_.get();
}
bool EmbedderExternalView::Render(const EmbedderRenderTarget& render_target) {
TRACE_EVENT0("flutter", "EmbedderExternalView::Render");
FML_DCHECK(HasEngineRenderedContents())
<< "Unnecessarily asked to render into a render target when there was "
"nothing to render.";
auto picture = recorder_->finishRecordingAsPicture();
if (!picture) {
return false;
}
auto surface = render_target.GetRenderSurface();
if (!surface) {
return false;
}
FML_DCHECK(SkISize::Make(surface->width(), surface->height()) ==
render_surface_size_);
auto canvas = surface->getCanvas();
if (!canvas) {
return false;
}
canvas->setMatrix(surface_transformation_);
canvas->clear(SK_ColorTRANSPARENT);
canvas->drawPicture(picture);
canvas->flush();
return true;
}
} // namespace flutter
// 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_SHELL_PLATFORM_EMBEDDER_EMBEDDER_EXTERNAL_VIEW_H_
#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_EXTERNAL_VIEW_H_
#include <optional>
#include <unordered_map>
#include <unordered_set>
#include "flutter/flow/embedded_views.h"
#include "flutter/fml/hash_combine.h"
#include "flutter/fml/macros.h"
#include "flutter/shell/common/canvas_spy.h"
#include "flutter/shell/platform/embedder/embedder_render_target.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
namespace flutter {
class EmbedderExternalView {
public:
using PlatformViewID = int64_t;
struct ViewIdentifier {
std::optional<PlatformViewID> platform_view_id;
ViewIdentifier() {}
ViewIdentifier(PlatformViewID view_id) : platform_view_id(view_id) {}
struct Hash {
constexpr std::size_t operator()(const ViewIdentifier& desc) const {
if (!desc.platform_view_id.has_value()) {
return fml::HashCombine();
}
return fml::HashCombine(desc.platform_view_id.value());
}
};
struct Equal {
constexpr bool operator()(const ViewIdentifier& lhs,
const ViewIdentifier& rhs) const {
return lhs.platform_view_id == rhs.platform_view_id;
}
};
};
struct RenderTargetDescriptor {
SkISize surface_size = SkISize::MakeEmpty();
struct Hash {
constexpr std::size_t operator()(
const RenderTargetDescriptor& desc) const {
return fml::HashCombine(desc.surface_size.width(),
desc.surface_size.height());
}
};
struct Equal {
bool operator()(const RenderTargetDescriptor& lhs,
const RenderTargetDescriptor& rhs) const {
return lhs.surface_size == rhs.surface_size;
}
};
};
using ViewIdentifierSet = std::unordered_set<ViewIdentifier,
ViewIdentifier::Hash,
ViewIdentifier::Equal>;
using PendingViews = std::unordered_map<ViewIdentifier,
std::unique_ptr<EmbedderExternalView>,
ViewIdentifier::Hash,
ViewIdentifier::Equal>;
EmbedderExternalView(const SkISize& frame_size,
const SkMatrix& surface_transformation);
EmbedderExternalView(const SkISize& frame_size,
const SkMatrix& surface_transformation,
ViewIdentifier view_identifier,
std::unique_ptr<EmbeddedViewParams> params);
~EmbedderExternalView();
bool IsRootView() const;
bool HasPlatformView() const;
bool HasEngineRenderedContents() const;
ViewIdentifier GetViewIdentifier() const;
const EmbeddedViewParams* GetEmbeddedViewParams() const;
RenderTargetDescriptor CreateRenderTargetDescriptor() const;
SkCanvas* GetCanvas() const;
SkISize GetRenderSurfaceSize() const;
bool Render(const EmbedderRenderTarget& render_target);
private:
const SkISize render_surface_size_;
const SkMatrix surface_transformation_;
ViewIdentifier view_identifier_;
std::unique_ptr<EmbeddedViewParams> embedded_view_params_;
std::unique_ptr<SkPictureRecorder> recorder_;
std::unique_ptr<CanvasSpy> canvas_spy_;
FML_DISALLOW_COPY_AND_ASSIGN(EmbedderExternalView);
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_EXTERNAL_VIEW_H_
......@@ -8,7 +8,6 @@
#include "flutter/shell/platform/embedder/embedder_layers.h"
#include "flutter/shell/platform/embedder/embedder_render_target.h"
#include "third_party/skia/include/gpu/GrContext.h"
namespace flutter {
......@@ -37,7 +36,9 @@ SkMatrix EmbedderExternalViewEmbedder::GetSurfaceTransformation() const {
}
void EmbedderExternalViewEmbedder::Reset() {
pending_views_.clear();
pending_recorders_.clear();
pending_canvas_spies_.clear();
pending_params_.clear();
composition_order_.clear();
}
......@@ -46,6 +47,25 @@ void EmbedderExternalViewEmbedder::CancelFrame() {
Reset();
}
static FlutterBackingStoreConfig MakeBackingStoreConfig(
const SkISize& backing_store_size) {
FlutterBackingStoreConfig config = {};
config.struct_size = sizeof(config);
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,
......@@ -56,203 +76,212 @@ void EmbedderExternalViewEmbedder::BeginFrame(SkISize frame_size,
pending_device_pixel_ratio_ = device_pixel_ratio;
pending_surface_transformation_ = GetSurfaceTransformation();
static const auto kRootViewIdentifier =
EmbedderExternalView::ViewIdentifier{};
pending_views_[kRootViewIdentifier] = std::make_unique<EmbedderExternalView>(
const auto surface_size = TransformedSurfaceSize(
pending_frame_size_, pending_surface_transformation_);
composition_order_.push_back(kRootViewIdentifier);
// Decide if we want to discard the previous root render target.
if (root_render_target_) {
auto surface = root_render_target_->GetRenderSurface();
// This is unlikely to happen but the embedder could have given the
// rasterizer a render target the previous frame that Skia could not
// materialize into a renderable surface. Discard the target and try again.
if (!surface) {
root_render_target_ = nullptr;
} else {
auto last_surface_size =
SkISize::Make(surface->width(), surface->height());
if (surface_size != last_surface_size) {
root_render_target_ = nullptr;
}
}
}
// If there is no root render target, create one now.
// TODO(43778): This should now be moved to be later in the submit call.
if (!root_render_target_) {
root_render_target_ = create_render_target_callback_(
context, MakeBackingStoreConfig(surface_size));
}
root_picture_recorder_ = std::make_unique<SkPictureRecorder>();
root_picture_recorder_->beginRecording(pending_frame_size_.width(),
pending_frame_size_.height());
}
// |ExternalViewEmbedder|
void EmbedderExternalViewEmbedder::PrerollCompositeEmbeddedView(
int view_id,
std::unique_ptr<EmbeddedViewParams> params) {
FML_DCHECK(pending_views_.count(view_id) == 0);
pending_views_[view_id] = std::make_unique<EmbedderExternalView>(
pending_frame_size_, // frame size
pending_surface_transformation_, // surface xformation
view_id, // view identifier
std::move(params) // embedded view params
);
FML_DCHECK(pending_recorders_.count(view_id) == 0);
FML_DCHECK(pending_canvas_spies_.count(view_id) == 0);
FML_DCHECK(pending_params_.count(view_id) == 0);
FML_DCHECK(std::find(composition_order_.begin(), composition_order_.end(),
view_id) == composition_order_.end());
pending_recorders_[view_id] = std::make_unique<SkPictureRecorder>();
SkCanvas* recording_canvas = pending_recorders_[view_id]->beginRecording(
pending_frame_size_.width(), pending_frame_size_.height());
pending_canvas_spies_[view_id] =
std::make_unique<CanvasSpy>(recording_canvas);
pending_params_[view_id] = *params;
composition_order_.push_back(view_id);
}
// |ExternalViewEmbedder|
SkCanvas* EmbedderExternalViewEmbedder::GetRootCanvas() {
auto found = pending_views_.find(EmbedderExternalView::ViewIdentifier{});
if (found == pending_views_.end()) {
FML_DLOG(WARNING)
<< "No root canvas could be found. This is extremely unlikely and "
"indicates that the external view embedder did not receive the "
"notification to begin the frame.";
return nullptr;
}
return found->second->GetCanvas();
}
// |ExternalViewEmbedder|
std::vector<SkCanvas*> EmbedderExternalViewEmbedder::GetCurrentCanvases() {
std::vector<SkCanvas*> canvases;
for (const auto& view : pending_views_) {
const auto& external_view = view.second;
// This method (for legacy reasons) expects non-root current canvases.
if (!external_view->IsRootView()) {
canvases.push_back(external_view->GetCanvas());
}
for (const auto& spy : pending_canvas_spies_) {
canvases.push_back(spy.second->GetSpyingCanvas());
}
return canvases;
}
// |ExternalViewEmbedder|
SkCanvas* EmbedderExternalViewEmbedder::CompositeEmbeddedView(int view_id) {
auto found = pending_views_.find(view_id);
if (found == pending_views_.end()) {
auto found = pending_canvas_spies_.find(view_id);
if (found == pending_canvas_spies_.end()) {
FML_DCHECK(false) << "Attempted to composite a view that was not "
"pre-rolled.";
return nullptr;
}
return found->second->GetCanvas();
return found->second->GetSpyingCanvas();
}
static FlutterBackingStoreConfig MakeBackingStoreConfig(
const SkISize& backing_store_size) {
FlutterBackingStoreConfig config = {};
bool EmbedderExternalViewEmbedder::RenderPictureToRenderTarget(
sk_sp<SkPicture> picture,
const EmbedderRenderTarget* render_target) const {
if (!picture || render_target == nullptr) {
return false;
}
config.struct_size = sizeof(config);
auto render_surface = render_target->GetRenderSurface();
config.size.width = backing_store_size.width();
config.size.height = backing_store_size.height();
if (!render_surface) {
return false;
}
return config;
auto render_canvas = render_surface->getCanvas();
if (render_canvas == nullptr) {
return false;
}
render_canvas->setMatrix(pending_surface_transformation_);
render_canvas->clear(SK_ColorTRANSPARENT);
render_canvas->drawPicture(picture);
render_canvas->flush();
return true;
}
// |ExternalViewEmbedder|
bool EmbedderExternalViewEmbedder::SubmitFrame(GrContext* context) {
auto [matched_render_targets, pending_keys] =
render_target_cache_.GetExistingTargetsInCache(pending_views_);
// This is where unused render targets will be collected. Control may flow to
// the embedder. Here, the embedder has the opportunity to trample on the
// OpenGL context.
//
// We tell the render target cache to clear its unused entries before
// allocating new ones. This collection step before allocating new render
// targets ameliorates peak memory usage within the frame.
//
// @warning: Embedder may trample on our OpenGL context here.
render_target_cache_.ClearAllRenderTargetsInCache();
for (const auto& pending_key : pending_keys) {
const auto& external_view = pending_views_.at(pending_key);
// If the external view does not have engine rendered contents, it makes no
// sense to ask to embedder to create a render target for us as we don't
// intend to render into it and ask the embedder for presentation anyway.
// Save some memory.
if (!external_view->HasEngineRenderedContents()) {
Registry render_targets_used;
EmbedderLayers presented_layers(pending_frame_size_,
pending_device_pixel_ratio_,
pending_surface_transformation_);
if (!root_render_target_) {
FML_LOG(ERROR)
<< "Could not acquire the root render target from the embedder.";
return false;
}
// Copy the contents of the root picture recorder onto the root surface.
if (!RenderPictureToRenderTarget(
root_picture_recorder_->finishRecordingAsPicture(),
root_render_target_.get())) {
FML_LOG(ERROR) << "Could not render into the root render target.";
return false;
}
// The root picture recorder will be reset when a new frame begins.
root_picture_recorder_.reset();
{
// The root surface is expressed as a layer.
presented_layers.PushBackingStoreLayer(
root_render_target_->GetBackingStore());
}
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);
FML_DCHECK(pending_params_.count(view_id) == 1);
const auto& params = pending_params_.at(view_id);
auto& recorder = pending_recorders_.at(view_id);
auto picture = recorder->finishRecordingAsPicture();
if (!picture) {
FML_LOG(ERROR) << "Could not finish recording into the picture before "
"on-screen composition.";
return false;
}
// Tell the embedder that a platform view layer is present at this point.
presented_layers.PushPlatformViewLayer(view_id, params);
if (!pending_canvas_spies_.at(view_id)->DidDrawIntoCanvas()) {
// Nothing was drawn into the overlay canvas, we don't need to tell the
// embedder to composite it.
continue;
}
// This is the size of render surface we want the embedder to create for
// us. As or right now, this is going to always be equal to the frame size
// post transformation. But, in case optimizations are applied that make
// it so that embedder rendered into surfaces that aren't full screen,
// this assumption will break. So it's just best to ask view for its size
// directly.
const auto render_surface_size = external_view->GetRenderSurfaceSize();
const auto backing_store_config =
MakeBackingStoreConfig(render_surface_size);
// This is where the embedder will create render targets for us. Control
// flow to the embedder makes the engine susceptible to having the embedder
// trample on the OpenGL context. Before any Skia operations are performed,
// the context must be reset.
//
// @warning: Embedder may trample on our OpenGL context here.
auto render_target =
create_render_target_callback_(context, backing_store_config);
const auto backing_store_config = MakeBackingStoreConfig(surface_size);
RegistryKey registry_key(view_id, backing_store_config);
auto found_render_target = registry_.find(registry_key);
// Find a cached render target in the registry. If none exists, ask the
// embedder for a new one.
std::shared_ptr<EmbedderRenderTarget> render_target;
if (found_render_target == registry_.end()) {
render_target =
create_render_target_callback_(context, backing_store_config);
} else {
render_target = found_render_target->second;
}
if (!render_target) {
FML_LOG(ERROR) << "Embedder did not return a valid render target.";
FML_LOG(ERROR) << "Could not acquire external render target for "
"on-screen composition.";
return false;
}
matched_render_targets[pending_key] = std::move(render_target);
}
// The OpenGL context could have been trampled by the embedder at this point
// as it attempted to collect old render targets and create new ones. Tell
// Skia to not rely on existing bindings.
if (context) {
context->resetContext(kAll_GrBackendState);
}
render_targets_used[registry_key] = render_target;
// Scribble embedder provide render targets. The order in which we scribble
// into the buffers is irrelevant to the presentation order.
for (const auto& render_target : matched_render_targets) {
if (!pending_views_.at(render_target.first)
->Render(*render_target.second)) {
FML_LOG(ERROR)
<< "Could not render into the embedder supplied render target.";
if (!RenderPictureToRenderTarget(picture, render_target.get())) {
FML_LOG(ERROR) << "Could not render into the render target for platform "
"view of identifier "
<< view_id;
return false;
}
}
// We are going to be transferring control back over to the embedder there the
// context may be trampled upon again. Flush all operations to the underlying
// rendering API.
//
// @warning: Embedder may trample on our OpenGL context here.
if (context) {
context->flush();
// Indicate a layer for the backing store containing contents rendered by
// Flutter.
presented_layers.PushBackingStoreLayer(render_target->GetBackingStore());
}
// Submit the scribbled layer to the embedder for presentation.
//
// @warning: Embedder may trample on our OpenGL context here.
{
EmbedderLayers presented_layers(pending_frame_size_,
pending_device_pixel_ratio_,
pending_surface_transformation_);
// In composition order, submit backing stores and platform views to the
// embedder.
for (const auto& view_id : composition_order_) {
// If the external view has a platform view, ask the emebdder to place it
// before the Flutter rendered contents for that interleaving level.
const auto& external_view = pending_views_.at(view_id);
if (external_view->HasPlatformView()) {
presented_layers.PushPlatformViewLayer(
external_view->GetViewIdentifier()
.platform_view_id.value(), // view id
*external_view->GetEmbeddedViewParams() // view params
);
}
// Flush the layer description down to the embedder for presentation.
presented_layers.InvokePresentCallback(present_callback_);
// If the view has engine rendered contents, ask the embedder to place
// Flutter rendered contents for this interleaving level on top of a
// platform view.
if (external_view->HasEngineRenderedContents()) {
const auto& exteral_render_target = matched_render_targets.at(view_id);
presented_layers.PushBackingStoreLayer(
exteral_render_target->GetBackingStore());
}
}
// Keep the previously used render target around in case they are required
// next frame.
registry_ = std::move(render_targets_used);
// Flush the layer description down to the embedder for presentation.
//
// @warning: Embedder may trample on our OpenGL context here.
presented_layers.InvokePresentCallback(present_callback_);
}
return true;
}
// Hold all rendered layers in the render target cache for one frame to see if
// they may be reused next frame.
for (auto& render_target : matched_render_targets) {
render_target_cache_.CacheRenderTarget(std::move(render_target.second));
// |ExternalViewEmbedder|
SkCanvas* EmbedderExternalViewEmbedder::GetRootCanvas() {
if (!root_picture_recorder_) {
return nullptr;
}
return true;
return root_picture_recorder_->getRecordingCanvas();
}
} // namespace flutter
......@@ -9,10 +9,10 @@
#include <unordered_map>
#include "flutter/flow/embedded_views.h"
#include "flutter/fml/hash_combine.h"
#include "flutter/fml/macros.h"
#include "flutter/shell/platform/embedder/embedder_external_view.h"
#include "flutter/shell/platform/embedder/embedder_render_target_cache.h"
#include "flutter/shell/common/canvas_spy.h"
#include "flutter/shell/platform/embedder/embedder_render_target.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
namespace flutter {
......@@ -95,20 +95,59 @@ class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder {
SkCanvas* GetRootCanvas() override;
private:
using ViewIdentifier = int64_t;
struct RegistryKey {
ViewIdentifier view_identifier = 0;
SkISize size = SkISize::Make(0, 0);
RegistryKey(ViewIdentifier view_identifier,
const FlutterBackingStoreConfig& config)
: view_identifier(view_identifier),
size(SkISize::Make(config.size.width, config.size.height)) {}
struct Hash {
constexpr std::size_t operator()(RegistryKey const& key) const {
return key.view_identifier;
};
};
struct Equal {
constexpr bool operator()(const RegistryKey& lhs,
const RegistryKey& rhs) const {
return lhs.view_identifier == rhs.view_identifier &&
lhs.size == rhs.size;
}
};
};
const CreateRenderTargetCallback create_render_target_callback_;
const PresentCallback present_callback_;
SurfaceTransformationCallback surface_transformation_callback_;
using Registry = std::unordered_map<RegistryKey,
std::shared_ptr<EmbedderRenderTarget>,
RegistryKey::Hash,
RegistryKey::Equal>;
SkISize pending_frame_size_ = SkISize::Make(0, 0);
double pending_device_pixel_ratio_ = 1.0;
SkMatrix pending_surface_transformation_;
EmbedderExternalView::PendingViews pending_views_;
std::vector<EmbedderExternalView::ViewIdentifier> composition_order_;
EmbedderRenderTargetCache render_target_cache_;
std::map<ViewIdentifier, std::unique_ptr<SkPictureRecorder>>
pending_recorders_;
std::map<ViewIdentifier, std::unique_ptr<CanvasSpy>> pending_canvas_spies_;
std::map<ViewIdentifier, EmbeddedViewParams> pending_params_;
std::vector<ViewIdentifier> composition_order_;
std::shared_ptr<EmbedderRenderTarget> root_render_target_;
std::unique_ptr<SkPictureRecorder> root_picture_recorder_;
Registry registry_;
void Reset();
SkMatrix GetSurfaceTransformation() const;
bool RenderPictureToRenderTarget(
sk_sp<SkPicture> picture,
const EmbedderRenderTarget* render_target) const;
FML_DISALLOW_COPY_AND_ASSIGN(EmbedderExternalViewEmbedder);
};
......
// 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/shell/platform/embedder/embedder.h"
// This file is the same as embedder_include.c and ensures that static methods
// don't end up in public API header. This will cause duplicate symbols when the
// header in imported in the multiple translation units in the embedder.
......@@ -195,8 +195,9 @@ void EmbedderLayers::PushPlatformViewLayer(
layer.size.height = transformed_layer_bounds.height();
presented_layers_.push_back(layer);
}
} // namespace flutter
/// @note Procedure doesn't copy all closures.
void EmbedderLayers::InvokePresentCallback(
const PresentCallback& callback) const {
std::vector<const FlutterLayer*> presented_layers_pointers;
......
// 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/shell/platform/embedder/embedder_render_target_cache.h"
namespace flutter {
EmbedderRenderTargetCache::EmbedderRenderTargetCache() = default;
EmbedderRenderTargetCache::~EmbedderRenderTargetCache() = default;
std::pair<EmbedderRenderTargetCache::RenderTargets,
EmbedderExternalView::ViewIdentifierSet>
EmbedderRenderTargetCache::GetExistingTargetsInCache(
const EmbedderExternalView::PendingViews& pending_views) {
RenderTargets resolved_render_targets;
EmbedderExternalView::ViewIdentifierSet unmatched_identifiers;
for (const auto& view : pending_views) {
const auto& external_view = view.second;
if (!external_view->HasEngineRenderedContents()) {
continue;
}
auto& compatible_targets =
cached_render_targets_[external_view->CreateRenderTargetDescriptor()];
if (compatible_targets.size() == 0) {
unmatched_identifiers.insert(view.first);
} else {
std::unique_ptr<EmbedderRenderTarget> target =
std::move(compatible_targets.top());
compatible_targets.pop();
resolved_render_targets[view.first] = std::move(target);
}
}
return {std::move(resolved_render_targets), std::move(unmatched_identifiers)};
}
void EmbedderRenderTargetCache::ClearAllRenderTargetsInCache() {
cached_render_targets_.clear();
}
void EmbedderRenderTargetCache::CacheRenderTarget(
std::unique_ptr<EmbedderRenderTarget> target) {
if (target == nullptr) {
return;
}
auto surface = target->GetRenderSurface();
EmbedderExternalView::RenderTargetDescriptor desc{
SkISize::Make(surface->width(), surface->height())};
cached_render_targets_[desc].push(std::move(target));
}
size_t EmbedderRenderTargetCache::GetCachedTargetsCount() const {
size_t count = 0;
for (const auto& targets : cached_render_targets_) {
count += targets.second.size();
}
return count;
}
} // namespace flutter
// 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_SHELL_PLATFORM_EMBEDDER_EMBEDDER_RENDER_TARGET_CACHE_H_
#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_RENDER_TARGET_CACHE_H_
#include <stack>
#include <tuple>
#include <unordered_map>
#include "flutter/fml/macros.h"
#include "flutter/shell/platform/embedder/embedder_external_view.h"
namespace flutter {
//------------------------------------------------------------------------------
/// @brief A cache used to reference render targets that are owned by the
/// embedder but needed by th engine to render a frame.
///
class EmbedderRenderTargetCache {
public:
EmbedderRenderTargetCache();
~EmbedderRenderTargetCache();
using RenderTargets =
std::unordered_map<EmbedderExternalView::ViewIdentifier,
std::unique_ptr<EmbedderRenderTarget>,
EmbedderExternalView::ViewIdentifier::Hash,
EmbedderExternalView::ViewIdentifier::Equal>;
std::pair<RenderTargets, EmbedderExternalView::ViewIdentifierSet>
GetExistingTargetsInCache(
const EmbedderExternalView::PendingViews& pending_views);
void ClearAllRenderTargetsInCache();
void CacheRenderTarget(std::unique_ptr<EmbedderRenderTarget> target);
size_t GetCachedTargetsCount() const;
private:
using CachedRenderTargets =
std::unordered_map<EmbedderExternalView::RenderTargetDescriptor,
std::stack<std::unique_ptr<EmbedderRenderTarget>>,
EmbedderExternalView::RenderTargetDescriptor::Hash,
EmbedderExternalView::RenderTargetDescriptor::Equal>;
CachedRenderTargets cached_render_targets_;
FML_DISALLOW_COPY_AND_ASSIGN(EmbedderRenderTargetCache);
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_RENDER_TARGET_CACHE_H_
......@@ -689,43 +689,3 @@ void objects_can_be_posted() {
signalNativeCount(port.sendPort.nativePort);
}
@pragma('vm:entry-point')
void empty_scene_posts_zero_layers_to_compositor() {
window.onBeginFrame = (Duration duration) {
SceneBuilder builder = SceneBuilder();
// Should not render anything.
builder.pushClipRect(Rect.fromLTRB(0.0, 0.0, 300.0, 200.0));
window.render(builder.build());
};
window.scheduleFrame();
}
@pragma('vm:entry-point')
void compositor_can_post_only_platform_views() {
window.onBeginFrame = (Duration duration) {
SceneBuilder builder = SceneBuilder();
builder.addPlatformView(42, width: 300.0, height: 200.0);
builder.addPlatformView(24, width: 300.0, height: 200.0);
window.render(builder.build());
};
window.scheduleFrame();
}
@pragma('vm:entry-point')
void render_targets_are_recycled() {
int frame_count = 0;
window.onBeginFrame = (Duration duration) {
SceneBuilder builder = SceneBuilder();
for (int i = 0; i < 10; i++) {
builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(30.0, 20.0)));
builder.addPlatformView(42 + i, width: 30.0, height: 20.0);
}
window.render(builder.build());
window.scheduleFrame();
frame_count++;
if (frame_count == 8) {
signalNativeTest();
}
};
window.scheduleFrame();
}
......@@ -24,14 +24,6 @@ void EmbedderTestCompositor::SetRenderTargetType(RenderTargetType type) {
type_ = type;
}
static void InvokeAllCallbacks(const std::vector<fml::closure>& callbacks) {
for (const auto& callback : callbacks) {
if (callback) {
callback();
}
}
}
bool EmbedderTestCompositor::CreateBackingStore(
const FlutterBackingStoreConfig* config,
FlutterBackingStore* backing_store_out) {
......@@ -51,8 +43,7 @@ bool EmbedderTestCompositor::CreateBackingStore(
return false;
}
if (success) {
backing_stores_created_++;
InvokeAllCallbacks(on_create_render_target_callbacks_);
backing_stores_count_++;
}
return success;
}
......@@ -63,8 +54,7 @@ bool EmbedderTestCompositor::CollectBackingStore(
// stores. Our user_data is just the canvas from that backing store and does
// not need to be explicitly collected. Embedders might have some other state
// they want to collect though.
backing_stores_collected_++;
InvokeAllCallbacks(on_collect_render_target_callbacks_);
backing_stores_count_--;
return true;
}
......@@ -178,7 +168,6 @@ bool EmbedderTestCompositor::Present(const FlutterLayer** layers,
callback(layers, layers_count);
}
InvokeAllCallbacks(on_present_callbacks_);
return true;
}
......@@ -196,6 +185,7 @@ bool EmbedderTestCompositor::CreateFramebufferRenderSurface(
kBottomLeft_GrSurfaceOrigin, // surface origin
nullptr, // surface properties
false // mipmaps
);
if (!surface) {
......@@ -332,31 +322,8 @@ void EmbedderTestCompositor::SetPlatformViewRendererCallback(
platform_view_renderer_callback_ = callback;
}
size_t EmbedderTestCompositor::GetPendingBackingStoresCount() const {
FML_CHECK(backing_stores_created_ >= backing_stores_collected_);
return backing_stores_created_ - backing_stores_collected_;
}
size_t EmbedderTestCompositor::GetBackingStoresCreatedCount() const {
return backing_stores_created_;
}
size_t EmbedderTestCompositor::GetBackingStoresCollectedCount() const {
return backing_stores_collected_;
}
void EmbedderTestCompositor::AddOnCreateRenderTargetCallback(
fml::closure callback) {
on_create_render_target_callbacks_.push_back(callback);
}
void EmbedderTestCompositor::AddOnCollectRenderTargetCallback(
fml::closure callback) {
on_collect_render_target_callbacks_.push_back(callback);
}
void EmbedderTestCompositor::AddOnPresentCallback(fml::closure callback) {
on_present_callbacks_.push_back(callback);
size_t EmbedderTestCompositor::GetBackingStoresCount() const {
return backing_stores_count_;
}
} // namespace testing
......
......@@ -7,7 +7,6 @@
#include <vector>
#include "flutter/fml/closure.h"
#include "flutter/fml/macros.h"
#include "flutter/shell/platform/embedder/embedder.h"
#include "third_party/skia/include/gpu/GrContext.h"
......@@ -58,17 +57,7 @@ class EmbedderTestCompositor {
sk_sp<SkImage> GetLastComposition();
size_t GetPendingBackingStoresCount() const;
size_t GetBackingStoresCreatedCount() const;
size_t GetBackingStoresCollectedCount() const;
void AddOnCreateRenderTargetCallback(fml::closure callback);
void AddOnCollectRenderTargetCallback(fml::closure callback);
void AddOnPresentCallback(fml::closure callback);
size_t GetBackingStoresCount() const;
private:
const SkISize surface_size_;
......@@ -78,11 +67,8 @@ class EmbedderTestCompositor {
PresentCallback next_present_callback_;
NextSceneCallback next_scene_callback_;
sk_sp<SkImage> last_composition_;
size_t backing_stores_created_ = 0;
size_t backing_stores_collected_ = 0;
std::vector<fml::closure> on_create_render_target_callbacks_;
std::vector<fml::closure> on_collect_render_target_callbacks_;
std::vector<fml::closure> on_present_callbacks_;
// The number of currently allocated backing stores (created - collected).
size_t backing_stores_count_ = 0;
bool UpdateOffscrenComposition(const FlutterLayer** layers,
size_t layers_count);
......
......@@ -1793,7 +1793,7 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithPlatformLayerOnBottom) {
ASSERT_TRUE(ImageMatchesFixture(
"compositor_with_platform_layer_on_bottom.png", scene_image));
ASSERT_EQ(context.GetCompositor().GetPendingBackingStoresCount(), 1u);
ASSERT_EQ(context.GetCompositor().GetBackingStoresCount(), 1u);
}
//------------------------------------------------------------------------------
......@@ -2397,11 +2397,26 @@ TEST_F(EmbedderTest, VerifyB141980393) {
context.GetCompositor().SetNextPresentCallback(
[&](const FlutterLayer** layers, size_t layers_count) {
ASSERT_EQ(layers_count, 1u);
ASSERT_EQ(layers_count, 2u);
// Layer Root
{
FlutterPlatformView platform_view = *layers[0]->platform_view;
FlutterLayer layer = {};
FlutterBackingStore backing_store = *layers[0]->backing_store;
layer.backing_store = &backing_store;
layer.struct_size = sizeof(layer);
layer.type = kFlutterLayerContentTypeBackingStore;
// Our root surface has been rotated.
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 = 1337;
......@@ -2443,7 +2458,7 @@ TEST_F(EmbedderTest, VerifyB141980393) {
layer.size = FlutterSizeMake(xformed_platform_view_rect.width(),
xformed_platform_view_rect.height());
ASSERT_EQ(*layers[0], layer);
ASSERT_EQ(*layers[1], layer);
}
latch.Signal();
......@@ -3529,10 +3544,10 @@ TEST_F(EmbedderTest, ClipsAreCorrectlyCalculated) {
fml::AutoResetWaitableEvent latch;
context.GetCompositor().SetNextPresentCallback(
[&](const FlutterLayer** layers, size_t layers_count) {
ASSERT_EQ(layers_count, 2u);
ASSERT_EQ(layers_count, 3u);
{
FlutterPlatformView platform_view = *layers[0]->platform_view;
FlutterPlatformView platform_view = *layers[1]->platform_view;
platform_view.struct_size = sizeof(platform_view);
platform_view.identifier = 42;
......@@ -3543,16 +3558,16 @@ TEST_F(EmbedderTest, ClipsAreCorrectlyCalculated) {
layer.size = FlutterSizeMake(300.0, 400.0);
layer.offset = FlutterPointMake(0.0, 0.0);
ASSERT_EQ(*layers[0], layer);
ASSERT_EQ(*layers[1], 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);
GetTotalMutationTransformationMatrix(layers[1]->platform_view);
FilterMutationsByType(
layers[0]->platform_view,
layers[1]->platform_view,
kFlutterPlatformViewMutationTypeClipRect,
[&](const auto& mutation) {
FlutterRect clip = mutation.clip_rect;
......@@ -3606,10 +3621,10 @@ TEST_F(EmbedderTest, ComplexClipsAreCorrectlyCalculated) {
fml::AutoResetWaitableEvent latch;
context.GetCompositor().SetNextPresentCallback(
[&](const FlutterLayer** layers, size_t layers_count) {
ASSERT_EQ(layers_count, 2u);
ASSERT_EQ(layers_count, 3u);
{
FlutterPlatformView platform_view = *layers[0]->platform_view;
FlutterPlatformView platform_view = *layers[1]->platform_view;
platform_view.struct_size = sizeof(platform_view);
platform_view.identifier = 42;
......@@ -3620,7 +3635,7 @@ TEST_F(EmbedderTest, ComplexClipsAreCorrectlyCalculated) {
layer.size = FlutterSizeMake(600.0, 1024.0);
layer.offset = FlutterPointMake(0.0, -256.0);
ASSERT_EQ(*layers[0], layer);
ASSERT_EQ(*layers[1], layer);
const auto** mutations = platform_view.mutations;
......@@ -3991,146 +4006,5 @@ TEST_F(EmbedderTest, CanPostTaskToAllNativeThreads) {
ASSERT_FALSE(engine.is_valid());
}
TEST_F(EmbedderTest, CompositorCanPostZeroLayersForPresentation) {
auto& context = GetEmbedderContext();
EmbedderConfigBuilder builder(context);
builder.SetOpenGLRendererConfig(SkISize::Make(300, 200));
builder.SetCompositor();
builder.SetDartEntrypoint("empty_scene_posts_zero_layers_to_compositor");
context.GetCompositor().SetRenderTargetType(
EmbedderTestCompositor::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();
EmbedderConfigBuilder builder(context);
builder.SetOpenGLRendererConfig(SkISize::Make(300, 200));
builder.SetCompositor();
builder.SetDartEntrypoint("compositor_can_post_only_platform_views");
context.GetCompositor().SetRenderTargetType(
EmbedderTestCompositor::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();
EmbedderConfigBuilder builder(context);
builder.SetOpenGLRendererConfig(SkISize::Make(300, 200));
builder.SetCompositor();
builder.SetDartEntrypoint("render_targets_are_recycled");
context.GetCompositor().SetRenderTargetType(
EmbedderTestCompositor::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);
}
} // namespace testing
} // namespace flutter
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册