Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/metal/metal_device_properties.cpp
20919 views
1
/**************************************************************************/
2
/* metal_device_properties.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
/**************************************************************************/
32
/* */
33
/* Portions of this code were derived from MoltenVK. */
34
/* */
35
/* Copyright (c) 2015-2023 The Brenwill Workshop Ltd. */
36
/* (http://www.brenwill.com) */
37
/* */
38
/* Licensed under the Apache License, Version 2.0 (the "License"); */
39
/* you may not use this file except in compliance with the License. */
40
/* You may obtain a copy of the License at */
41
/* */
42
/* http://www.apache.org/licenses/LICENSE-2.0 */
43
/* */
44
/* Unless required by applicable law or agreed to in writing, software */
45
/* distributed under the License is distributed on an "AS IS" BASIS, */
46
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or */
47
/* implied. See the License for the specific language governing */
48
/* permissions and limitations under the License. */
49
/**************************************************************************/
50
51
#include "metal_device_properties.h"
52
53
#include "metal_utils.h"
54
55
#include "servers/rendering/renderer_rd/effects/metal_fx.h"
56
57
#include <MetalFX/MetalFX.hpp>
58
#include <spirv_cross.hpp>
59
#include <spirv_msl.hpp>
60
61
#include <unistd.h>
62
63
// Common scaling multipliers.
64
#define KIBI (1024)
65
#define MEBI (KIBI * KIBI)
66
67
#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED < 140000) || (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED < 170000)
68
constexpr MTL::GPUFamily GPUFamilyApple9 = static_cast<MTL::GPUFamily>(1009);
69
#else
70
constexpr MTL::GPUFamily GPUFamilyApple9 = MTL::GPUFamilyApple9;
71
#endif
72
73
API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0), visionos(1.0))
74
MTL::GPUFamily &operator--(MTL::GPUFamily &p_family) {
75
p_family = static_cast<MTL::GPUFamily>(static_cast<int>(p_family) - 1);
76
if (p_family < MTL::GPUFamilyApple1) {
77
p_family = GPUFamilyApple9;
78
}
79
80
return p_family;
81
}
82
83
void MetalDeviceProperties::init_features(MTL::Device *p_device) {
84
features = {};
85
86
MTL::CompileOptions *opts = MTL::CompileOptions::alloc()->init();
87
MTL::LanguageVersion lang_version = opts->languageVersion();
88
features.msl_max_version = make_msl_version((static_cast<uint32_t>(lang_version) >> 0x10) & 0xff, (static_cast<uint32_t>(lang_version) >> 0x00) & 0xff);
89
features.msl_target_version = features.msl_max_version;
90
opts->release();
91
if (String version = OS::get_singleton()->get_environment("GODOT_MTL_TARGET_VERSION"); !version.is_empty()) {
92
if (version != "max") {
93
Vector<String> parts = version.split(".", true, 2);
94
if (parts.size() == 2) {
95
uint32_t major = parts[0].to_int();
96
uint32_t minor = parts[1].to_int();
97
uint32_t msl_version = make_msl_version(major, minor);
98
if (msl_version < MSL_VERSION_23 || msl_version > MSL_VERSION_40) {
99
WARN_PRINT(vformat("GODOT_MTL_TARGET_VERSION: invalid MSL version '%d.%d'", major, minor));
100
} else {
101
print_line(vformat("Override: Targeting Metal version %d.%d", major, minor));
102
features.msl_target_version = msl_version;
103
}
104
} else {
105
WARN_PRINT("GODOT_MTL_TARGET_VERSION: invalid version string format. Expected major.minor or 'max'.");
106
}
107
}
108
}
109
110
features.highestFamily = MTL::GPUFamilyApple1;
111
for (MTL::GPUFamily family = GPUFamilyApple9; family >= MTL::GPUFamilyApple1; --family) {
112
if (p_device->supportsFamily(family)) {
113
features.highestFamily = family;
114
break;
115
}
116
}
117
118
if (__builtin_available(macOS 11, iOS 16.4, tvOS 16.4, *)) {
119
features.supportsBCTextureCompression = p_device->supportsBCTextureCompression();
120
} else {
121
features.supportsBCTextureCompression = false;
122
}
123
124
#if TARGET_OS_OSX
125
features.supportsDepth24Stencil8 = p_device->isDepth24Stencil8PixelFormatSupported();
126
#endif
127
128
if (__builtin_available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
129
features.supports32BitFloatFiltering = p_device->supports32BitFloatFiltering();
130
features.supports32BitMSAA = p_device->supports32BitMSAA();
131
}
132
133
if (__builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) {
134
features.supports_gpu_address = true;
135
}
136
137
features.hostMemoryPageSize = sysconf(_SC_PAGESIZE);
138
139
for (SampleCount sc = SampleCount1; sc <= SampleCount64; sc <<= 1) {
140
if (p_device->supportsTextureSampleCount(sc)) {
141
features.supportedSampleCounts |= sc;
142
}
143
}
144
145
features.layeredRendering = p_device->supportsFamily(MTL::GPUFamilyApple5);
146
features.multisampleLayeredRendering = p_device->supportsFamily(MTL::GPUFamilyApple7);
147
features.tessellationShader = p_device->supportsFamily(MTL::GPUFamilyApple3);
148
features.imageCubeArray = p_device->supportsFamily(MTL::GPUFamilyApple3);
149
features.quadPermute = p_device->supportsFamily(MTL::GPUFamilyApple4);
150
features.simdPermute = p_device->supportsFamily(MTL::GPUFamilyApple6);
151
features.simdReduction = p_device->supportsFamily(MTL::GPUFamilyApple7);
152
features.argument_buffers_tier = p_device->argumentBuffersSupport();
153
features.supports_image_atomic_32_bit = p_device->supportsFamily(MTL::GPUFamilyApple6);
154
features.supports_image_atomic_64_bit = p_device->supportsFamily(GPUFamilyApple9) || (p_device->supportsFamily(MTL::GPUFamilyApple8) && p_device->supportsFamily(MTL::GPUFamilyMac2));
155
156
if (features.msl_target_version >= MSL_VERSION_31) {
157
// Native atomics are only supported on 3.1 and above.
158
if (__builtin_available(macOS 14.0, iOS 17.0, tvOS 17.0, visionOS 1.0, *)) {
159
features.supports_native_image_atomics = true;
160
}
161
}
162
163
if (OS::get_singleton()->get_environment("GODOT_MTL_DISABLE_IMAGE_ATOMICS") == "1") {
164
features.supports_native_image_atomics = false;
165
}
166
167
if (__builtin_available(macOS 15.0, iOS 18.0, tvOS 18.0, visionOS 2.0, *)) {
168
features.supports_residency_sets = true;
169
} else {
170
features.supports_residency_sets = false;
171
}
172
173
if (__builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) {
174
features.needs_arg_encoders = !(p_device->supportsFamily(MTL::GPUFamilyMetal3) && features.argument_buffers_tier == MTL::ArgumentBuffersTier2);
175
}
176
177
if (String v = OS::get_singleton()->get_environment("GODOT_MTL_DISABLE_ARGUMENT_BUFFERS"); v == "1") {
178
features.use_argument_buffers = false;
179
}
180
181
if (__builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) {
182
features.metal_fx_spatial = MTLFX::SpatialScalerDescriptor::supportsDevice(p_device);
183
#ifdef METAL_MFXTEMPORAL_ENABLED
184
features.metal_fx_temporal = MTLFX::TemporalScalerDescriptor::supportsDevice(p_device);
185
#else
186
features.metal_fx_temporal = false;
187
#endif
188
}
189
}
190
191
void MetalDeviceProperties::init_limits(MTL::Device *p_device) {
192
using std::max;
193
using std::min;
194
195
// FST: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
196
197
// FST: Maximum number of layers per 1D texture array, 2D texture array, or 3D texture.
198
limits.maxImageArrayLayers = 2048;
199
if (p_device->supportsFamily(MTL::GPUFamilyApple3)) {
200
// FST: Maximum 2D texture width and height.
201
limits.maxFramebufferWidth = 16384;
202
limits.maxFramebufferHeight = 16384;
203
limits.maxViewportDimensionX = 16384;
204
limits.maxViewportDimensionY = 16384;
205
// FST: Maximum 1D texture width.
206
limits.maxImageDimension1D = 16384;
207
// FST: Maximum 2D texture width and height.
208
limits.maxImageDimension2D = 16384;
209
// FST: Maximum cube map texture width and height.
210
limits.maxImageDimensionCube = 16384;
211
} else {
212
// FST: Maximum 2D texture width and height.
213
limits.maxFramebufferWidth = 8192;
214
limits.maxFramebufferHeight = 8192;
215
limits.maxViewportDimensionX = 8192;
216
limits.maxViewportDimensionY = 8192;
217
// FST: Maximum 1D texture width.
218
limits.maxImageDimension1D = 8192;
219
// FST: Maximum 2D texture width and height.
220
limits.maxImageDimension2D = 8192;
221
// FST: Maximum cube map texture width and height.
222
limits.maxImageDimensionCube = 8192;
223
}
224
// FST: Maximum 3D texture width, height, and depth.
225
limits.maxImageDimension3D = 2048;
226
227
limits.maxThreadsPerThreadGroup = p_device->maxThreadsPerThreadgroup();
228
// No effective limits.
229
limits.maxComputeWorkGroupCount = { std::numeric_limits<uint32_t>::max(), std::numeric_limits<uint32_t>::max(), std::numeric_limits<uint32_t>::max() };
230
// https://github.com/KhronosGroup/MoltenVK/blob/568cc3acc0e2299931fdaecaaa1fc3ec5b4af281/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h#L85
231
limits.maxBoundDescriptorSets = SPIRV_CROSS_NAMESPACE::kMaxArgumentBuffers;
232
// FST: Maximum number of color render targets per render pass descriptor.
233
limits.maxColorAttachments = 8;
234
235
// Maximum number of textures the device can access, per stage, from an argument buffer.
236
if (p_device->supportsFamily(MTL::GPUFamilyApple6)) {
237
limits.maxTexturesPerArgumentBuffer = 1'000'000;
238
} else if (p_device->supportsFamily(MTL::GPUFamilyApple4)) {
239
limits.maxTexturesPerArgumentBuffer = 96;
240
} else {
241
limits.maxTexturesPerArgumentBuffer = 31;
242
}
243
244
// Maximum number of samplers the device can access, per stage, from an argument buffer.
245
if (p_device->supportsFamily(MTL::GPUFamilyApple6)) {
246
limits.maxSamplersPerArgumentBuffer = 1024;
247
} else {
248
limits.maxSamplersPerArgumentBuffer = 16;
249
}
250
251
// Maximum number of buffers the device can access, per stage, from an argument buffer.
252
if (p_device->supportsFamily(MTL::GPUFamilyApple6)) {
253
limits.maxBuffersPerArgumentBuffer = std::numeric_limits<uint64_t>::max();
254
} else if (p_device->supportsFamily(MTL::GPUFamilyApple4)) {
255
limits.maxBuffersPerArgumentBuffer = 96;
256
} else {
257
limits.maxBuffersPerArgumentBuffer = 31;
258
}
259
260
limits.minSubgroupSize = limits.maxSubgroupSize = 1;
261
// These values were taken from MoltenVK.
262
if (features.simdPermute) {
263
limits.minSubgroupSize = 4;
264
limits.maxSubgroupSize = 32;
265
} else if (features.quadPermute) {
266
limits.minSubgroupSize = limits.maxSubgroupSize = 4;
267
}
268
269
limits.subgroupSupportedShaderStages.set_flag(RDD::ShaderStage::SHADER_STAGE_COMPUTE_BIT);
270
if (features.tessellationShader) {
271
limits.subgroupSupportedShaderStages.set_flag(RDD::ShaderStage::SHADER_STAGE_TESSELATION_CONTROL_BIT);
272
}
273
limits.subgroupSupportedShaderStages.set_flag(RDD::ShaderStage::SHADER_STAGE_FRAGMENT_BIT);
274
275
limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_BASIC_BIT);
276
if (features.simdPermute || features.quadPermute) {
277
limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_VOTE_BIT);
278
limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_BALLOT_BIT);
279
limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_SHUFFLE_BIT);
280
limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_SHUFFLE_RELATIVE_BIT);
281
}
282
283
if (features.simdReduction) {
284
limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_ARITHMETIC_BIT);
285
}
286
287
if (features.quadPermute) {
288
limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_QUAD_BIT);
289
}
290
291
limits.maxBufferLength = p_device->maxBufferLength();
292
293
// FST: Maximum size of vertex descriptor layout stride.
294
limits.maxVertexDescriptorLayoutStride = std::numeric_limits<uint64_t>::max();
295
296
// Maximum number of viewports.
297
if (p_device->supportsFamily(MTL::GPUFamilyApple5)) {
298
limits.maxViewports = 16;
299
} else {
300
limits.maxViewports = 1;
301
}
302
303
limits.maxPerStageBufferCount = 31;
304
limits.maxPerStageSamplerCount = 16;
305
if (p_device->supportsFamily(MTL::GPUFamilyApple6)) {
306
limits.maxPerStageTextureCount = 128;
307
} else if (p_device->supportsFamily(MTL::GPUFamilyApple4)) {
308
limits.maxPerStageTextureCount = 96;
309
} else {
310
limits.maxPerStageTextureCount = 31;
311
}
312
313
limits.maxVertexInputAttributes = 31;
314
limits.maxVertexInputBindings = 31;
315
limits.maxVertexInputBindingStride = (2 * KIBI);
316
limits.maxShaderVaryings = 31; // Accurate on Apple4 and above. See: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
317
318
if (p_device->supportsFamily(MTL::GPUFamilyApple4)) {
319
limits.maxThreadGroupMemoryAllocation = 32768;
320
} else if (p_device->supportsFamily(MTL::GPUFamilyApple3)) {
321
limits.maxThreadGroupMemoryAllocation = 16384;
322
} else {
323
limits.maxThreadGroupMemoryAllocation = 16352;
324
}
325
326
#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
327
limits.minUniformBufferOffsetAlignment = 64;
328
#endif
329
330
#if TARGET_OS_OSX
331
// This is Apple Silicon specific.
332
limits.minUniformBufferOffsetAlignment = 16;
333
#endif
334
335
limits.maxDrawIndexedIndexValue = std::numeric_limits<uint32_t>::max() - 1;
336
337
#ifdef METAL_MFXTEMPORAL_ENABLED
338
if (__builtin_available(macOS 14.0, iOS 17.0, tvOS 17.0, *)) {
339
limits.temporalScalerInputContentMinScale = MTLFX::TemporalScalerDescriptor::supportedInputContentMinScale(p_device);
340
limits.temporalScalerInputContentMaxScale = MTLFX::TemporalScalerDescriptor::supportedInputContentMaxScale(p_device);
341
} else {
342
// Defaults taken from macOS 14+
343
limits.temporalScalerInputContentMinScale = 1.0;
344
limits.temporalScalerInputContentMaxScale = 3.0;
345
}
346
#else
347
// Defaults taken from macOS 14+
348
limits.temporalScalerInputContentMinScale = 1.0;
349
limits.temporalScalerInputContentMaxScale = 3.0;
350
#endif
351
}
352
353
void MetalDeviceProperties::init_os_props() {
354
NS::OperatingSystemVersion ver = NS::ProcessInfo::processInfo()->operatingSystemVersion();
355
os_version = (uint32_t)ver.majorVersion * 10000 + (uint32_t)ver.minorVersion * 100 + (uint32_t)ver.patchVersion;
356
}
357
358
MetalDeviceProperties::MetalDeviceProperties(MTL::Device *p_device) {
359
init_features(p_device);
360
init_limits(p_device);
361
init_os_props();
362
}
363
364
MetalDeviceProperties::~MetalDeviceProperties() {
365
}
366
367
SampleCount MetalDeviceProperties::find_nearest_supported_sample_count(RenderingDevice::TextureSamples p_samples) const {
368
SampleCount supported = features.supportedSampleCounts;
369
if (supported & sample_count[p_samples]) {
370
return sample_count[p_samples];
371
}
372
373
SampleCount requested_sample_count = sample_count[p_samples];
374
// Find the nearest supported sample count.
375
while (requested_sample_count > SampleCount1) {
376
if (supported & requested_sample_count) {
377
return requested_sample_count;
378
}
379
requested_sample_count = (SampleCount)(requested_sample_count >> 1);
380
}
381
382
return SampleCount1;
383
}
384
385
// region static members
386
387
const SampleCount MetalDeviceProperties::sample_count[RenderingDevice::TextureSamples::TEXTURE_SAMPLES_MAX] = {
388
SampleCount1,
389
SampleCount2,
390
SampleCount4,
391
SampleCount8,
392
SampleCount16,
393
SampleCount32,
394
SampleCount64,
395
};
396
397
// endregion
398
399