Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
02556ae
++
Jul 11, 2024
b3f6670
move all state updates to end of compositing.
Jul 11, 2024
b31aae6
working but unsynchronized.
Jul 11, 2024
75f931a
hey it almost works.
Jul 12, 2024
4525bd8
disable partial repaint.
Jul 12, 2024
6ce3aa0
cleanups.
Jul 12, 2024
be03cf8
set presentsWithTransaction.
Jul 12, 2024
6770376
sanity refactor.
Jul 12, 2024
912cd40
linting.
Jul 12, 2024
1f65cc3
skip post message if there are no platform views.
Jul 12, 2024
53adc81
linting.
Jul 12, 2024
870b950
dispose views in callback.
Jul 15, 2024
f8267bd
++
Jul 15, 2024
80565d5
Merge branch 'main' of github.com:flutter/engine into three_phase_render
Jul 16, 2024
2b9a825
tear down.
Jul 16, 2024
0a7dc5a
reserve.
Jul 17, 2024
2745e88
++
Jul 17, 2024
6002aa0
++
Jul 17, 2024
e7a832f
++
Jul 17, 2024
f099d74
come on big money no whammies.
Jul 17, 2024
e2e6a2a
okay one more.
Jul 17, 2024
44d5b5b
debug printf
Jul 17, 2024
b53063a
++
Jul 19, 2024
9a84539
make test work.
Jul 23, 2024
65a5210
++
Jul 23, 2024
735ce22
testing fixes.
Jul 24, 2024
a4f523b
Merge branch 'main' of github.com:flutter/engine into three_phase_render
Jul 24, 2024
b6c3345
merge in view slicer.
Jul 24, 2024
a4b771e
Merge branch 'main' of github.com:flutter/engine into three_phase_render
Jul 24, 2024
cd8e1de
++
Jul 25, 2024
6603d18
minor cleanups.
Jul 25, 2024
27c0daa
formatting.
Jul 25, 2024
d794211
add back clear
Jul 26, 2024
2f4314b
++
Jul 26, 2024
25a4e06
++
Jul 26, 2024
ea2e66d
merge threads on SIM.
Jul 26, 2024
d220885
++
Jul 26, 2024
9b534cd
Update shell/platform/darwin/ios/ios_external_view_embedder.mm
Jul 26, 2024
970e13a
++
Jul 26, 2024
63ebe66
Merge branch 'three_phase_render' of github.com:jonahwilliams/engine …
Jul 26, 2024
ff4802a
Merge branch 'main' of github.com:flutter/engine into three_phase_render
Jul 26, 2024
e3fc731
does it blend?
Jul 26, 2024
c0fab3b
++
Jul 26, 2024
d73671e
test fixes and clang tidy.
Jul 27, 2024
7285907
simplify and clean up thread access for UIViews.
Jul 27, 2024
bcd8519
reset before clearning composition order.
Jul 27, 2024
b25a742
Merge branch 'main' of github.com:flutter/engine into three_phase_render
Jul 30, 2024
5f6a8d4
switch to SurfaceFrame API.
Jul 30, 2024
be7853d
++
Jul 30, 2024
b19b4d8
Remove extra include.
Jul 30, 2024
ab08739
clang tidy
Jul 30, 2024
6e6e5a2
bracken feedback
Jul 30, 2024
355ab30
more bracken review.
Aug 1, 2024
8cd1c74
++
Aug 1, 2024
30e7b97
review comments and remove dead code.
Aug 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
move all state updates to end of compositing.
  • Loading branch information
jonahwilliams committed Jul 11, 2024
commit b3f6670fcf1aaf1e5cf75c9460257053851bcd8e
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
FLUTTER_ASSERT_ARC

// This is mostly a duplication of FlutterView.
// TODO(amirh): once GL support is in evaluate if we can merge this with FlutterView.
@implementation FlutterOverlayView {
fml::CFRef<CGColorSpaceRef> _colorSpaceRef;
}
Expand Down
264 changes: 147 additions & 117 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
Original file line number Diff line number Diff line change
Expand Up @@ -79,78 +79,84 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
// Becomes NO if Apple's API changes and blurred backdrop filters cannot be applied.
BOOL canApplyBlurBackdrop = YES;

bool FlutterPlatformViewLayerPool::HasLayer() const {
available_layer_index_
}

