Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[macOS] Implement unobstructed platform views #42960

Merged
merged 5 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@
info.surface = surface;
info.offset = CGPointMake(layer->offset.x, layer->offset.y);
info.zIndex = i;
FlutterBackingStorePresentInfo* present_info = layer->backing_store_present_info;
if (present_info != nullptr && present_info->paint_region != nullptr) {
auto paint_region = present_info->paint_region;
// Safe because the size of FlutterRect is not expected to change.
info.paintRegion = std::vector<FlutterRect>(
paint_region->rects, paint_region->rects + paint_region->rects_count);
knopp marked this conversation as resolved.
Show resolved Hide resolved
}
[surfaces addObject:info];
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>

#include <vector>

#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurface.h"

/**
Expand All @@ -15,6 +17,7 @@
@property(readwrite, strong, nonatomic, nonnull) FlutterSurface* surface;
@property(readwrite, nonatomic) CGPoint offset;
@property(readwrite, nonatomic) size_t zIndex;
@property(readwrite, nonatomic) std::vector<FlutterRect> paintRegion;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ @interface FlutterSurfaceManager () {

// Currently visible layers.
NSMutableArray<CALayer*>* _layers;

// Whether to highlight borders of overlay surfaces. Determined by
// FLTEnableSurfaceDebugInfo value in main bundle Info.plist.
NSNumber* _enableSurfaceDebugInfo;
knopp marked this conversation as resolved.
Show resolved Hide resolved
CATextLayer* _infoLayer;
}

/**
Expand All @@ -38,6 +43,60 @@ - (void)commit:(NSArray<FlutterSurfacePresentInfo*>*)surfaces;

@end

static NSColor* GetBorderColorForLayer(int layer) {
NSArray* colors = @[
[NSColor yellowColor],
[NSColor cyanColor],
[NSColor magentaColor],
[NSColor greenColor],
[NSColor purpleColor],
[NSColor orangeColor],
[NSColor blueColor],
];
return colors[layer % colors.count];
}

/// Creates sublayers for given layer, each one displaying a portion of the
/// of the surface determined by a rectangle in the provided paint region.
static void UpdateContentSubLayers(CALayer* layer,
knopp marked this conversation as resolved.
Show resolved Hide resolved
IOSurfaceRef surface,
CGFloat scale,
CGSize surfaceSize,
NSColor* borderColor,
const std::vector<FlutterRect>& paintRegion) {
// Adjust sublayer count to paintRegion count.
while (layer.sublayers.count > paintRegion.size()) {
knopp marked this conversation as resolved.
Show resolved Hide resolved
cbracken marked this conversation as resolved.
Show resolved Hide resolved
[layer.sublayers.lastObject removeFromSuperlayer];
}

while (layer.sublayers.count < paintRegion.size()) {
CALayer* newLayer = [CALayer layer];
[layer addSublayer:newLayer];
}

for (size_t i = 0; i < paintRegion.size(); i++) {
CALayer* subLayer = [layer.sublayers objectAtIndex:i];
const auto& rect = paintRegion[i];
subLayer.frame = CGRectMake(rect.left / scale, rect.top / scale,
(rect.right - rect.left) / scale, (rect.bottom - rect.top) / scale);

double width = surfaceSize.width;
double height = surfaceSize.height;

subLayer.contentsRect =
CGRectMake(rect.left / width, rect.top / height, (rect.right - rect.left) / width,
(rect.bottom - rect.top) / height);

if (borderColor != nil) {
// Visualize sublayer
subLayer.borderColor = borderColor.CGColor;
subLayer.borderWidth = 1.0;
}

subLayer.contents = (__bridge id)surface;
}
}

@implementation FlutterSurfaceManager

- (instancetype)initWithDevice:(id<MTLDevice>)device
Expand Down Expand Up @@ -77,6 +136,17 @@ - (FlutterSurface*)surfaceForSize:(CGSize)size {
return surface;
}

- (BOOL)enableSurfaceDebugInfo {
cbracken marked this conversation as resolved.
Show resolved Hide resolved
if (_enableSurfaceDebugInfo == nil) {
_enableSurfaceDebugInfo =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTEnableSurfaceDebugInfo"];
if (_enableSurfaceDebugInfo == nil) {
_enableSurfaceDebugInfo = @NO;
}
}
return [_enableSurfaceDebugInfo boolValue];
}

- (void)commit:(NSArray<FlutterSurfacePresentInfo*>*)surfaces {
FML_DCHECK([NSThread isMainThread]);

Expand All @@ -100,16 +170,38 @@ - (void)commit:(NSArray<FlutterSurfacePresentInfo*>*)surfaces {
[_layers addObject:layer];
}

bool enableSurfaceDebugInfo = self.enableSurfaceDebugInfo;

// Update contents of surfaces.
for (size_t i = 0; i < surfaces.count; ++i) {
FlutterSurfacePresentInfo* info = surfaces[i];
CALayer* layer = _layers[i];
CGFloat scale = _containingLayer.contentsScale;
layer.frame = CGRectMake(info.offset.x / scale, info.offset.y / scale,
info.surface.size.width / scale, info.surface.size.height / scale);
layer.contents = (__bridge id)info.surface.ioSurface;
if (i == 0) {
layer.frame = CGRectMake(info.offset.x / scale, info.offset.y / scale,
info.surface.size.width / scale, info.surface.size.height / scale);
layer.contents = (__bridge id)info.surface.ioSurface;
} else {
layer.frame = CGRectZero;
NSColor* borderColor = enableSurfaceDebugInfo ? GetBorderColorForLayer(i - 1) : nil;
UpdateContentSubLayers(layer, info.surface.ioSurface, scale, info.surface.size, borderColor,
info.paintRegion);
}
layer.zPosition = info.zIndex;
}

if (enableSurfaceDebugInfo) {
if (_infoLayer == nil) {
_infoLayer = [[CATextLayer alloc] init];
[_containingLayer addSublayer:_infoLayer];
_infoLayer.fontSize = 15;
_infoLayer.foregroundColor = [NSColor yellowColor].CGColor;
_infoLayer.frame = CGRectMake(15, 15, 300, 100);
_infoLayer.contentsScale = _containingLayer.contentsScale;
_infoLayer.zPosition = 100000;
}
_infoLayer.string = [NSString stringWithFormat:@"Surface count: %li", _layers.count];
}
}

static CGSize GetRequiredFrameSize(NSArray<FlutterSurfacePresentInfo*>* surfaces) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ - (instancetype)init {
self = [super initWithFrame:NSZeroRect];
if (self) {
[self setWantsLayer:YES];
self.layer.contentsScale = 2.0;
}
return self;
}
Expand All @@ -48,13 +49,16 @@ - (void)onPresent:(CGSize)frameSize withBlock:(nonnull dispatch_block_t)block {
delegate:testView];
}

static FlutterSurfacePresentInfo* CreatePresentInfo(FlutterSurface* surface,
CGPoint offset = CGPointZero,
size_t index = 0) {
static FlutterSurfacePresentInfo* CreatePresentInfo(
FlutterSurface* surface,
CGPoint offset = CGPointZero,
size_t index = 0,
const std::vector<FlutterRect>& paintRegion = {}) {
FlutterSurfacePresentInfo* res = [[FlutterSurfacePresentInfo alloc] init];
res.surface = surface;
res.offset = offset;
res.zIndex = index;
res.paintRegion = paintRegion;
return res;
}

Expand Down Expand Up @@ -160,6 +164,10 @@ - (void)onPresent:(CGSize)frameSize withBlock:(nonnull dispatch_block_t)block {
EXPECT_EQ(surface3, surface1);
}

inline bool operator==(const CGRect& lhs, const CGRect& rhs) {
return CGRectEqualToRect(lhs, rhs);
}

TEST(FlutterSurfaceManager, LayerManagement) {
TestView* testView = [[TestView alloc] init];
FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
Expand All @@ -176,15 +184,68 @@ - (void)onPresent:(CGSize)frameSize withBlock:(nonnull dispatch_block_t)block {
auto surface2_2 = [surfaceManager surfaceForSize:CGSizeMake(20, 20)];
[surfaceManager present:@[
CreatePresentInfo(surface2_1, CGPointMake(20, 10), 1),
CreatePresentInfo(surface2_2, CGPointMake(40, 50), 2)
CreatePresentInfo(surface2_2, CGPointMake(40, 50), 2,
{
FlutterRect{0, 0, 20, 20},
FlutterRect{40, 0, 60, 20},
})
]
notify:nil];

EXPECT_EQ(testView.layer.sublayers.count, 2ul);
EXPECT_EQ([testView.layer.sublayers objectAtIndex:0].zPosition, 1.0);
EXPECT_EQ([testView.layer.sublayers objectAtIndex:1].zPosition, 2.0);
EXPECT_EQ(testView.layer.sublayers[0].zPosition, 1.0);
EXPECT_EQ(testView.layer.sublayers[1].zPosition, 2.0);
CALayer* firstOverlaySublayer;
{
NSArray<CALayer*>* sublayers = testView.layer.sublayers[1].sublayers;
EXPECT_EQ(sublayers.count, 2ul);
EXPECT_TRUE(CGRectEqualToRect(sublayers[0].frame, CGRectMake(0, 0, 10, 10)));
EXPECT_TRUE(CGRectEqualToRect(sublayers[1].frame, CGRectMake(20, 0, 10, 10)));
EXPECT_TRUE(CGRectEqualToRect(sublayers[0].contentsRect, CGRectMake(0, 0, 1, 1)));
EXPECT_TRUE(CGRectEqualToRect(sublayers[1].contentsRect, CGRectMake(2, 0, 1, 1)));
EXPECT_EQ(sublayers[0].contents, sublayers[1].contents);
firstOverlaySublayer = sublayers[0];
}
EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(70, 70)));

// Check second overlay sublayer is removed while first is reused and updated
[surfaceManager present:@[
CreatePresentInfo(surface2_1, CGPointMake(20, 10), 1),
CreatePresentInfo(surface2_2, CGPointMake(40, 50), 2,
{
FlutterRect{0, 10, 20, 20},
})
]
notify:nil];
EXPECT_EQ(testView.layer.sublayers.count, 2ul);
{
NSArray<CALayer*>* sublayers = testView.layer.sublayers[1].sublayers;
EXPECT_EQ(sublayers.count, 1ul);
EXPECT_EQ(sublayers[0], firstOverlaySublayer);
EXPECT_TRUE(CGRectEqualToRect(sublayers[0].frame, CGRectMake(0, 5, 10, 5)));
}

// Check that second overlay sublayer is added back while first is reused and updated
[surfaceManager present:@[
CreatePresentInfo(surface2_1, CGPointMake(20, 10), 1),
CreatePresentInfo(surface2_2, CGPointMake(40, 50), 2,
{
FlutterRect{0, 0, 20, 20},
FlutterRect{40, 0, 60, 20},
})
]
notify:nil];

EXPECT_EQ(testView.layer.sublayers.count, 2ul);
{
NSArray<CALayer*>* sublayers = testView.layer.sublayers[1].sublayers;
EXPECT_EQ(sublayers.count, 2ul);
EXPECT_EQ(sublayers[0], firstOverlaySublayer);
EXPECT_TRUE(CGRectEqualToRect(sublayers[0].frame, CGRectMake(0, 0, 10, 10)));
EXPECT_TRUE(CGRectEqualToRect(sublayers[1].frame, CGRectMake(20, 0, 10, 10)));
EXPECT_EQ(sublayers[0].contents, sublayers[1].contents);
}

auto surface3_1 = [surfaceManager surfaceForSize:CGSizeMake(50, 30)];
[surfaceManager present:@[ CreatePresentInfo(surface3_1, CGPointMake(20, 10)) ] notify:nil];

Expand Down
25 changes: 25 additions & 0 deletions shell/platform/embedder/embedder.h
Original file line number Diff line number Diff line change
Expand Up @@ -1663,6 +1663,27 @@ typedef enum {
kFlutterLayerContentTypePlatformView,
} FlutterLayerContentType;

/// A region represented by a collection of non-overlapping rectangles.
typedef struct {
/// The size of this struct. Must be sizeof(FlutterRegion).
size_t struct_size;
/// Number of rectangles in the region.
size_t rects_count;
/// The rectangles that make up the region.
FlutterRect* rects;
} FlutterRegion;

/// Contains additional information about the backing store provided
/// during presentation to the embedder.
typedef struct {
size_t struct_size;

/// The area of the backing store that contains Flutter contents. Pixels
/// outside of this area are transparent and the embedder may choose not
/// to render them. Coordinates are in physical pixels.
FlutterRegion* paint_region;
} FlutterBackingStorePresentInfo;

typedef struct {
/// This size of this struct. Must be sizeof(FlutterLayer).
size_t struct_size;
Expand All @@ -1682,6 +1703,10 @@ typedef struct {
FlutterPoint offset;
/// The size of the layer (in physical pixels).
FlutterSize size;

/// Extra information for the backing store that the embedder may
/// use during presentation.
FlutterBackingStorePresentInfo* backing_store_present_info;
cbracken marked this conversation as resolved.
Show resolved Hide resolved
} FlutterLayer;

typedef bool (*FlutterBackingStoreCreateCallback)(
Expand Down
5 changes: 5 additions & 0 deletions shell/platform/embedder/embedder_external_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ bool EmbedderExternalView::HasPlatformView() const {
return view_identifier_.platform_view_id.has_value();
}

std::list<SkRect> EmbedderExternalView::GetEngineRenderedContentsRegion(
const SkRect& query) const {
return slice_->searchNonOverlappingDrawnRects(query);
}

bool EmbedderExternalView::HasEngineRenderedContents() {
if (has_engine_rendered_contents_.has_value()) {
return has_engine_rendered_contents_.value();
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/embedder/embedder_external_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ class EmbedderExternalView {

bool Render(const EmbedderRenderTarget& render_target);

std::list<SkRect> GetEngineRenderedContentsRegion(const SkRect& query) const;

private:
// End the recording of the slice.
// Noop if the slice's recording has already ended.
Expand Down
11 changes: 10 additions & 1 deletion shell/platform/embedder/embedder_external_view_embedder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,17 @@ void EmbedderExternalViewEmbedder::SubmitFrame(
// platform view.
if (external_view->HasEngineRenderedContents()) {
const auto& exteral_render_target = matched_render_targets.at(view_id);
const auto& external_view = pending_views_.at(view_id);
auto rect_list =
external_view->GetEngineRenderedContentsRegion(SkRect::MakeIWH(
pending_frame_size_.width(), pending_frame_size_.height()));
std::vector<SkIRect> rects;
knopp marked this conversation as resolved.
Show resolved Hide resolved
rects.reserve(rect_list.size());
for (const auto& rect : rect_list) {
rects.push_back(rect.roundOut());
}
presented_layers.PushBackingStoreLayer(
exteral_render_target->GetBackingStore());
exteral_render_target->GetBackingStore(), rects);
}
}

Expand Down
31 changes: 30 additions & 1 deletion shell/platform/embedder/embedder_layers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ EmbedderLayers::EmbedderLayers(SkISize frame_size,

EmbedderLayers::~EmbedderLayers() = default;

void EmbedderLayers::PushBackingStoreLayer(const FlutterBackingStore* store) {
void EmbedderLayers::PushBackingStoreLayer(
const FlutterBackingStore* store,
const std::vector<SkIRect>& paint_region_vec) {
FlutterLayer layer = {};

layer.struct_size = sizeof(FlutterLayer);
Expand All @@ -35,6 +37,33 @@ void EmbedderLayers::PushBackingStoreLayer(const FlutterBackingStore* store) {
layer.size.width = transformed_layer_bounds.width();
layer.size.height = transformed_layer_bounds.height();

auto paint_region_rects = std::make_unique<std::vector<FlutterRect>>();
paint_region_rects->reserve(paint_region_vec.size());

for (const auto& rect : paint_region_vec) {
auto transformed_rect =
root_surface_transformation_.mapRect(SkRect::Make(rect));
paint_region_rects->push_back(FlutterRect{
cbracken marked this conversation as resolved.
Show resolved Hide resolved
transformed_rect.x(),
transformed_rect.y(),
transformed_rect.right(),
transformed_rect.bottom(),
});
}

auto paint_region = std::make_unique<FlutterRegion>();
paint_region->struct_size = sizeof(FlutterRegion);
paint_region->rects = paint_region_rects->data();
paint_region->rects_count = paint_region_rects->size();
rects_referenced_.push_back(std::move(paint_region_rects));

auto present_info = std::make_unique<FlutterBackingStorePresentInfo>();
present_info->struct_size = sizeof(FlutterBackingStorePresentInfo);
present_info->paint_region = paint_region.get();
regions_referenced_.push_back(std::move(paint_region));
layer.backing_store_present_info = present_info.get();

present_info_referenced_.push_back(std::move(present_info));
presented_layers_.push_back(layer);
}

Expand Down
Loading