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