| Flutter Impeller |

surface_mtl.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 #include "impeller/renderer/backend/metal/surface_mtl.h"
6
7 #include "flutter/fml/trace_event.h"
8 #include "flutter/impeller/renderer/command_buffer.h"
9 #include "impeller/base/validation.h"
10 #include "impeller/core/texture_descriptor.h"
11 #include "impeller/renderer/backend/metal/context_mtl.h"
12 #include "impeller/renderer/backend/metal/formats_mtl.h"
13 #include "impeller/renderer/backend/metal/texture_mtl.h"
14 #include "impeller/renderer/render_target.h"
15
16 @protocol FlutterMetalDrawable <MTLDrawable>
17 - (void)flutterPrepareForPresent:(nonnull id<MTLCommandBuffer>)commandBuffer;
18 @end
19
20 namespace impeller {
21
22 #pragma GCC diagnostic push
23 #pragma GCC diagnostic ignored "-Wunguarded-availability-new"
24
25 id<CAMetalDrawable> SurfaceMTL::GetMetalDrawableAndValidate(
26 const std::shared_ptr<Context>& context,
27 CAMetalLayer* layer) {
28 TRACE_EVENT0("impeller", "SurfaceMTL::WrapCurrentMetalLayerDrawable");
29
30 if (context == nullptr || !context->IsValid() || layer == nil) {
31 return nullptr;
32 }
33
34 id<CAMetalDrawable> current_drawable = nil;
35 {
36 TRACE_EVENT0("impeller", "WaitForNextDrawable");
37 current_drawable = [layer nextDrawable];
38 }
39
40 if (!current_drawable) {
41 VALIDATION_LOG << "Could not acquire current drawable.";
42 return nullptr;
43 }
44 return current_drawable;
45 }
46
47 static std::optional<RenderTarget> WrapTextureWithRenderTarget(
48 Allocator& allocator,
49 id<MTLTexture> texture,
50 bool requires_blit,
51 std::optional<IRect> clip_rect) {
52 // compositor_context.cc will offset the rendering by the clip origin. Here we
53 // shrink to the size of the clip. This has the same effect as clipping the
54 // rendering but also creates smaller intermediate passes.
55 ISize root_size;
56 if (requires_blit) {
57 if (!clip_rect.has_value()) {
58 VALIDATION_LOG << "Missing clip rectangle.";
59 return std::nullopt;
60 }
61 root_size = ISize(clip_rect->GetWidth(), clip_rect->GetHeight());
62 } else {
63 root_size = {static_cast<ISize::Type>(texture.width),
64 static_cast<ISize::Type>(texture.height)};
65 }
66
67 TextureDescriptor resolve_tex_desc;
68 resolve_tex_desc.format = FromMTLPixelFormat(texture.pixelFormat);
69 resolve_tex_desc.size = root_size;
70 resolve_tex_desc.usage =
71 TextureUsage::kRenderTarget | TextureUsage::kShaderRead;
72 resolve_tex_desc.sample_count = SampleCount::kCount1;
73 resolve_tex_desc.storage_mode = StorageMode::kDevicePrivate;
74
75 if (resolve_tex_desc.format == PixelFormat::kUnknown) {
76 VALIDATION_LOG << "Unknown drawable color format.";
77 return std::nullopt;
78 }
79
80 // Create color resolve texture.
81 std::shared_ptr<Texture> resolve_tex;
82 if (requires_blit) {
83 resolve_tex_desc.compression_type = CompressionType::kLossy;
84 resolve_tex = allocator.CreateTexture(resolve_tex_desc);
85 } else {
86 resolve_tex = TextureMTL::Create(resolve_tex_desc, texture);
87 }
88
89 if (!resolve_tex) {
90 VALIDATION_LOG << "Could not wrap resolve texture.";
91 return std::nullopt;
92 }
93 resolve_tex->SetLabel("ImpellerOnscreenResolve");
94
95 TextureDescriptor msaa_tex_desc;
96 msaa_tex_desc.storage_mode = StorageMode::kDeviceTransient;
97 msaa_tex_desc.type = TextureType::kTexture2DMultisample;
98 msaa_tex_desc.sample_count = SampleCount::kCount4;
99 msaa_tex_desc.format = resolve_tex->GetTextureDescriptor().format;
100 msaa_tex_desc.size = resolve_tex->GetSize();
101 msaa_tex_desc.usage = TextureUsage::kRenderTarget;
102
103 auto msaa_tex = allocator.CreateTexture(msaa_tex_desc);
104 if (!msaa_tex) {
105 VALIDATION_LOG << "Could not allocate MSAA color texture.";
106 return std::nullopt;
107 }
108 msaa_tex->SetLabel("ImpellerOnscreenColorMSAA");
109
110 ColorAttachment color0;
111 color0.texture = msaa_tex;
112 color0.clear_color = Color::DarkSlateGray();
113 color0.load_action = LoadAction::kClear;
114 color0.store_action = StoreAction::kMultisampleResolve;
115 color0.resolve_texture = std::move(resolve_tex);
116
117 auto render_target_desc = std::make_optional<RenderTarget>();
118 render_target_desc->SetColorAttachment(color0, 0u);
119
120 return render_target_desc;
121 }
122
123 std::unique_ptr<SurfaceMTL> SurfaceMTL::MakeFromMetalLayerDrawable(
124 const std::shared_ptr<Context>& context,
125 id<CAMetalDrawable> drawable,
126 std::optional<IRect> clip_rect) {
127 return SurfaceMTL::MakeFromTexture(context, drawable.texture, clip_rect,
128 drawable);
129 }
130
131 std::unique_ptr<SurfaceMTL> SurfaceMTL::MakeFromTexture(
132 const std::shared_ptr<Context>& context,
133 id<MTLTexture> texture,
134 std::optional<IRect> clip_rect,
135 id<CAMetalDrawable> drawable) {
136 bool partial_repaint_blit_required = ShouldPerformPartialRepaint(clip_rect);
137
138 // The returned render target is the texture that Impeller will render the
139 // root pass to. If partial repaint is in use, this may be a new texture which
140 // is smaller than the given MTLTexture.
141 auto render_target =
142 WrapTextureWithRenderTarget(*context->GetResourceAllocator(), texture,
143 partial_repaint_blit_required, clip_rect);
144 if (!render_target) {
145 return nullptr;
146 }
147
148 // If partial repainting, set a "source" texture. The presence of a source
149 // texture and clip rect instructs the surface to blit this texture to the
150 // destination texture.
151 auto source_texture = partial_repaint_blit_required
152 ? render_target->GetRenderTargetTexture()
153 : nullptr;
154
155 // The final "destination" texture is the texture that will be presented. In
156 // this case, it's always the given drawable.
157 std::shared_ptr<Texture> destination_texture;
158 if (partial_repaint_blit_required) {
159 // If blitting for partial repaint, we need to wrap the drawable. Simply
160 // reuse the texture descriptor that was already formed for the new render
161 // target, but override the size with the drawable's size.
162 auto destination_descriptor =
163 render_target->GetRenderTargetTexture()->GetTextureDescriptor();
164 destination_descriptor.size = {static_cast<ISize::Type>(texture.width),
165 static_cast<ISize::Type>(texture.height)};
166 destination_texture = TextureMTL::Wrapper(destination_descriptor, texture);
167 } else {
168 // When not partial repaint blit is needed, the render target texture _is_
169 // the drawable texture.
170 destination_texture = render_target->GetRenderTargetTexture();
171 }
172
173 return std::unique_ptr<SurfaceMTL>(new SurfaceMTL(
174 context, // context
175 *render_target, // target
176 render_target->GetRenderTargetTexture(), // resolve_texture
177 drawable, // drawable
178 source_texture, // source_texture
179 destination_texture, // destination_texture
180 partial_repaint_blit_required, // requires_blit
181 clip_rect // clip_rect
182 ));
183 }
184
185 SurfaceMTL::SurfaceMTL(const std::weak_ptr<Context>& context,
186 const RenderTarget& target,
187 std::shared_ptr<Texture> resolve_texture,
188 id<CAMetalDrawable> drawable,
189 std::shared_ptr<Texture> source_texture,
190 std::shared_ptr<Texture> destination_texture,
191 bool requires_blit,
192 std::optional<IRect> clip_rect)
193 : Surface(target),
194 context_(context),
195 resolve_texture_(std::move(resolve_texture)),
196 drawable_(drawable),
197 source_texture_(std::move(source_texture)),
198 destination_texture_(std::move(destination_texture)),
199 requires_blit_(requires_blit),
200 clip_rect_(clip_rect) {}
201
202 // |Surface|
203 SurfaceMTL::~SurfaceMTL() = default;
204
205 bool SurfaceMTL::ShouldPerformPartialRepaint(std::optional<IRect> damage_rect) {
206 // compositor_context.cc will conditionally disable partial repaint if the
207 // damage region is large. If that happened, then a nullopt damage rect
208 // will be provided here.
209 if (!damage_rect.has_value()) {
210 return false;
211 }
212 // If the damage rect is 0 in at least one dimension, partial repaint isn't
213 // performed as we skip right to present.
214 if (damage_rect->IsEmpty()) {
215 return false;
216 }
217 return true;
218 }
219
220 // |Surface|
221 IRect SurfaceMTL::coverage() const {
222 return IRect::MakeSize(resolve_texture_->GetSize());
223 }
224
225 bool SurfaceMTL::PreparePresent() const {
226 auto context = context_.lock();
227 if (!context) {
228 return false;
229 }
230
231 #ifdef IMPELLER_DEBUG
232 context->GetResourceAllocator()->DebugTraceMemoryStatistics();
233 if (frame_boundary_) {
234 ContextMTL::Cast(context.get())->GetCaptureManager()->FinishCapture();
235 }
236 #endif // IMPELLER_DEBUG
237
238 if (requires_blit_) {
239 if (!(source_texture_ && destination_texture_)) {
240 return false;
241 }
242
243 auto blit_command_buffer = context->CreateCommandBuffer();
244 if (!blit_command_buffer) {
245 return false;
246 }
247 auto blit_pass = blit_command_buffer->CreateBlitPass();
248 if (!clip_rect_.has_value()) {
249 VALIDATION_LOG << "Missing clip rectangle.";
250 return false;
251 }
252 blit_pass->AddCopy(source_texture_, destination_texture_, std::nullopt,
253 clip_rect_->GetOrigin());
254 blit_pass->EncodeCommands(context->GetResourceAllocator());
255 if (!context->GetCommandQueue()->Submit({blit_command_buffer}).ok()) {
256 return false;
257 }
258 }
259 #ifdef IMPELLER_DEBUG
260 ContextMTL::Cast(context.get())->GetGPUTracer()->MarkFrameEnd();
261 #endif // IMPELLER_DEBUG
262 prepared_ = true;
263 return true;
264 }
265
266 // |Surface|
267 bool SurfaceMTL::Present() const {
268 if (!prepared_) {
269 PreparePresent();
270 }
271 auto context = context_.lock();
272 if (!context) {
273 return false;
274 }
275
276 if (drawable_) {
277 id<MTLCommandBuffer> command_buffer =
278 ContextMTL::Cast(context.get())
279 ->CreateMTLCommandBuffer("Present Waiter Command Buffer");
280
281 id<CAMetalDrawable> metal_drawable =
282 reinterpret_cast<id<CAMetalDrawable>>(drawable_);
283 if ([metal_drawable conformsToProtocol:@protocol(FlutterMetalDrawable)]) {
284 [(id<FlutterMetalDrawable>)metal_drawable
285 flutterPrepareForPresent:command_buffer];
286 }
287
288 // Intel iOS simulators do not seem to give backpressure on Metal drawable
289 // aquisition, which can result in Impeller running head of the GPU
290 // workload by dozens of frames. Slow this process down by blocking
291 // on submit until the last command buffer is at least scheduled.
292 #if defined(FML_OS_IOS_SIMULATOR) && defined(FML_ARCH_CPU_X86_64)
293 constexpr bool alwaysWaitForScheduling = true;
294 #else
295 constexpr bool alwaysWaitForScheduling = false;
296 #endif // defined(FML_OS_IOS_SIMULATOR) && defined(FML_ARCH_CPU_X86_64)
297
298 // If the threads have been merged, or there is a pending frame capture,
299 // then block on cmd buffer scheduling to ensure that the
300 // transaction/capture work correctly.
301 if (present_with_transaction_ || [[NSThread currentThread] isMainThread] ||
302 [[MTLCaptureManager sharedCaptureManager] isCapturing] ||
303 alwaysWaitForScheduling) {
304 TRACE_EVENT0("flutter", "waitUntilScheduled");
305 [command_buffer commit];
306 #if defined(FML_OS_IOS_SIMULATOR) && defined(FML_ARCH_CPU_X86_64)
307 [command_buffer waitUntilCompleted];
308 #else
309 [command_buffer waitUntilScheduled];
310 #endif // defined(FML_OS_IOS_SIMULATOR) && defined(FML_ARCH_CPU_X86_64)
311 [drawable_ present];
312 } else {
313 // The drawable may come from a FlutterMetalLayer, so it can't be
314 // presented through the command buffer.
315 id<CAMetalDrawable> drawable = drawable_;
316 [command_buffer addScheduledHandler:^(id<MTLCommandBuffer> buffer) {
317 [drawable present];
318 }];
319 [command_buffer commit];
320 }
321 }
322
323 return true;
324 }
325 #pragma GCC diagnostic pop
326
327 } // namespace impeller
impeller::ISize
ISize64 ISize
Definition: size.h:140
impeller::StoreAction::kMultisampleResolve
@ kMultisampleResolve
impeller::Attachment::store_action
StoreAction store_action
Definition: formats.h:660
impeller::SurfaceMTL
Definition: surface_mtl.h:17
context_mtl.h
impeller::ColorAttachment
Definition: formats.h:665
impeller::TextureDescriptor::format
PixelFormat format
Definition: texture_descriptor.h:41
impeller::SurfaceMTL::coverage
IRect coverage() const
Definition: surface_mtl.mm:221
texture_descriptor.h
formats_mtl.h
impeller::TextureUsage::kRenderTarget
@ kRenderTarget
impeller::Allocator::CreateTexture
std::shared_ptr< Texture > CreateTexture(const TextureDescriptor &desc)
Definition: allocator.cc:49
impeller::TextureDescriptor::sample_count
SampleCount sample_count
Definition: texture_descriptor.h:45
validation.h
impeller::TextureDescriptor::usage
TextureUsageMask usage
Definition: texture_descriptor.h:44
impeller::TSize::Type
T Type
Definition: size.h:20
impeller::Surface
Definition: surface.h:12
impeller::WrapTextureWithRenderTarget
static std::optional< RenderTarget > WrapTextureWithRenderTarget(Allocator &allocator, id< MTLTexture > texture, bool requires_blit, std::optional< IRect > clip_rect)
Definition: surface_mtl.mm:47
impeller::TextureDescriptor::type
TextureType type
Definition: texture_descriptor.h:40
impeller::TextureType::kTexture2DMultisample
@ kTexture2DMultisample
impeller::Color::DarkSlateGray
static constexpr Color DarkSlateGray()
Definition: color.h:417
impeller::TSize
Definition: size.h:19
impeller::LoadAction::kClear
@ kClear
impeller::StorageMode::kDeviceTransient
@ kDeviceTransient
impeller::ColorAttachment::clear_color
Color clear_color
Definition: formats.h:666
impeller::SurfaceMTL::~SurfaceMTL
~SurfaceMTL() override
impeller::Attachment::texture
std::shared_ptr< Texture > texture
Definition: formats.h:657
impeller::StorageMode::kDevicePrivate
@ kDevicePrivate
FlutterMetalDrawable-p
Definition: surface_mtl.mm:16
impeller::SurfaceMTL::MakeFromMetalLayerDrawable
static std::unique_ptr< SurfaceMTL > MakeFromMetalLayerDrawable(const std::shared_ptr< Context > &context, id< CAMetalDrawable > drawable, std::optional< IRect > clip_rect=std::nullopt)
Definition: surface_mtl.mm:123
impeller::Allocator
An object that allocates device memory.
Definition: allocator.h:23
impeller::TextureMTL::Create
static std::shared_ptr< TextureMTL > Create(TextureDescriptor desc, id< MTLTexture > texture)
Definition: texture_mtl.mm:58
impeller::RenderTarget
Definition: render_target.h:38
impeller::SurfaceMTL::MakeFromTexture
static std::unique_ptr< SurfaceMTL > MakeFromTexture(const std::shared_ptr< Context > &context, id< MTLTexture > texture, std::optional< IRect > clip_rect, id< CAMetalDrawable > drawable=nil)
Definition: surface_mtl.mm:131
impeller::SurfaceMTL::PreparePresent
bool PreparePresent() const
Perform the final blit and trigger end of frame workloads.
Definition: surface_mtl.mm:225
impeller::CompressionType::kLossy
@ kLossy
impeller::SurfaceMTL::drawable
id< MTLDrawable > drawable() const
Definition: surface_mtl.h:56
impeller::TextureMTL::Wrapper
static std::shared_ptr< TextureMTL > Wrapper(TextureDescriptor desc, id< MTLTexture > texture, std::function< void()> deletion_proc=nullptr)
Definition: texture_mtl.mm:41
surface_mtl.h
impeller::PixelFormat::kUnknown
@ kUnknown
impeller::TextureDescriptor::size
ISize size
Definition: texture_descriptor.h:42
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:91
command_buffer.h
impeller::Attachment::resolve_texture
std::shared_ptr< Texture > resolve_texture
Definition: formats.h:658
impeller::TRect::MakeSize
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:150
std
Definition: comparable.h:95
impeller::SurfaceMTL::GetMetalDrawableAndValidate
static id< CAMetalDrawable > GetMetalDrawableAndValidate(const std::shared_ptr< Context > &context, CAMetalLayer *layer)
Wraps the current drawable of the given Metal layer to create a surface Impeller can render to....
Definition: surface_mtl.mm:25
texture_mtl.h
impeller::BackendCast< ContextMTL, Context >::Cast
static ContextMTL & Cast(Context &base)
Definition: backend_cast.h:13
impeller::Attachment::load_action
LoadAction load_action
Definition: formats.h:659
impeller::ContextMTL::CreateMTLCommandBuffer
id< MTLCommandBuffer > CreateMTLCommandBuffer(const std::string &label) const
Definition: context_mtl.mm:373
impeller::TextureUsage::kShaderRead
@ kShaderRead
impeller::FromMTLPixelFormat
constexpr PixelFormat FromMTLPixelFormat(MTLPixelFormat format)
Definition: formats_mtl.h:22
impeller::SampleCount::kCount1
@ kCount1
impeller::SampleCount::kCount4
@ kCount4
impeller::TextureDescriptor::storage_mode
StorageMode storage_mode
Definition: texture_descriptor.h:39
impeller::TextureDescriptor
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
Definition: texture_descriptor.h:38
render_target.h
impeller::TextureDescriptor::compression_type
CompressionType compression_type
Definition: texture_descriptor.h:46
impeller
Definition: allocation.cc:12
impeller::TRect
Definition: rect.h:122
impeller::ContextMTL::CreateCommandBuffer
std::shared_ptr< CommandBuffer > CreateCommandBuffer() const override
Create a new command buffer. Command buffers can be used to encode graphics, blit,...
Definition: context_mtl.mm:318
impeller::SurfaceMTL::Present
bool Present() const override
Definition: surface_mtl.mm:267