| Flutter Impeller |

allocator_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/allocator_mtl.h"
6
7 #include "flutter/fml/build_config.h"
8 #include "flutter/fml/logging.h"
9 #include "fml/trace_event.h"
10 #include "impeller/base/validation.h"
11 #include "impeller/core/formats.h"
12 #include "impeller/renderer/backend/metal/device_buffer_mtl.h"
13 #include "impeller/renderer/backend/metal/formats_mtl.h"
14 #include "impeller/renderer/backend/metal/texture_mtl.h"
15
16 namespace impeller {
17
18 static bool DeviceSupportsDeviceTransientTargets(id<MTLDevice> device) {
19 // Refer to the "Memoryless render targets" feature in the table below:
20 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
21 if (@available(ios 13.0, tvos 13.0, macos 10.15, *)) {
22 return [device supportsFamily:MTLGPUFamilyApple2];
23 } else {
24 #if FML_OS_IOS
25 // This is perhaps redundant. But, just in case we somehow get into a case
26 // where Impeller runs on iOS versions less than 8.0 and/or without A8
27 // GPUs, we explicitly check feature set support.
28 return [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v1];
29 #else
30 // MacOS devices with Apple GPUs are only available with macos 10.15 and
31 // above. So, if we are here, it is safe to assume that memory-less targets
32 // are not supported.
33 return false;
34 #endif
35 }
36 FML_UNREACHABLE();
37 }
38
39 static bool DeviceHasUnifiedMemoryArchitecture(id<MTLDevice> device) {
40 if (@available(ios 13.0, tvos 13.0, macOS 10.15, *)) {
41 return [device hasUnifiedMemory];
42 } else {
43 #if FML_OS_IOS
44 // iOS devices where the availability check can fail always have had UMA.
45 return true;
46 #else
47 // Mac devices where the availability check can fail have never had UMA.
48 return false;
49 #endif
50 }
51 FML_UNREACHABLE();
52 }
53
54 static ISize DeviceMaxTextureSizeSupported(id<MTLDevice> device) {
55 // Since Apple didn't expose API for us to get the max texture size, we have
56 // to use hardcoded data from
57 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
58 // According to the feature set table, there are two supported max sizes :
59 // 16384 and 8192 for devices flutter support. The former is used on macs and
60 // latest ios devices. The latter is used on old ios devices.
61 if (@available(macOS 10.15, iOS 13, tvOS 13, *)) {
62 if ([device supportsFamily:MTLGPUFamilyApple3] ||
63 [device supportsFamily:MTLGPUFamilyMacCatalyst1] ||
64 [device supportsFamily:MTLGPUFamilyMac1]) {
65 return {16384, 16384};
66 }
67 return {8192, 8192};
68 } else {
69 #if FML_OS_IOS
70 if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1] ||
71 [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
72 return {16384, 16384};
73 }
74 #endif
75 #if FML_OS_MACOSX
76 return {16384, 16384};
77 #endif
78 return {8192, 8192};
79 }
80 }
81
82 static bool SupportsLossyTextureCompression(id<MTLDevice> device) {
83 #ifdef FML_OS_IOS_SIMULATOR
84 return false;
85 #else
86 if (@available(macOS 10.15, iOS 13, tvOS 13, *)) {
87 return [device supportsFamily:MTLGPUFamilyApple8];
88 }
89 return false;
90 #endif
91 }
92
93 void DebugAllocatorStats::Increment(size_t size) {
94 size_.fetch_add(size, std::memory_order_relaxed);
95 }
96
97 void DebugAllocatorStats::Decrement(size_t size) {
98 size_.fetch_sub(size, std::memory_order_relaxed);
99 }
100
101 size_t DebugAllocatorStats::GetAllocationSizeMB() {
102 // RAM is measured in MiB, thus a divisor of 2^20 instead of 1,000,000.
103 size_t new_value = size_ / (1024 * 1024);
104 return new_value;
105 }
106
107 AllocatorMTL::AllocatorMTL(id<MTLDevice> device, std::string label)
108 : device_(device), allocator_label_(std::move(label)) {
109 if (!device_) {
110 return;
111 }
112
113 supports_memoryless_targets_ = DeviceSupportsDeviceTransientTargets(device_);
114 supports_uma_ = DeviceHasUnifiedMemoryArchitecture(device_);
115 max_texture_supported_ = DeviceMaxTextureSizeSupported(device_);
116
117 is_valid_ = true;
118 }
119
120 AllocatorMTL::~AllocatorMTL() = default;
121
122 bool AllocatorMTL::IsValid() const {
123 return is_valid_;
124 }
125
126 static MTLResourceOptions ToMTLResourceOptions(StorageMode type,
127 bool supports_memoryless_targets,
128 bool supports_uma) {
129 switch (type) {
130 case StorageMode::kHostVisible:
131 #if FML_OS_IOS
132 return MTLResourceStorageModeShared;
133 #else
134 if (supports_uma) {
135 return MTLResourceStorageModeShared;
136 } else {
137 return MTLResourceStorageModeManaged;
138 }
139 #endif
140 case StorageMode::kDevicePrivate:
141 return MTLResourceStorageModePrivate;
142 case StorageMode::kDeviceTransient:
143 if (supports_memoryless_targets) {
144 // Device may support but the OS has not been updated.
145 if (@available(macOS 11.0, *)) {
146 return MTLResourceStorageModeMemoryless;
147 } else {
148 return MTLResourceStorageModePrivate;
149 }
150 } else {
151 return MTLResourceStorageModePrivate;
152 }
153 FML_UNREACHABLE();
154 }
155 FML_UNREACHABLE();
156 }
157
158 static MTLStorageMode ToMTLStorageMode(StorageMode mode,
159 bool supports_memoryless_targets,
160 bool supports_uma) {
161 switch (mode) {
162 case StorageMode::kHostVisible:
163 #if FML_OS_IOS
164 return MTLStorageModeShared;
165 #else
166 if (supports_uma) {
167 return MTLStorageModeShared;
168 } else {
169 return MTLStorageModeManaged;
170 }
171 #endif
172 case StorageMode::kDevicePrivate:
173 return MTLStorageModePrivate;
174 case StorageMode::kDeviceTransient:
175 if (supports_memoryless_targets) {
176 // Device may support but the OS has not been updated.
177 if (@available(macOS 11.0, *)) {
178 return MTLStorageModeMemoryless;
179 } else {
180 return MTLStorageModePrivate;
181 }
182 } else {
183 return MTLStorageModePrivate;
184 }
185 FML_UNREACHABLE();
186 }
187 FML_UNREACHABLE();
188 }
189
190 std::shared_ptr<DeviceBuffer> AllocatorMTL::OnCreateBuffer(
191 const DeviceBufferDescriptor& desc) {
192 const auto resource_options = ToMTLResourceOptions(
193 desc.storage_mode, supports_memoryless_targets_, supports_uma_);
194 const auto storage_mode = ToMTLStorageMode(
195 desc.storage_mode, supports_memoryless_targets_, supports_uma_);
196
197 auto buffer = [device_ newBufferWithLength:desc.size
198 options:resource_options];
199 if (!buffer) {
200 return nullptr;
201 }
202 return std::shared_ptr<DeviceBufferMTL>(new DeviceBufferMTL(desc, //
203 buffer, //
204 storage_mode //
205 ));
206 }
207
208 std::shared_ptr<Texture> AllocatorMTL::OnCreateTexture(
209 const TextureDescriptor& desc) {
210 if (!IsValid()) {
211 return nullptr;
212 }
213
214 auto mtl_texture_desc = ToMTLTextureDescriptor(desc);
215
216 if (!mtl_texture_desc) {
217 VALIDATION_LOG << "Texture descriptor was invalid.";
218 return nullptr;
219 }
220
221 mtl_texture_desc.storageMode = ToMTLStorageMode(
222 desc.storage_mode, supports_memoryless_targets_, supports_uma_);
223
224 if (@available(macOS 12.5, ios 15.0, *)) {
225 if (desc.compression_type == CompressionType::kLossy &&
226 SupportsLossyTextureCompression(device_)) {
227 mtl_texture_desc.compressionType = MTLTextureCompressionTypeLossy;
228 }
229 }
230
231 #ifdef IMPELLER_DEBUG
232 if (desc.storage_mode != StorageMode::kDeviceTransient) {
233 debug_allocater_->Increment(desc.GetByteSizeOfAllMipLevels());
234 }
235 #endif // IMPELLER_DEBUG
236
237 auto texture = [device_ newTextureWithDescriptor:mtl_texture_desc];
238 if (!texture) {
239 return nullptr;
240 }
241 std::shared_ptr<TextureMTL> result_texture =
242 TextureMTL::Create(desc, texture);
243 #ifdef IMPELLER_DEBUG
244 result_texture->SetDebugAllocator(debug_allocater_);
245 #endif // IMPELLER_DEBUG
246
247 return result_texture;
248 }
249
250 uint16_t AllocatorMTL::MinimumBytesPerRow(PixelFormat format) const {
251 return static_cast<uint16_t>([device_
252 minimumLinearTextureAlignmentForPixelFormat:ToMTLPixelFormat(format)]);
253 }
254
255 ISize AllocatorMTL::GetMaxTextureSizeSupported() const {
256 return max_texture_supported_;
257 }
258
259 size_t AllocatorMTL::DebugGetHeapUsage() const {
260 #ifdef IMPELLER_DEBUG
261 return debug_allocater_->GetAllocationSizeMB();
262 #else
263 return 0u;
264 #endif // IMPELLER_DEBUG
265 }
266
267 void AllocatorMTL::DebugTraceMemoryStatistics() const {
268 #ifdef IMPELLER_DEBUG
269 size_t allocated_size = DebugGetHeapUsage();
270 FML_TRACE_COUNTER("flutter", "AllocatorMTL",
271 reinterpret_cast<int64_t>(this), // Trace Counter ID
272 "MemoryBudgetUsageMB", allocated_size);
273 #endif // IMPELLER_DEBUG
274 }
275
276 } // namespace impeller
impeller::ISize
ISize64 ISize
Definition: size.h:140
impeller::DeviceHasUnifiedMemoryArchitecture
static bool DeviceHasUnifiedMemoryArchitecture(id< MTLDevice > device)
Definition: allocator_mtl.mm:39
impeller::AllocatorMTL::DebugGetHeapUsage
size_t DebugGetHeapUsage() const override
Definition: allocator_mtl.mm:259
formats.h
allocator_mtl.h
impeller::DebugAllocatorStats::GetAllocationSizeMB
size_t GetAllocationSizeMB()
Get the current tracked allocation size in MB.
Definition: allocator_mtl.mm:101
formats_mtl.h
impeller::SupportsLossyTextureCompression
static bool SupportsLossyTextureCompression(id< MTLDevice > device)
Definition: allocator_mtl.mm:82
impeller::AllocatorMTL::AllocatorMTL
AllocatorMTL()
impeller::StorageMode::kHostVisible
@ kHostVisible
validation.h
impeller::ToMTLStorageMode
static MTLStorageMode ToMTLStorageMode(StorageMode mode, bool supports_memoryless_targets, bool supports_uma)
Definition: allocator_mtl.mm:158
impeller::PixelFormat
PixelFormat
The Pixel formats supported by Impeller. The naming convention denotes the usage of the component,...
Definition: formats.h:99
impeller::ToMTLTextureDescriptor
MTLTextureDescriptor * ToMTLTextureDescriptor(const TextureDescriptor &desc)
Definition: formats_mtl.mm:86
impeller::DebugAllocatorStats::Decrement
void Decrement(size_t size)
Decrement the tracked allocation size in bytes.
Definition: allocator_mtl.mm:97
impeller::TSize
Definition: size.h:19
impeller::StorageMode::kDeviceTransient
@ kDeviceTransient
impeller::StorageMode
StorageMode
Specified where the allocation resides and how it is used.
Definition: formats.h:32
impeller::ToMTLResourceOptions
static MTLResourceOptions ToMTLResourceOptions(StorageMode type, bool supports_memoryless_targets, bool supports_uma)
Definition: allocator_mtl.mm:126
impeller::StorageMode::kDevicePrivate
@ kDevicePrivate
impeller::DeviceSupportsDeviceTransientTargets
static bool DeviceSupportsDeviceTransientTargets(id< MTLDevice > device)
Definition: allocator_mtl.mm:18
impeller::DeviceMaxTextureSizeSupported
static ISize DeviceMaxTextureSizeSupported(id< MTLDevice > device)
Definition: allocator_mtl.mm:54
type
GLenum type
Definition: blit_command_gles.cc:126
impeller::TextureMTL::Create
static std::shared_ptr< TextureMTL > Create(TextureDescriptor desc, id< MTLTexture > texture)
Definition: texture_mtl.mm:58
impeller::CompressionType::kLossy
@ kLossy
impeller::ToMTLPixelFormat
constexpr MTLPixelFormat ToMTLPixelFormat(PixelFormat format)
Definition: formats_mtl.h:76
impeller::AllocatorMTL::~AllocatorMTL
~AllocatorMTL() override
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:73
impeller::DebugAllocatorStats::Increment
void Increment(size_t size)
Increment the tracked allocation size in bytes.
Definition: allocator_mtl.mm:93
std
Definition: comparable.h:95
texture_mtl.h
impeller
Definition: aiks_blend_unittests.cc:18
device_buffer_mtl.h