| Flutter iOS Embedder |

FlutterView.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
6 #include <Metal/Metal.h>
7
8 #include "flutter/common/settings.h"
9 #include "flutter/common/task_runners.h"
10 #include "flutter/flow/layers/layer_tree.h"
11 #include "flutter/fml/platform/darwin/cf_utils.h"
12 #include "flutter/fml/synchronization/waitable_event.h"
13 #include "flutter/fml/trace_event.h"
14 #include "flutter/shell/common/platform_view.h"
15 #include "flutter/shell/common/rasterizer.h"
16 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
17 #import "flutter/shell/platform/darwin/ios/ios_surface_software.h"
18 #include "third_party/skia/include/utils/mac/SkCGUtils.h"
19
20 @implementation FlutterView {
21 id<FlutterViewEngineDelegate> _delegate;
22 BOOL _isWideGamutEnabled;
23 }
24
25 - (instancetype)init {
26 NSAssert(NO, @"FlutterView must initWithDelegate");
27 return nil;
28 }
29
30 - (instancetype)initWithFrame:(CGRect)frame {
31 NSAssert(NO, @"FlutterView must initWithDelegate");
32 return nil;
33 }
34
35 - (instancetype)initWithCoder:(NSCoder*)aDecoder {
36 NSAssert(NO, @"FlutterView must initWithDelegate");
37 return nil;
38 }
39
40 - (UIScreen*)screen {
41 if (@available(iOS 13.0, *)) {
42 return self.window.windowScene.screen;
43 }
44 return UIScreen.mainScreen;
45 }
46
47 - (MTLPixelFormat)pixelFormat {
48 if ([self.layer isKindOfClass:NSClassFromString(@"CAMetalLayer")]) {
49 // It is a known Apple bug that CAMetalLayer incorrectly reports its supported
50 // SDKs. It is, in fact, available since iOS 8.
51 #pragma clang diagnostic push
52 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
53 CAMetalLayer* layer = (CAMetalLayer*)self.layer;
54 return layer.pixelFormat;
55 }
56 return MTLPixelFormatBGRA8Unorm;
57 }
58 - (BOOL)isWideGamutSupported {
59 if (![_delegate isUsingImpeller]) {
60 return NO;
61 }
62
63 FML_DCHECK(self.screen);
64
65 // This predicates the decision on the capabilities of the iOS device's
66 // display. This means external displays will not support wide gamut if the
67 // device's display doesn't support it. It practice that should be never.
68 return self.screen.traitCollection.displayGamut != UIDisplayGamutSRGB;
69 }
70
71 - (instancetype)initWithDelegate:(id<FlutterViewEngineDelegate>)delegate
72 opaque:(BOOL)opaque
73 enableWideGamut:(BOOL)isWideGamutEnabled {
74 if (delegate == nil) {
75 NSLog(@"FlutterView delegate was nil.");
76 [self release];
77 return nil;
78 }
79
80 self = [super initWithFrame:CGRectNull];
81
82 if (self) {
83 _delegate = delegate;
84 _isWideGamutEnabled = isWideGamutEnabled;
85 self.layer.opaque = opaque;
86
87 // This line is necessary. CoreAnimation(or UIKit) may take this to do
88 // something to compute the final frame presented on screen, if we don't set this,
89 // it will make it take long time for us to take next CAMetalDrawable and will
90 // cause constant junk during rendering.
91 self.backgroundColor = UIColor.clearColor;
92 }
93
94 return self;
95 }
96
97 static void PrintWideGamutWarningOnce() {
98 static BOOL did_print = NO;
99 if (did_print) {
100 return;
101 }
102 FML_DLOG(WARNING) << "Rendering wide gamut colors is turned on but isn't "
103 "supported, downgrading the color gamut to sRGB.";
104 did_print = YES;
105 }
106
107 - (void)layoutSubviews {
108 if ([self.layer isKindOfClass:NSClassFromString(@"CAMetalLayer")]) {
109 // It is a known Apple bug that CAMetalLayer incorrectly reports its supported
110 // SDKs. It is, in fact, available since iOS 8.
111 #pragma clang diagnostic push
112 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
113 CAMetalLayer* layer = (CAMetalLayer*)self.layer;
114 #pragma clang diagnostic pop
115 CGFloat screenScale = self.screen.scale;
116 layer.allowsGroupOpacity = YES;
117 layer.contentsScale = screenScale;
118 layer.rasterizationScale = screenScale;
119 layer.framebufferOnly = flutter::Settings::kSurfaceDataAccessible ? NO : YES;
120 BOOL isWideGamutSupported = self.isWideGamutSupported;
121 if (_isWideGamutEnabled && isWideGamutSupported) {
122 CGColorSpaceRef srgb = CGColorSpaceCreateWithName(kCGColorSpaceExtendedSRGB);
123 layer.colorspace = srgb;
124 CFRelease(srgb);
125 // MTLPixelFormatRGBA16Float was chosen since it is compatible with
126 // impeller's offscreen buffers which need to have transparency. Also,
127 // F16 was chosen over BGRA10_XR since Skia does not support decoding
128 // BGRA10_XR.
129 layer.pixelFormat = MTLPixelFormatRGBA16Float;
130 } else if (_isWideGamutEnabled && !isWideGamutSupported) {
131 PrintWideGamutWarningOnce();
132 }
133 }
134
135 [super layoutSubviews];
136 }
137
138 static BOOL _forceSoftwareRendering;
139
140 + (BOOL)forceSoftwareRendering {
141 return _forceSoftwareRendering;
142 }
143
144 + (void)setForceSoftwareRendering:(BOOL)forceSoftwareRendering {
145 _forceSoftwareRendering = forceSoftwareRendering;
146 }
147
148 + (Class)layerClass {
149 return flutter::GetCoreAnimationLayerClassForRenderingAPI(
150 flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering));
151 }
152
153 - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
154 TRACE_EVENT0("flutter", "SnapshotFlutterView");
155
156 if (layer != self.layer || context == nullptr) {
157 return;
158 }
159
160 auto screenshot = [_delegate takeScreenshot:flutter::Rasterizer::ScreenshotType::UncompressedImage
161 asBase64Encoded:NO];
162
163 if (!screenshot.data || screenshot.data->isEmpty() || screenshot.frame_size.isEmpty()) {
164 return;
165 }
166
167 NSData* data = [NSData dataWithBytes:const_cast<void*>(screenshot.data->data())
168 length:screenshot.data->size()];
169
170 fml::CFRef<CGDataProviderRef> image_data_provider(
171 CGDataProviderCreateWithCFData(reinterpret_cast<CFDataRef>(data)));
172
173 fml::CFRef<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB());
174
175 // Defaults for RGBA8888.
176 size_t bits_per_component = 8u;
177 size_t bits_per_pixel = 32u;
178 size_t bytes_per_row_multiplier = 4u;
179 CGBitmapInfo bitmap_info =
180 static_cast<CGBitmapInfo>(static_cast<uint32_t>(kCGImageAlphaPremultipliedLast) |
181 static_cast<uint32_t>(kCGBitmapByteOrder32Big));
182
183 switch (screenshot.pixel_format) {
184 case flutter::Rasterizer::ScreenshotFormat::kUnknown:
185 case flutter::Rasterizer::ScreenshotFormat::kR8G8B8A8UNormInt:
186 // Assume unknown is Skia and is RGBA8888. Keep defaults.
187 break;
188 case flutter::Rasterizer::ScreenshotFormat::kB8G8R8A8UNormInt:
189 // Treat this as little endian with the alpha first so that it's read backwards.
190 bitmap_info =
191 static_cast<CGBitmapInfo>(static_cast<uint32_t>(kCGImageAlphaPremultipliedFirst) |
192 static_cast<uint32_t>(kCGBitmapByteOrder32Little));
193 break;
194 case flutter::Rasterizer::ScreenshotFormat::kR16G16B16A16Float:
195 bits_per_component = 16u;
196 bits_per_pixel = 64u;
197 bytes_per_row_multiplier = 8u;
198 bitmap_info =
199 static_cast<CGBitmapInfo>(static_cast<uint32_t>(kCGImageAlphaPremultipliedLast) |
200 static_cast<uint32_t>(kCGBitmapFloatComponents) |
201 static_cast<uint32_t>(kCGBitmapByteOrder16Little));
202 break;
203 }
204
205 fml::CFRef<CGImageRef> image(CGImageCreate(
206 screenshot.frame_size.width(), // size_t width
207 screenshot.frame_size.height(), // size_t height
208 bits_per_component, // size_t bitsPerComponent
209 bits_per_pixel, // size_t bitsPerPixel,
210 bytes_per_row_multiplier * screenshot.frame_size.width(), // size_t bytesPerRow
211 colorspace, // CGColorSpaceRef space
212 bitmap_info, // CGBitmapInfo bitmapInfo
213 image_data_provider, // CGDataProviderRef provider
214 nullptr, // const CGFloat* decode
215 false, // bool shouldInterpolate
216 kCGRenderingIntentDefault // CGColorRenderingIntent intent
217 ));
218
219 const CGRect frame_rect =
220 CGRectMake(0.0, 0.0, screenshot.frame_size.width(), screenshot.frame_size.height());
221 CGContextSaveGState(context);
222 // If the CGContext is not a bitmap based context, this returns zero.
223 CGFloat height = CGBitmapContextGetHeight(context);
224 if (height == 0) {
225 height = CGFloat(screenshot.frame_size.height());
226 }
227 CGContextTranslateCTM(context, 0.0, height);
228 CGContextScaleCTM(context, 1.0, -1.0);
229 CGContextDrawImage(context, frame_rect, image);
230 CGContextRestoreGState(context);
231 }
232
233 - (BOOL)isAccessibilityElement {
234 // iOS does not provide an API to query whether the voice control
235 // is turned on or off. It is likely at least one of the assitive
236 // technologies is turned on if this method is called. If we do
237 // not catch it in notification center, we will catch it here.
238 //
239 // TODO(chunhtai): Remove this workaround once iOS provides an
240 // API to query whether voice control is enabled.
241 // https://github.com/flutter/flutter/issues/76808.
242 [_delegate flutterViewAccessibilityDidCall];
243 return NO;
244 }
245
246 @end
FlutterView::forceSoftwareRendering
BOOL forceSoftwareRendering
Definition: FlutterView.h:54
flutter::GetCoreAnimationLayerClassForRenderingAPI
Class GetCoreAnimationLayerClassForRenderingAPI(IOSRenderingAPI rendering_api)
Definition: rendering_api_selection.mm:63
FlutterViewEngineDelegate-p
Definition: FlutterView.h:19
initWithFrame
instancetype initWithFrame
Definition: FlutterTextInputPlugin.h:167
ios_surface_software.h
flutter::GetRenderingAPIForProcess
IOSRenderingAPI GetRenderingAPIForProcess(bool force_software)
Definition: rendering_api_selection.mm:33
initWithCoder
instancetype initWithCoder
Definition: FlutterTextInputPlugin.h:166
_isWideGamutEnabled
BOOL _isWideGamutEnabled
Definition: FlutterView.mm:20
FlutterViewController_Internal.h
FlutterView
Definition: FlutterView.h:39
FlutterView.h
_forceSoftwareRendering
static BOOL _forceSoftwareRendering
Definition: FlutterView.mm:138