std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewLayerPool::GetLayer(
GrDirectContext* gr_context,
const std::shared_ptr<IOSContext>& ios_context,
MTLPixelFormat pixel_format) {
if (available_layer_index_ >= layers_.size()) {
//FML_DCHECK([[NSThread] isMainThread]);
std::shared_ptr<FlutterPlatformViewLayer> layer;
fml::scoped_nsobject<UIView> overlay_view;
fml::scoped_nsobject<UIView> overlay_view_wrapper;

bool impeller_enabled = !!ios_context->GetImpellerContext();
if (!gr_context && !impeller_enabled) {
overlay_view.reset([[FlutterOverlayView alloc] init]);
overlay_view_wrapper.reset([[FlutterOverlayView alloc] init]);

auto ca_layer = fml::scoped_nsobject<CALayer>{[overlay_view.get() layer]};
std::unique_ptr<IOSSurface> ios_surface = IOSSurface::Create(ios_context, ca_layer);
std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface();

layer = std::make_shared<FlutterPlatformViewLayer>(
std::move(overlay_view), std::move(overlay_view_wrapper), std::move(ios_surface),
std::move(surface));
} else {
CGFloat screenScale = [UIScreen mainScreen].scale;
overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale
pixelFormat:pixel_format]);
overlay_view_wrapper.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale
pixelFormat:pixel_format]);

auto ca_layer = fml::scoped_nsobject<CALayer>{[overlay_view.get() layer]};
std::unique_ptr<IOSSurface> ios_surface = IOSSurface::Create(ios_context, ca_layer);
std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface(gr_context);

layer = std::make_shared<FlutterPlatformViewLayer>(
std::move(overlay_view), std::move(overlay_view_wrapper), std::move(ios_surface),
std::move(surface));
MTLPixelFormat pixel_format,
bool create_if_missing) {
if (available_layer_index_ < layers_.size()) {
// TODO: Skia
std::shared_ptr<FlutterPlatformViewLayer> layer = layers_[available_layer_index_];

// This condition can only happen with the Skia backend, which is due to be removed from
// iOS in short order.
if (gr_context != layer->gr_context) {
layer->gr_context = gr_context;
// The overlay already exists, but the GrContext was changed so we need to recreate
// the rendering surface with the new GrContext.
IOSSurface* ios_surface = layer->ios_surface.get();
std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface(gr_context);
layer->surface = std::move(surface);
}
// The overlay view wrapper masks the overlay view.
// This is required to keep the backing surface size unchanged between frames.
//
// Otherwise, changing the size of the overlay would require a new surface,
// which can be very expensive.
//
// This is the case of an animation in which the overlay size is changing in every frame.
//
// +------------------------+
// | overlay_view |
// | +--------------+ | +--------------+
// | | wrapper | | == mask => | overlay_view |
// | +--------------+ | +--------------+
// +------------------------+
layer->overlay_view_wrapper.get().clipsToBounds = YES;
[layer->overlay_view_wrapper.get() addSubview:layer->overlay_view];
layers_.push_back(layer);

available_layer_index_++;
return layer;
}
std::shared_ptr<FlutterPlatformViewLayer> layer = layers_[available_layer_index_];
// This condition can only happen with the Skia backend, which is due to be removed from
// iOS in short order.
if (gr_context != layer->gr_context) {
layer->gr_context = gr_context;
// The overlay already exists, but the GrContext was changed so we need to recreate
// the rendering surface with the new GrContext.
IOSSurface* ios_surface = layer->ios_surface.get();

if (!create_if_missing) {
return nullptr;
}

std::shared_ptr<FlutterPlatformViewLayer> layer;
fml::scoped_nsobject<UIView> overlay_view;
fml::scoped_nsobject<UIView> overlay_view_wrapper;

bool impeller_enabled = !!ios_context->GetImpellerContext();
if (!gr_context && !impeller_enabled) {
overlay_view.reset([[FlutterOverlayView alloc] init]);
overlay_view_wrapper.reset([[FlutterOverlayView alloc] init]);

auto ca_layer = fml::scoped_nsobject<CALayer>{[overlay_view.get() layer]};
std::unique_ptr<IOSSurface> ios_surface = IOSSurface::Create(ios_context, ca_layer);
std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface();

layer = std::make_shared<FlutterPlatformViewLayer>(std::move(overlay_view),
std::move(overlay_view_wrapper),
std::move(ios_surface), std::move(surface));
} else {
CGFloat screenScale = [UIScreen mainScreen].scale;
overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale
pixelFormat:pixel_format]);
overlay_view_wrapper.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale
pixelFormat:pixel_format]);

auto ca_layer = fml::scoped_nsobject<CALayer>{[overlay_view.get() layer]};
std::unique_ptr<IOSSurface> ios_surface = IOSSurface::Create(ios_context, ca_layer);
std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface(gr_context);
layer->surface = std::move(surface);

