-
Notifications
You must be signed in to change notification settings - Fork 6k
Platform View implemenation for Metal #11070
Changes from all commits
c322ee9
051504f
98d3a08
62d52a5
388c6e0
0fe03a7
0644a99
5be14d4
d0a15e0
2a87e08
d672d53
edb557b
0664f40
64dea80
9f465ec
a1be9dc
57d78a3
90e9e71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,22 +27,27 @@ Animator::Animator(Delegate& delegate, | |
waiter_(std::move(waiter)), | ||
last_begin_frame_time_(), | ||
dart_frame_deadline_(0), | ||
#if FLUTTER_SHELL_ENABLE_METAL | ||
layer_tree_pipeline_(fml::MakeRefCounted<LayerTreePipeline>(2)), | ||
#else // FLUTTER_SHELL_ENABLE_METAL | ||
// TODO(dnfield): We should remove this logic and set the pipeline depth | ||
// back to 2 in this case. See https://github.com/flutter/engine/pull/9132 | ||
// for discussion. | ||
// back to 2 in this case. See | ||
// https://github.com/flutter/engine/pull/9132 for discussion. | ||
layer_tree_pipeline_(fml::MakeRefCounted<LayerTreePipeline>( | ||
task_runners.GetPlatformTaskRunner() == | ||
task_runners.GetGPUTaskRunner() | ||
? 1 | ||
: 2)), | ||
#endif // FLUTTER_SHELL_ENABLE_METAL | ||
pending_frame_semaphore_(1), | ||
frame_number_(1), | ||
paused_(false), | ||
regenerate_layer_tree_(false), | ||
frame_scheduled_(false), | ||
notify_idle_task_id_(0), | ||
dimension_change_pending_(false), | ||
weak_factory_(this) {} | ||
weak_factory_(this) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: This may require a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is how clang-format is putting it - maybe it's confused by the preprocessor macros? |
||
} | ||
|
||
Animator::~Animator() = default; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
#ifndef FLUTTER_SHELL_GPU_GPU_SURFACE_DELEGATE_H_ | ||
#define FLUTTER_SHELL_GPU_GPU_SURFACE_DELEGATE_H_ | ||
|
||
#include "flutter/flow/embedded_views.h" | ||
#include "flutter/fml/macros.h" | ||
|
||
namespace flutter { | ||
|
||
class GPUSurfaceDelegate { | ||
public: | ||
// Get a reference to the external views embedder. This happens on the same | ||
// thread that the renderer is operating on. | ||
virtual ExternalViewEmbedder* GetExternalViewEmbedder() = 0; | ||
}; | ||
|
||
} // namespace flutter | ||
|
||
#endif // FLUTTER_SHELL_GPU_GPU_SURFACE_DELEGATE_H_ |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,8 +12,9 @@ | |
|
||
namespace flutter { | ||
|
||
GPUSurfaceMetal::GPUSurfaceMetal(fml::scoped_nsobject<CAMetalLayer> layer) | ||
: layer_(std::move(layer)) { | ||
GPUSurfaceMetal::GPUSurfaceMetal(GPUSurfaceDelegate* delegate, | ||
fml::scoped_nsobject<CAMetalLayer> layer) | ||
: delegate_(delegate), layer_(std::move(layer)) { | ||
if (!layer_) { | ||
FML_LOG(ERROR) << "Could not create metal surface because of invalid layer."; | ||
return; | ||
|
@@ -41,6 +42,32 @@ | |
context_ = context; | ||
} | ||
|
||
GPUSurfaceMetal::GPUSurfaceMetal(GPUSurfaceDelegate* delegate, | ||
sk_sp<GrContext> gr_context, | ||
fml::scoped_nsobject<CAMetalLayer> layer) | ||
: delegate_(delegate), layer_(std::move(layer)), context_(gr_context) { | ||
if (!layer_) { | ||
FML_LOG(ERROR) << "Could not create metal surface because of invalid layer."; | ||
return; | ||
} | ||
if (!context_) { | ||
FML_LOG(ERROR) << "Could not create metal surface because of invalid Skia metal context."; | ||
return; | ||
} | ||
|
||
layer.get().pixelFormat = MTLPixelFormatBGRA8Unorm; | ||
|
||
auto metal_device = fml::scoped_nsprotocol<id<MTLDevice>>([layer_.get().device retain]); | ||
auto metal_queue = fml::scoped_nsprotocol<id<MTLCommandQueue>>([metal_device newCommandQueue]); | ||
|
||
if (!metal_device || !metal_queue) { | ||
FML_LOG(ERROR) << "Could not create metal device or queue."; | ||
return; | ||
} | ||
|
||
command_queue_ = metal_queue; | ||
} | ||
|
||
GPUSurfaceMetal::~GPUSurfaceMetal() = default; | ||
|
||
// |Surface| | ||
|
@@ -112,11 +139,26 @@ GrBackendRenderTarget metal_render_target(bounds.width * scale, // width | |
return nullptr; | ||
} | ||
|
||
auto submit_callback = [drawable = next_drawable, command_buffer]( | ||
bool hasExternalViewEmbedder = delegate_->GetExternalViewEmbedder() != nullptr; | ||
|
||
// External views need to present with transaction. When presenting with | ||
// transaction, we have to block, otherwise we risk presenting the drawable | ||
// after the CATransaction has completed. | ||
// See: | ||
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction | ||
// TODO(dnfield): only do this if transactions are actually being used. | ||
// https://github.com/flutter/flutter/issues/24133 | ||
auto submit_callback = [drawable = next_drawable, command_buffer, hasExternalViewEmbedder]( | ||
const SurfaceFrame& surface_frame, SkCanvas* canvas) -> bool { | ||
canvas->flush(); | ||
[command_buffer.get() presentDrawable:drawable.get()]; | ||
[command_buffer.get() commit]; | ||
if (!hasExternalViewEmbedder) { | ||
[command_buffer.get() presentDrawable:drawable.get()]; | ||
[command_buffer.get() commit]; | ||
} else { | ||
[command_buffer.get() commit]; | ||
[command_buffer.get() waitUntilScheduled]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to not block here but instead finish rasterizing, and then do all the waits, present, and commit the transaction? [Not asking to do it in this PR, just wondering whether it is possible and whether we think it can be more performant] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should do as much work as we possibly can before getting the next drawable - guidelines from Apple say to hold it for as little time as possible. But if we use transactions, we have to block before presenting so that we are sure to present in the transaction. Alternatively, we have to make sure to not commit until we've presented in a callback. |
||
[drawable.get() present]; | ||
} | ||
return true; | ||
}; | ||
|
||
|
@@ -137,6 +179,11 @@ GrBackendRenderTarget metal_render_target(bounds.width * scale, // width | |
return context_.get(); | ||
} | ||
|
||
// |Surface| | ||
flutter::ExternalViewEmbedder* GPUSurfaceMetal::GetExternalViewEmbedder() { | ||
return delegate_->GetExternalViewEmbedder(); | ||
} | ||
|
||
// |Surface| | ||
bool GPUSurfaceMetal::MakeRenderContextCurrent() { | ||
// This backend has no such concept. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
#include "flutter/shell/common/platform_view.h" | ||
#include "flutter/shell/common/rasterizer.h" | ||
#include "flutter/shell/platform/darwin/ios/ios_surface_gl.h" | ||
#include "flutter/shell/platform/darwin/ios/ios_surface_metal.h" | ||
#include "flutter/shell/platform/darwin/ios/ios_surface_software.h" | ||
#include "third_party/skia/include/utils/mac/SkCGUtils.h" | ||
|
||
|
@@ -51,6 +52,13 @@ - (instancetype)initWithContentsScale:(CGFloat)contentsScale { | |
layer.allowsGroupOpacity = NO; | ||
layer.contentsScale = contentsScale; | ||
layer.rasterizationScale = contentsScale; | ||
#if FLUTTER_SHELL_ENABLE_METAL | ||
} else if ([self.layer isKindOfClass:[CAMetalLayer class]]) { | ||
CAMetalLayer* layer = reinterpret_cast<CAMetalLayer*>(self.layer); | ||
layer.allowsGroupOpacity = NO; | ||
layer.contentsScale = contentsScale; | ||
layer.rasterizationScale = contentsScale; | ||
#endif // FLUTTER_SHELL_ENABLE_METAL | ||
} | ||
|
||
return self; | ||
|
@@ -59,27 +67,39 @@ - (instancetype)initWithContentsScale:(CGFloat)contentsScale { | |
+ (Class)layerClass { | ||
#if TARGET_IPHONE_SIMULATOR | ||
return [CALayer class]; | ||
#else // TARGET_IPHONE_SIMULATOR | ||
#else // TARGET_IPHONE_SIMULATOR | ||
#if FLUTTER_SHELL_ENABLE_METAL | ||
return [CAMetalLayer class]; | ||
#else // FLUTTER_SHELL_ENABLE_METAL | ||
return [CAEAGLLayer class]; | ||
#endif // FLUTTER_SHELL_ENABLE_METAL | ||
#endif // TARGET_IPHONE_SIMULATOR | ||
} | ||
|
||
- (std::unique_ptr<flutter::IOSSurface>)createSoftwareSurface { | ||
fml::scoped_nsobject<CALayer> layer(reinterpret_cast<CALayer*>([self.layer retain])); | ||
return std::make_unique<flutter::IOSSurfaceSoftware>(std::move(layer), nullptr); | ||
} | ||
|
||
- (std::unique_ptr<flutter::IOSSurfaceGL>)createGLSurfaceWithContext: | ||
- (std::unique_ptr<flutter::IOSSurface>)createSurface: | ||
(std::shared_ptr<flutter::IOSGLContext>)gl_context { | ||
fml::scoped_nsobject<CAEAGLLayer> eagl_layer(reinterpret_cast<CAEAGLLayer*>([self.layer retain])); | ||
// TODO(amirh): We can lower this to iOS 8.0 once we have a Metal rendering backend. | ||
// https://github.com/flutter/flutter/issues/24132 | ||
if (@available(iOS 9.0, *)) { | ||
eagl_layer.get().presentsWithTransaction = YES; | ||
if ([self.layer isKindOfClass:[CAEAGLLayer class]]) { | ||
fml::scoped_nsobject<CAEAGLLayer> eagl_layer( | ||
reinterpret_cast<CAEAGLLayer*>([self.layer retain])); | ||
if (@available(iOS 9.0, *)) { | ||
eagl_layer.get().presentsWithTransaction = YES; | ||
} | ||
return std::make_unique<flutter::IOSSurfaceGL>(std::move(eagl_layer), gl_context); | ||
#if FLUTTER_SHELL_ENABLE_METAL | ||
} else if ([self.layer isKindOfClass:[CAMetalLayer class]]) { | ||
fml::scoped_nsobject<CAMetalLayer> metalLayer( | ||
reinterpret_cast<CAMetalLayer*>([self.layer retain])); | ||
if (@available(iOS 8.0, *)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. woohoo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need a guard for iOS 8 features? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably not, but it doesn't hurt either right? I can remove if you want. |
||
metalLayer.get().presentsWithTransaction = YES; | ||
} | ||
return std::make_unique<flutter::IOSSurfaceMetal>(std::move(metalLayer)); | ||
#endif // FLUTTER_SHELL_ENABLE_METAL | ||
} else { | ||
fml::scoped_nsobject<CALayer> layer(reinterpret_cast<CALayer*>([self.layer retain])); | ||
return std::make_unique<flutter::IOSSurfaceSoftware>(std::move(layer), nullptr); | ||
} | ||
return std::make_unique<flutter::IOSSurfaceGL>(eagl_layer, std::move(gl_context)); | ||
} | ||
|
||
// TODO(amirh): implement drawLayer to suppoer snapshotting. | ||
// TODO(amirh): implement drawLayer to support snapshotting. | ||
|
||
@end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't an issue on Metal, I believe because we're controlling when we actually present the frame so that it always happens before the transaction is committed.
Can we do that on OpenGL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the issue was identified when using OpenGL. IIRC this was a speculative fix for the keyboard lag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes - but the keyboard lag doesn't happen on metal. Without the wait added in this PR for metal, similar issues are seen but much worse.