Flutter Impeller
compute_pass_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 <Metal/Metal.h>
8 #include <algorithm>
9 #include <memory>
10 
11 #include "flutter/fml/backtrace.h"
12 #include "flutter/fml/closure.h"
13 #include "flutter/fml/logging.h"
14 #include "flutter/fml/trace_event.h"
15 #include "fml/status.h"
18 #include "impeller/core/formats.h"
27 
28 namespace impeller {
29 
30 ComputePassMTL::ComputePassMTL(std::shared_ptr<const Context> context,
31  id<MTLCommandBuffer> buffer)
32  : ComputePass(std::move(context)), buffer_(buffer) {
33  if (!buffer_) {
34  return;
35  }
36  encoder_ = [buffer_ computeCommandEncoderWithDispatchType:
37  MTLDispatchType::MTLDispatchTypeConcurrent];
38  if (!encoder_) {
39  return;
40  }
41  pass_bindings_cache_.SetEncoder(encoder_);
42  is_valid_ = true;
43 }
44 
45 ComputePassMTL::~ComputePassMTL() = default;
46 
47 bool ComputePassMTL::IsValid() const {
48  return is_valid_;
49 }
50 
51 void ComputePassMTL::OnSetLabel(const std::string& label) {
52 #ifdef IMPELLER_DEBUG
53  if (label.empty()) {
54  return;
55  }
56  [encoder_ setLabel:@(label.c_str())];
57 #endif // IMPELLER_DEBUG
58 }
59 
60 void ComputePassMTL::SetCommandLabel(std::string_view label) {
61  has_label_ = true;
62  [encoder_ pushDebugGroup:@(label.data())];
63 }
64 
65 // |ComputePass|
66 void ComputePassMTL::SetPipeline(
67  const std::shared_ptr<Pipeline<ComputePipelineDescriptor>>& pipeline) {
68  pass_bindings_cache_.SetComputePipelineState(
69  ComputePipelineMTL::Cast(*pipeline).GetMTLComputePipelineState());
70  workgroup_size_ = pipeline->GetDescriptor().GetWorkgroupSize();
71 }
72 
73 // |ComputePass|
74 void ComputePassMTL::AddBufferMemoryBarrier() {
75  [encoder_ memoryBarrierWithScope:MTLBarrierScopeBuffers];
76 }
77 
78 // |ComputePass|
79 void ComputePassMTL::AddTextureMemoryBarrier() {
80  [encoder_ memoryBarrierWithScope:MTLBarrierScopeTextures];
81 }
82 
83 // |ComputePass|
84 bool ComputePassMTL::BindResource(ShaderStage stage,
85  DescriptorType type,
86  const ShaderUniformSlot& slot,
87  const ShaderMetadata* metadata,
88  BufferView view) {
89  if (!view.GetBuffer()) {
90  return false;
91  }
92 
93  const DeviceBuffer* device_buffer = view.GetBuffer();
94  if (!device_buffer) {
95  return false;
96  }
97 
98  id<MTLBuffer> buffer = DeviceBufferMTL::Cast(*device_buffer).GetMTLBuffer();
99  // The Metal call is a void return and we don't want to make it on nil.
100  if (!buffer) {
101  return false;
102  }
103 
104  pass_bindings_cache_.SetBuffer(slot.ext_res_0, view.GetRange().offset,
105  buffer);
106  return true;
107 }
108 
109 // |ComputePass|
110 bool ComputePassMTL::BindResource(ShaderStage stage,
111  DescriptorType type,
112  const SampledImageSlot& slot,
113  const ShaderMetadata* metadata,
114  std::shared_ptr<const Texture> texture,
115  raw_ptr<const Sampler> sampler) {
116  if (!sampler || !texture->IsValid()) {
117  return false;
118  }
119 
120  pass_bindings_cache_.SetTexture(slot.texture_index,
121  TextureMTL::Cast(*texture).GetMTLTexture());
122  pass_bindings_cache_.SetSampler(
123  slot.texture_index, SamplerMTL::Cast(*sampler).GetMTLSamplerState());
124  return true;
125 }
126 
127 fml::Status ComputePassMTL::Compute(std::array<uint32_t, 3> workgroup_count) {
128  if (workgroup_count[0] == 0u || workgroup_count[1] == 0u ||
129  workgroup_count[2] == 0u) {
130  return fml::Status(fml::StatusCode::kCancelled,
131  "Invalid workgroup count for compute command.");
132  }
133 
134  const NSUInteger max_total =
135  pass_bindings_cache_.GetPipeline().maxTotalThreadsPerThreadgroup;
136 
137  // Unlike Vulkan and GLES, Metal does not bake the threadgroup size into the
138  // shader; it is supplied here at dispatch. Honor the shader's declared local
139  // size. A dimension of 0 means the shader sized it with a specialization
140  // constant. In that case fill the remaining threadgroup capacity into x while
141  // honoring any literal y and z, keeping the total within the device maximum.
142  MTLSize threads_per_threadgroup;
143  if (workgroup_size_[0] == 0u) {
144  const NSUInteger size_y =
145  workgroup_size_[1] == 0u ? 1u : workgroup_size_[1];
146  const NSUInteger size_z =
147  workgroup_size_[2] == 0u ? 1u : workgroup_size_[2];
148  const NSUInteger size_x =
149  std::max<NSUInteger>(1u, max_total / (size_y * size_z));
150  threads_per_threadgroup = MTLSizeMake(size_x, size_y, size_z);
151  } else {
152  threads_per_threadgroup = MTLSizeMake(
153  workgroup_size_[0], workgroup_size_[1] == 0u ? 1 : workgroup_size_[1],
154  workgroup_size_[2] == 0u ? 1 : workgroup_size_[2]);
155  }
156 
157  // Metal aborts at dispatch if the threadgroup size exceeds the device
158  // maximum. Vulkan rejects an oversized local size at pipeline creation, so do
159  // the equivalent here and fail gracefully instead.
160  if (threads_per_threadgroup.width * threads_per_threadgroup.height *
161  threads_per_threadgroup.depth >
162  max_total) {
163  return fml::Status(fml::StatusCode::kCancelled,
164  "Compute shader workgroup size exceeds the device's "
165  "maximum threads per threadgroup.");
166  }
167 
168  [encoder_
169  dispatchThreadgroups:MTLSizeMake(workgroup_count[0], workgroup_count[1],
170  workgroup_count[2])
171  threadsPerThreadgroup:threads_per_threadgroup];
172 
173 #ifdef IMPELLER_DEBUG
174  if (has_label_) {
175  [encoder_ popDebugGroup];
176  }
177  has_label_ = false;
178 #endif // IMPELLER_DEBUG
179  return fml::Status();
180 }
181 
182 bool ComputePassMTL::EncodeCommands() const {
183  [encoder_ endEncoding];
184  return true;
185 }
186 
187 } // namespace impeller
Definition: comparable.h:93
std::shared_ptr< ContextGLES > context
std::shared_ptr< PipelineGLES > pipeline