layer = std::make_shared<FlutterPlatformViewLayer>(std::move(overlay_view),
std::move(overlay_view_wrapper),
std::move(ios_surface), std::move(surface));
layer->gr_context = gr_context;
}
available_layer_index_++;
// The overlay view wrapper masks the overlay view.
// This is required to keep the backing surface size unchanged between frames.
//
// Otherwise, changing the size of the overlay would require a new surface,
// which can be very expensive.
//
// This is the case of an animation in which the overlay size is changing in every frame.
//
// +------------------------+
// | overlay_view |
// | +--------------+ | +--------------+
// | | wrapper | | == mask => | overlay_view |
// | +--------------+ | +--------------+
// +------------------------+
layer->overlay_view_wrapper.get().clipsToBounds = YES;
[layer->overlay_view_wrapper.get() addSubview:layer->overlay_view];
layers_.push_back(layer);

return layer;
}

Expand Down Expand Up @@ -682,8 +688,8 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
auto did_submit = true;
auto num_platform_views = composition_order_.size();

// TODO(hellohuanlin) this double for-loop is expensive with wasted computations.
// See: https://github.com/flutter/flutter/issues/145802
size_t missing_layer_count = 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this design, if we're missing an overlay layer then it won't render until the next frame. I think we can handle that by forcing resubmission of the frame but I'll have to follow up on it.


for (size_t i = 0; i < num_platform_views; i++) {
int64_t platform_view_id = composition_order_[i];
EmbedderViewSlice* slice = slices_[platform_view_id].get();
Expand All @@ -693,7 +699,8 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
// current platform view or any of the previous platform views.
for (size_t j = i + 1; j > 0; j--) {
int64_t current_platform_view_id = composition_order_[j - 1];
SkRect platform_view_rect = current_composition_params_[current_platform_view_id].finalBoundingRect();
SkRect platform_view_rect =
current_composition_params_[current_platform_view_id].finalBoundingRect();
std::vector<SkIRect> intersection_rects = slice->region(platform_view_rect).getRects();
const SkIRect rounded_in_platform_view_rect = platform_view_rect.roundIn();
// Ignore intersections of single width/height on the edge of the platform view.
Expand Down Expand Up @@ -724,9 +731,6 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,

// If the max number of allocations per platform view is exceeded,
// then join all the rects into a single one.
//
// TODO(egarciad): Consider making this configurable.
// https://github.com/flutter/flutter/issues/52510
if (allocation_size > kMaxLayerAllocations) {
SkIRect joined_rect = SkIRect::MakeEmpty();
for (const SkIRect& rect : intersection_rects) {
Expand All @@ -746,14 +750,40 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
background_canvas->ClipRect(SkRect::Make(joined_rect), DlCanvas::ClipOp::kDifference);
// Get a new host layer.
std::shared_ptr<FlutterPlatformViewLayer> layer =
GetLayer(gr_context, //
ios_context, //
slice, //
joined_rect, //
current_platform_view_id, //
overlay_id, //
((FlutterView*)flutter_view_.get()).pixelFormat //
GetExistingLayer(gr_context, //
ios_context, //
MTLPixelFormatBGRA10_XR //
);
if (!layer) {
missing_layer_count++;
continue;
}

layer->view_id = current_platform_view_id;
layer->overlay_id = overlay_id;
layer->rect = joined_rect;

{
std::unique_ptr<SurfaceFrame> frame = layer->surface->AcquireFrame(frame_size_);
// If frame is null, AcquireFrame already printed out an error message.
if (!frame) {
continue;
}
DlCanvas* overlay_canvas = frame->Canvas();
int restore_count = overlay_canvas->GetSaveCount();
overlay_canvas->Save();
overlay_canvas->ClipRect(SkRect::Make(joined_rect));
// overlay_canvas->Clear(DlColor::kTransparent());
slice->render_into(overlay_canvas);
overlay_canvas->RestoreToCount(restore_count);

// This flutter view is never the last in a frame, since we always submit the
// underlay view last.
frame->set_submit_info({.frame_boundary = false});

layer->did_submit_last_frame = frame->Submit();
}

did_submit &= layer->did_submit_last_frame;
platform_view_layers[current_platform_view_id].push_back(layer);
overlay_id++;
Expand All @@ -773,6 +803,25 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
CompositeWithParams(view_id, current_composition_params_[view_id]);
}

// Configure Flutter overlay views.
for (const auto& [key, layers] : platform_view_layers) {
for (const auto& layer : layers) {
layer->UpdateViewState(flutter_view_);
}
}

// Create Missing Layers
for (auto i = 0u; i < missing_layer_count; i++) {
auto layer = GetOrCreateLayer(gr_context, //
ios_context, //
MTLPixelFormatBGRA10_XR //
);
layer->did_submit_last_frame = true;
}
if (missing_layer_count > 0) {
FML_LOG(ERROR) << "Created " << missing_layer_count;
}

// If a layer was allocated in the previous frame, but it's not used in the current frame,
// then it can be removed from the scene.
RemoveUnusedLayers();
Expand Down Expand Up @@ -825,29 +874,31 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
}
}



bool HasPlatformViewLayerAlready(GrDirectContext* gr_context,
const std::shared_ptr<IOSContext>& ios_context,
MTLPixelFormat pixel_format) {
bool FlutterPlatformViewsController::HasPlatformViewLayerAlready(
GrDirectContext* gr_context,
const std::shared_ptr<IOSContext>& ios_context,
MTLPixelFormat pixel_format) {
std::shared_ptr<FlutterPlatformViewLayer> layer =
layer_pool_->GetLayer(gr_context, ios_context, pixel_format);
layer_pool_->GetLayer(gr_context, ios_context, pixel_format, /*create_if_missing=*/false);
return !!layer;
}

std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewsController::GetExistingLayer(
GrDirectContext* gr_context,
const std::shared_ptr<IOSContext>& ios_context,
MTLPixelFormat pixel_format) {
return layer_pool_->GetLayer(gr_context, ios_context, pixel_format, /*create_if_missing=*/false);
}

std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewsController::GetLayer(
std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewsController::GetOrCreateLayer(
GrDirectContext* gr_context,
const std::shared_ptr<IOSContext>& ios_context,
EmbedderViewSlice* slice,
SkIRect rect,
int64_t view_id,
int64_t overlay_id,
MTLPixelFormat pixel_format) {
FML_DCHECK(flutter_view_);
std::shared_ptr<FlutterPlatformViewLayer> layer =
layer_pool_->GetLayer(gr_context, ios_context, pixel_format);
return layer_pool_->GetLayer(gr_context, ios_context, pixel_format, /*create_if_missing=*/true);
}

UIView* overlay_view_wrapper = layer->overlay_view_wrapper.get();
void FlutterPlatformViewLayer::UpdateViewState(UIView* flutter_view) {
UIView* overlay_view_wrapper = this->overlay_view_wrapper.get();
auto screenScale = [UIScreen mainScreen].scale;
// Set the size of the overlay view wrapper.
// This wrapper view masks the overlay view.
Expand All @@ -857,34 +908,13 @@ bool HasPlatformViewLayerAlready(GrDirectContext* gr_context,
overlay_view_wrapper.accessibilityIdentifier =
[NSString stringWithFormat:@"platform_view[%lld].overlay[%lld]", view_id, overlay_id];

UIView* overlay_view = layer->overlay_view.get();
UIView* overlay_view = this->overlay_view.get();
// Set the size of the overlay view.
// This size is equal to the device screen size.
overlay_view.frame = [flutter_view_.get() convertRect:flutter_view_.get().bounds
toView:overlay_view_wrapper];
overlay_view.frame = [flutter_view convertRect:flutter_view.bounds toView:overlay_view_wrapper];
// Set a unique view identifier, so the overlay_view can be identified in XCUITests.
overlay_view.accessibilityIdentifier =
[NSString stringWithFormat:@"platform_view[%lld].overlay_view[%lld]", view_id, overlay_id];

std::unique_ptr<SurfaceFrame> frame = layer->surface->AcquireFrame(frame_size_);
// If frame is null, AcquireFrame already printed out an error message.
if (!frame) {
return layer;
}
DlCanvas* overlay_canvas = frame->Canvas();
int restore_count = overlay_canvas->GetSaveCount();
overlay_canvas->Save();
overlay_canvas->ClipRect(SkRect::Make(rect));
overlay_canvas->Clear(DlColor::kTransparent());
slice->render_into(overlay_canvas);
overlay_canvas->RestoreToCount(restore_count);

// This flutter view is never the last in a frame, since we always submit the
// underlay view last.
frame->set_submit_info({.frame_boundary = false});

layer->did_submit_last_frame = frame->Submit();
return layer;
}

void FlutterPlatformViewsController::RemoveUnusedLayers() {
Expand Down Expand Up @@ -934,9 +964,9 @@ bool HasPlatformViewLayerAlready(GrDirectContext* gr_context,
views_to_dispose_ = std::move(views_to_delay_dispose);
}

void FlutterPlatformViewsController::BeginCATransaction() { }
void FlutterPlatformViewsController::BeginCATransaction() {}

void FlutterPlatformViewsController::CommitCATransactionIfNeeded() { }
void FlutterPlatformViewsController::CommitCATransactionIfNeeded() {}

void FlutterPlatformViewsController::ResetFrameState() {
slices_.clear();
Expand Down
Loading