Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/metal/rendering_shader_container_metal.cpp
20919 views
1
/**************************************************************************/
2
/* rendering_shader_container_metal.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
#include "rendering_shader_container_metal.h"
32
33
#include "metal_utils.h"
34
35
#include "core/io/file_access.h"
36
#include "core/io/marshalls.h"
37
#include "core/templates/fixed_vector.h"
38
#include "servers/rendering/rendering_device.h"
39
40
#include "thirdparty/spirv-reflect/spirv_reflect.h"
41
42
#include <Metal/Metal.hpp>
43
#include <spirv.hpp>
44
#include <spirv_msl.hpp>
45
#include <spirv_parser.hpp>
46
47
void RenderingShaderContainerMetal::_initialize_toolchain_properties() {
48
if (compiler_props.is_valid()) {
49
return;
50
}
51
52
String sdk;
53
switch (device_profile->platform) {
54
case MetalDeviceProfile::Platform::macOS:
55
sdk = "macosx";
56
break;
57
case MetalDeviceProfile::Platform::iOS:
58
sdk = "iphoneos";
59
break;
60
case MetalDeviceProfile::Platform::visionOS:
61
sdk = "xros";
62
break;
63
}
64
65
Vector<String> parts{ "echo", R"("")", "|", "/usr/bin/xcrun", "-sdk", sdk, "metal", "-E", "-dM", "-x", "metal" };
66
67
switch (device_profile->platform) {
68
case MetalDeviceProfile::Platform::macOS: {
69
parts.push_back("-mtargetos=macos" + device_profile->min_os_version.to_compiler_os_version());
70
break;
71
}
72
case MetalDeviceProfile::Platform::iOS: {
73
parts.push_back("-mtargetos=ios" + device_profile->min_os_version.to_compiler_os_version());
74
break;
75
}
76
case MetalDeviceProfile::Platform::visionOS: {
77
parts.push_back("-mtargetos=xros" + device_profile->min_os_version.to_compiler_os_version());
78
break;
79
}
80
}
81
82
parts.append_array({ "-", "|", "grep", "-E", R"(\"__METAL_VERSION__|__ENVIRONMENT_OS\")" });
83
84
List<String> args = { "-c", String(" ").join(parts) };
85
86
String r_pipe;
87
int exit_code;
88
Error err = OS::get_singleton()->execute("sh", args, &r_pipe, &exit_code, true);
89
ERR_FAIL_COND_MSG(err != OK, "Failed to determine Metal toolchain properties");
90
91
// Parse the lines, which are in the form:
92
//
93
// #define VARNAME VALUE
94
Vector<String> lines = r_pipe.split("\n", false);
95
for (String &line : lines) {
96
Vector<String> name_val = line.trim_prefix("#define ").split(" ");
97
if (name_val.size() != 2) {
98
continue;
99
}
100
if (name_val[0] == "__ENVIRONMENT_OS_VERSION_MIN_REQUIRED__") {
101
compiler_props.os_version_min_required = MinOsVersion((uint32_t)name_val[1].to_int());
102
} else if (name_val[0] == "__METAL_VERSION__") {
103
uint32_t ver = (uint32_t)name_val[1].to_int();
104
uint32_t maj = ver / 100;
105
uint32_t min = (ver % 100) / 10;
106
compiler_props.metal_version = make_msl_version(maj, min);
107
}
108
109
if (compiler_props.is_valid()) {
110
break;
111
}
112
}
113
}
114
115
Error RenderingShaderContainerMetal::compile_metal_source(const char *p_source, const StageData &p_stage_data, Vector<uint8_t> &r_binary_data) {
116
String name(shader_name.ptr());
117
if (name.contains_char(':')) {
118
name = name.replace_char(':', '_');
119
}
120
Error r_error;
121
Ref<FileAccess> source_file = FileAccess::create_temp(FileAccess::ModeFlags::READ_WRITE,
122
name + "_" + itos(p_stage_data.hash.short_sha()),
123
"metal", false, &r_error);
124
ERR_FAIL_COND_V_MSG(r_error != OK, r_error, "Unable to create temporary source file.");
125
if (!source_file->store_buffer((const uint8_t *)p_source, strlen(p_source))) {
126
ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Unable to write temporary source file");
127
}
128
source_file->flush();
129
Ref<FileAccess> result_file = FileAccess::create_temp(FileAccess::ModeFlags::READ_WRITE,
130
name + "_" + itos(p_stage_data.hash.short_sha()),
131
"metallib", false, &r_error);
132
133
ERR_FAIL_COND_V_MSG(r_error != OK, r_error, "Unable to create temporary target file");
134
135
String sdk;
136
switch (device_profile->platform) {
137
case MetalDeviceProfile::Platform::macOS:
138
sdk = "macosx";
139
break;
140
case MetalDeviceProfile::Platform::iOS:
141
sdk = "iphoneos";
142
break;
143
case MetalDeviceProfile::Platform::visionOS:
144
sdk = "xros";
145
break;
146
}
147
148
// Build the .metallib binary.
149
{
150
List<String> args{ "-sdk", sdk, "metal", "-O3" };
151
152
// Compile metal shaders for the minimum supported target instead of the host machine.
153
switch (device_profile->platform) {
154
case MetalDeviceProfile::Platform::macOS: {
155
args.push_back("-mtargetos=macos" + device_profile->min_os_version.to_compiler_os_version());
156
break;
157
}
158
case MetalDeviceProfile::Platform::iOS: {
159
args.push_back("-mtargetos=ios" + device_profile->min_os_version.to_compiler_os_version());
160
break;
161
}
162
case MetalDeviceProfile::Platform::visionOS: {
163
args.push_back("-mtargetos=xros" + device_profile->min_os_version.to_compiler_os_version());
164
break;
165
}
166
}
167
168
if (p_stage_data.is_position_invariant) {
169
args.push_back("-fpreserve-invariance");
170
}
171
args.push_back("-fmetal-math-mode=fast");
172
args.push_back(source_file->get_path_absolute());
173
args.push_back("-o");
174
args.push_back(result_file->get_path_absolute());
175
String r_pipe;
176
int exit_code;
177
Error err = OS::get_singleton()->execute("/usr/bin/xcrun", args, &r_pipe, &exit_code, true);
178
if (!r_pipe.is_empty()) {
179
print_line(r_pipe);
180
}
181
if (err != OK) {
182
ERR_PRINT(vformat("Metal compiler returned error code: %d", err));
183
}
184
185
if (exit_code != 0) {
186
ERR_PRINT(vformat("Metal compiler exited with error code: %d", exit_code));
187
}
188
int len = result_file->get_length();
189
ERR_FAIL_COND_V_MSG(len == 0, ERR_CANT_CREATE, "Metal compiler created empty library");
190
}
191
192
// Strip the source from the binary.
193
{
194
List<String> args{ "-sdk", sdk, "metal-dsymutil", "--remove-source", result_file->get_path_absolute() };
195
String r_pipe;
196
int exit_code;
197
Error err = OS::get_singleton()->execute("/usr/bin/xcrun", args, &r_pipe, &exit_code, true);
198
if (!r_pipe.is_empty()) {
199
print_line(r_pipe);
200
}
201
if (err != OK) {
202
ERR_PRINT(vformat("metal-dsymutil tool returned error code: %d", err));
203
}
204
205
if (exit_code != 0) {
206
ERR_PRINT(vformat("metal-dsymutil Compiler exited with error code: %d", exit_code));
207
}
208
int len = result_file->get_length();
209
ERR_FAIL_COND_V_MSG(len == 0, ERR_CANT_CREATE, "metal-dsymutil tool created empty library");
210
}
211
212
r_binary_data = result_file->get_buffer(result_file->get_length());
213
214
return OK;
215
}
216
217
#pragma clang diagnostic push
218
#pragma clang diagnostic ignored "-Wunguarded-availability"
219
220
static spv::ExecutionModel SHADER_STAGE_REMAP[RDD::SHADER_STAGE_MAX] = {
221
[RDD::SHADER_STAGE_VERTEX] = spv::ExecutionModelVertex,
222
[RDD::SHADER_STAGE_FRAGMENT] = spv::ExecutionModelFragment,
223
[RDD::SHADER_STAGE_TESSELATION_CONTROL] = spv::ExecutionModelTessellationControl,
224
[RDD::SHADER_STAGE_TESSELATION_EVALUATION] = spv::ExecutionModelTessellationEvaluation,
225
[RDD::SHADER_STAGE_COMPUTE] = spv::ExecutionModelGLCompute,
226
};
227
228
spv::ExecutionModel get_stage(uint32_t p_stages_mask, RDD::ShaderStage p_stage) {
229
if (p_stages_mask & (1 << p_stage)) {
230
return SHADER_STAGE_REMAP[p_stage];
231
}
232
return spv::ExecutionModel::ExecutionModelMax;
233
}
234
235
spv::ExecutionModel map_stage(RDD::ShaderStage p_stage) {
236
return SHADER_STAGE_REMAP[p_stage];
237
}
238
239
Error RenderingShaderContainerMetal::reflect_spirv(const ReflectShader &p_shader) {
240
// const LocalVector<ReflectShaderStage> &p_spirv = p_shader.shader_stages;
241
//
242
// using ShaderStage = RenderingDeviceCommons::ShaderStage;
243
//
244
// const uint32_t spirv_size = p_spirv.size();
245
//
246
// HashSet<uint32_t> atomic_spirv_ids;
247
// bool atomics_scanned = false;
248
// auto scan_atomic_accesses = [&atomic_spirv_ids, &p_spirv, spirv_size, &atomics_scanned]() {
249
// if (atomics_scanned) {
250
// return;
251
// }
252
//
253
// for (uint32_t i = 0; i < spirv_size + 0; i++) {
254
// const uint32_t STARTING_WORD_INDEX = 5;
255
// Span<uint32_t> spirv = p_spirv[i].spirv();
256
// const uint32_t *words = spirv.ptr() + STARTING_WORD_INDEX;
257
// while (words < spirv.end()) {
258
// uint32_t instruction = *words;
259
// uint16_t word_count = instruction >> 16;
260
// SpvOp opcode = (SpvOp)(instruction & 0xFFFF);
261
// if (opcode == SpvOpImageTexelPointer) {
262
// uint32_t image_var_id = words[3];
263
// atomic_spirv_ids.insert(image_var_id);
264
// }
265
// words += word_count;
266
// }
267
// }
268
//
269
// atomics_scanned = true;
270
// };
271
//
272
// for (uint32_t i = 0; i < spirv_size + 0; i++) {
273
// ShaderStage stage = p_spirv[i].shader_stage;
274
// ShaderStage stage_flag = (ShaderStage)(1 << p_spirv[i].shader_stage);
275
// SpvReflectResult result;
276
//
277
// const SpvReflectShaderModule &module = p_spirv[i].module();
278
//
279
// uint32_t binding_count = 0;
280
// result = spvReflectEnumerateDescriptorBindings(&module, &binding_count, nullptr);
281
// CRASH_COND(result != SPV_REFLECT_RESULT_SUCCESS);
282
//
283
// if (binding_count > 0) {
284
// LocalVector<SpvReflectDescriptorBinding *> bindings;
285
// bindings.resize_uninitialized(binding_count);
286
// result = spvReflectEnumerateDescriptorBindings(&module, &binding_count, bindings.ptr());
287
//
288
// for (uint32_t j = 0; j < binding_count; j++) {
289
// const SpvReflectDescriptorBinding &binding = *bindings[j];
290
//
291
// switch (binding.descriptor_type) {
292
// case SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
293
// case SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
294
// case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_IMAGE:
295
// case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
296
// break;
297
// default:
298
// break;
299
// }
300
// }
301
// }
302
// }
303
//
304
return OK;
305
}
306
307
bool RenderingShaderContainerMetal::_set_code_from_spirv(const ReflectShader &p_shader) {
308
using namespace spirv_cross;
309
using spirv_cross::CompilerMSL;
310
using spirv_cross::Resource;
311
312
const LocalVector<ReflectShaderStage> &p_spirv = p_shader.shader_stages;
313
314
if (export_mode) {
315
_initialize_toolchain_properties();
316
}
317
318
// initialize Metal-specific reflection data
319
shaders.resize(p_spirv.size());
320
mtl_shaders.resize(p_spirv.size());
321
mtl_reflection_binding_set_uniforms_data.resize(reflection_binding_set_uniforms_data.size());
322
323
mtl_reflection_data.set_needs_view_mask_buffer(reflection_data.has_multiview);
324
mtl_reflection_data.profile = *device_profile;
325
326
CompilerMSL::Options msl_options{};
327
328
// Determine Metal language version.
329
uint32_t msl_version = 0;
330
{
331
if (export_mode && compiler_props.is_valid()) {
332
// Use the properties determined by the toolchain and minimum OS version.
333
msl_version = compiler_props.metal_version;
334
mtl_reflection_data.os_min_version = compiler_props.os_version_min_required;
335
} else {
336
msl_version = device_profile->features.msl_version;
337
mtl_reflection_data.os_min_version = MinOsVersion();
338
}
339
uint32_t msl_ver_maj = 0;
340
uint32_t msl_ver_min = 0;
341
parse_msl_version(msl_version, msl_ver_maj, msl_ver_min);
342
msl_options.set_msl_version(msl_ver_maj, msl_ver_min);
343
mtl_reflection_data.msl_version = msl_version;
344
}
345
346
msl_options.platform = device_profile->platform == MetalDeviceProfile::Platform::macOS ? CompilerMSL::Options::macOS : CompilerMSL::Options::iOS;
347
348
if (device_profile->platform == MetalDeviceProfile::Platform::iOS) {
349
msl_options.ios_use_simdgroup_functions = device_profile->features.simdPermute;
350
msl_options.ios_support_base_vertex_instance = true;
351
}
352
353
if (device_profile->features.use_argument_buffers) {
354
msl_options.argument_buffers_tier = CompilerMSL::Options::ArgumentBuffersTier::Tier2;
355
msl_options.argument_buffers = true;
356
mtl_reflection_data.set_uses_argument_buffers(true);
357
} else {
358
msl_options.argument_buffers_tier = CompilerMSL::Options::ArgumentBuffersTier::Tier1;
359
// Tier 1 argument buffers don't support writable textures, so we disable them completely.
360
msl_options.argument_buffers = false;
361
mtl_reflection_data.set_uses_argument_buffers(false);
362
}
363
msl_options.force_active_argument_buffer_resources = true;
364
msl_options.pad_argument_buffer_resources = true;
365
msl_options.texture_buffer_native = true; // Enable texture buffer support.
366
msl_options.use_framebuffer_fetch_subpasses = false;
367
msl_options.pad_fragment_output_components = true;
368
msl_options.r32ui_alignment_constant_id = R32UI_ALIGNMENT_CONSTANT_ID;
369
msl_options.agx_manual_cube_grad_fixup = true;
370
if (reflection_data.has_multiview) {
371
msl_options.multiview = true;
372
msl_options.multiview_layered_rendering = true;
373
msl_options.view_mask_buffer_index = VIEW_MASK_BUFFER_INDEX;
374
}
375
if (msl_version >= MSL_VERSION_32) {
376
// All 3.2+ versions support device coherence, so we can disable texture fences.
377
msl_options.readwrite_texture_fences = false;
378
}
379
380
CompilerGLSL::Options options{};
381
options.vertex.flip_vert_y = true;
382
#if DEV_ENABLED
383
options.emit_line_directives = true;
384
#endif
385
386
// Assign MSL bindings for all the descriptor sets.
387
typedef std::pair<MSLResourceBinding, uint32_t> MSLBindingInfo;
388
LocalVector<MSLBindingInfo> spirv_bindings;
389
MSLResourceBinding push_constant_resource_binding;
390
{
391
enum IndexType {
392
Texture,
393
Buffer,
394
Sampler,
395
Max,
396
};
397
398
uint32_t dset_count = p_shader.uniform_sets.size();
399
uint32_t size = reflection_binding_set_uniforms_data.size();
400
spirv_bindings.resize(size);
401
402
uint32_t indices[IndexType::Max] = { 0 };
403
auto next_index = [&indices](IndexType p_t, uint32_t p_stride) -> uint32_t {
404
uint32_t v = indices[p_t];
405
indices[p_t] += p_stride;
406
return v;
407
};
408
409
uint32_t idx_dset = 0;
410
MSLBindingInfo *iter = spirv_bindings.ptr();
411
UniformData *found = mtl_reflection_binding_set_uniforms_data.ptrw();
412
UniformData::IndexType shader_index_type = msl_options.argument_buffers ? UniformData::IndexType::ARG : UniformData::IndexType::SLOT;
413
414
for (const ReflectDescriptorSet &dset : p_shader.uniform_sets) {
415
// Reset the index count for each descriptor set, as this is an index in to the argument table.
416
uint32_t next_arg_buffer_index = 0;
417
auto next_arg_index = [&next_arg_buffer_index](uint32_t p_stride) -> uint32_t {
418
uint32_t v = next_arg_buffer_index;
419
next_arg_buffer_index += p_stride;
420
return v;
421
};
422
423
for (const ReflectUniform &uniform : dset) {
424
const SpvReflectDescriptorBinding &binding = uniform.get_spv_reflect();
425
426
found->active_stages = uniform.stages;
427
428
RD::UniformType type = RD::UniformType(uniform.type);
429
uint32_t binding_stride = 1; // If this is an array, stride will be the length of the array.
430
if (uniform.length > 1) {
431
switch (type) {
432
case RDC::UNIFORM_TYPE_UNIFORM_BUFFER_DYNAMIC:
433
case RDC::UNIFORM_TYPE_STORAGE_BUFFER_DYNAMIC:
434
case RDC::UNIFORM_TYPE_UNIFORM_BUFFER:
435
case RDC::UNIFORM_TYPE_STORAGE_BUFFER:
436
// Buffers's length is its size, in bytes, so there is no stride.
437
break;
438
default: {
439
binding_stride = uniform.length;
440
found->array_length = uniform.length;
441
} break;
442
}
443
}
444
445
// Determine access type.
446
switch (binding.descriptor_type) {
447
case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_IMAGE: {
448
if (!(binding.decoration_flags & SPV_REFLECT_DECORATION_NON_WRITABLE)) {
449
if (!(binding.decoration_flags & SPV_REFLECT_DECORATION_NON_READABLE)) {
450
found->access = MTL::BindingAccessReadWrite;
451
} else {
452
found->access = MTL::BindingAccessWriteOnly;
453
}
454
}
455
} break;
456
case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
457
case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER: {
458
if (!(binding.decoration_flags & SPV_REFLECT_DECORATION_NON_WRITABLE) && !(binding.block.decoration_flags & SPV_REFLECT_DECORATION_NON_WRITABLE)) {
459
if (!(binding.decoration_flags & SPV_REFLECT_DECORATION_NON_READABLE) && !(binding.block.decoration_flags & SPV_REFLECT_DECORATION_NON_READABLE)) {
460
found->access = MTL::BindingAccessReadWrite;
461
} else {
462
found->access = MTL::BindingAccessWriteOnly;
463
}
464
}
465
} break;
466
default:
467
break;
468
}
469
470
switch (found->access) {
471
case MTL::BindingAccessReadOnly:
472
found->usage = MTL::ResourceUsageRead;
473
break;
474
case MTL::BindingAccessWriteOnly:
475
found->usage = MTL::ResourceUsageWrite;
476
break;
477
case MTL::BindingAccessReadWrite:
478
found->usage = MTL::ResourceUsageRead | MTL::ResourceUsageWrite;
479
break;
480
}
481
482
iter->second = uniform.stages;
483
MSLResourceBinding &rb = iter->first;
484
rb.desc_set = idx_dset;
485
rb.binding = uniform.binding;
486
rb.count = binding_stride;
487
488
switch (type) {
489
case RDC::UNIFORM_TYPE_SAMPLER: {
490
found->data_type = MTL::DataTypeSampler;
491
found->get_indexes(UniformData::IndexType::SLOT).sampler = next_index(Sampler, binding_stride);
492
found->get_indexes(UniformData::IndexType::ARG).sampler = next_arg_index(binding_stride);
493
494
rb.basetype = SPIRType::BaseType::Sampler;
495
496
} break;
497
case RDC::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE:
498
case RDC::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE_BUFFER: {
499
found->data_type = MTL::DataTypeTexture;
500
found->get_indexes(UniformData::IndexType::SLOT).texture = next_index(Texture, binding_stride);
501
found->get_indexes(UniformData::IndexType::SLOT).sampler = next_index(Sampler, binding_stride);
502
found->get_indexes(UniformData::IndexType::ARG).texture = next_arg_index(binding_stride);
503
found->get_indexes(UniformData::IndexType::ARG).sampler = next_arg_index(binding_stride);
504
rb.basetype = SPIRType::BaseType::SampledImage;
505
} break;
506
case RDC::UNIFORM_TYPE_TEXTURE:
507
case RDC::UNIFORM_TYPE_IMAGE:
508
case RDC::UNIFORM_TYPE_TEXTURE_BUFFER: {
509
found->data_type = MTL::DataTypeTexture;
510
found->get_indexes(UniformData::IndexType::SLOT).texture = next_index(Texture, binding_stride);
511
found->get_indexes(UniformData::IndexType::ARG).texture = next_arg_index(binding_stride);
512
rb.basetype = SPIRType::BaseType::Image;
513
} break;
514
case RDC::UNIFORM_TYPE_IMAGE_BUFFER:
515
CRASH_NOW_MSG("Unimplemented!"); // TODO.
516
break;
517
case RDC::UNIFORM_TYPE_UNIFORM_BUFFER_DYNAMIC:
518
case RDC::UNIFORM_TYPE_STORAGE_BUFFER_DYNAMIC:
519
case RDC::UNIFORM_TYPE_UNIFORM_BUFFER:
520
case RDC::UNIFORM_TYPE_STORAGE_BUFFER: {
521
found->data_type = MTL::DataTypePointer;
522
found->get_indexes(UniformData::IndexType::SLOT).buffer = next_index(Buffer, binding_stride);
523
found->get_indexes(UniformData::IndexType::ARG).buffer = next_arg_index(binding_stride);
524
rb.basetype = SPIRType::BaseType::Void;
525
} break;
526
case RDC::UNIFORM_TYPE_INPUT_ATTACHMENT: {
527
found->data_type = MTL::DataTypeTexture;
528
found->get_indexes(UniformData::IndexType::SLOT).texture = next_index(Texture, binding_stride);
529
found->get_indexes(UniformData::IndexType::ARG).texture = next_arg_index(binding_stride);
530
rb.basetype = SPIRType::BaseType::Image;
531
} break;
532
case RDC::UNIFORM_TYPE_MAX:
533
default:
534
CRASH_NOW_MSG("Unreachable");
535
}
536
537
// Specify the MSL resource bindings based on how the binding mode used by the shader.
538
rb.msl_buffer = found->get_indexes(shader_index_type).buffer;
539
rb.msl_texture = found->get_indexes(shader_index_type).texture;
540
rb.msl_sampler = found->get_indexes(shader_index_type).sampler;
541
542
if (found->data_type == MTL::DataTypeTexture) {
543
const SpvReflectImageTraits &image = uniform.get_spv_reflect().image;
544
545
switch (image.dim) {
546
case SpvDim1D: {
547
if (image.arrayed) {
548
found->texture_type = MTL::TextureType1DArray;
549
} else {
550
found->texture_type = MTL::TextureType1D;
551
}
552
} break;
553
case SpvDimSubpassData:
554
case SpvDim2D: {
555
if (image.arrayed && image.ms) {
556
found->texture_type = MTL::TextureType2DMultisampleArray;
557
} else if (image.arrayed) {
558
found->texture_type = MTL::TextureType2DArray;
559
} else if (image.ms) {
560
found->texture_type = MTL::TextureType2DMultisample;
561
} else {
562
found->texture_type = MTL::TextureType2D;
563
}
564
} break;
565
case SpvDim3D: {
566
found->texture_type = MTL::TextureType3D;
567
} break;
568
case SpvDimCube: {
569
if (image.arrayed) {
570
found->texture_type = MTL::TextureTypeCubeArray;
571
} else {
572
found->texture_type = MTL::TextureTypeCube;
573
}
574
} break;
575
case SpvDimRect: {
576
// Ignored.
577
} break;
578
case SpvDimBuffer: {
579
found->texture_type = MTL::TextureTypeTextureBuffer;
580
// If this is used with atomics, we need to use a read-write texture.
581
// scan_atomic_accesses();
582
// if (atomic_spirv_ids.find(uniform.spirv_id) != atomic_spirv_ids.end()) {
583
// rb.access = MTLBindingAccessReadWrite;
584
// found->access = MTLBindingAccessReadWrite;
585
// } else {
586
// rb.access = MTLBindingAccessReadOnly;
587
// found->access = MTLBindingAccessReadOnly;
588
// }
589
} break;
590
case SpvDimTileImageDataEXT: {
591
// Godot does not use this extension.
592
// See: https://registry.khronos.org/vulkan/specs/latest/man/html/VK_EXT_shader_tile_image.html
593
} break;
594
case SpvDimMax: {
595
// Add all enumerations to silence the compiler warning
596
// and generate future warnings, should a new one be added.
597
} break;
598
}
599
}
600
601
iter++;
602
found++;
603
}
604
idx_dset++;
605
}
606
607
if (reflection_data.push_constant_size > 0) {
608
push_constant_resource_binding.desc_set = ResourceBindingPushConstantDescriptorSet;
609
push_constant_resource_binding.basetype = SPIRType::BaseType::Void;
610
if (msl_options.argument_buffers) {
611
push_constant_resource_binding.msl_buffer = dset_count;
612
} else {
613
push_constant_resource_binding.msl_buffer = next_index(Buffer, 1);
614
}
615
mtl_reflection_data.push_constant_binding = push_constant_resource_binding.msl_buffer;
616
}
617
}
618
619
for (uint32_t i = 0; i < p_spirv.size(); i++) {
620
StageData &stage_data = mtl_shaders.write[i];
621
const ReflectShaderStage &v = p_spirv[i];
622
RD::ShaderStage stage = v.shader_stage;
623
Span<uint32_t> spirv = v.spirv();
624
Parser parser(spirv.ptr(), spirv.size());
625
try {
626
parser.parse();
627
} catch (CompilerError &e) {
628
ERR_FAIL_V_MSG(false, "Failed to parse IR at stage " + String(RD::SHADER_STAGE_NAMES[stage]) + ": " + e.what());
629
}
630
631
CompilerMSL compiler(std::move(parser.get_parsed_ir()));
632
compiler.set_msl_options(msl_options);
633
compiler.set_common_options(options);
634
635
spv::ExecutionModel execution_model = map_stage(stage);
636
for (uint32_t jj = 0; jj < spirv_bindings.size(); jj++) {
637
MSLResourceBinding &rb = spirv_bindings.ptr()[jj].first;
638
rb.stage = execution_model;
639
compiler.add_msl_resource_binding(rb);
640
}
641
642
if (push_constant_resource_binding.desc_set == ResourceBindingPushConstantDescriptorSet) {
643
push_constant_resource_binding.stage = execution_model;
644
compiler.add_msl_resource_binding(push_constant_resource_binding);
645
}
646
647
std::unordered_set<VariableID> active = compiler.get_active_interface_variables();
648
ShaderResources resources = compiler.get_shader_resources();
649
650
std::string source;
651
try {
652
source = compiler.compile();
653
} catch (CompilerError &e) {
654
ERR_FAIL_V_MSG(false, "Failed to compile stage " + String(RD::SHADER_STAGE_NAMES[stage]) + ": " + e.what());
655
}
656
657
ERR_FAIL_COND_V_MSG(compiler.get_entry_points_and_stages().size() != 1, false, "Expected a single entry point and stage.");
658
659
SmallVector<EntryPoint> entry_pts_stages = compiler.get_entry_points_and_stages();
660
EntryPoint &entry_point_stage = entry_pts_stages.front();
661
SPIREntryPoint &entry_point = compiler.get_entry_point(entry_point_stage.name, entry_point_stage.execution_model);
662
663
for (auto ext : compiler.get_declared_extensions()) {
664
if (ext == "SPV_KHR_non_semantic_info" || ext == "SPV_KHR_printf") {
665
mtl_reflection_data.set_needs_debug_logging(true);
666
break;
667
}
668
}
669
670
if (!resources.stage_inputs.empty()) {
671
for (Resource const &res : resources.stage_inputs) {
672
uint32_t binding = compiler.get_automatic_msl_resource_binding(res.id);
673
if (binding != (uint32_t)-1) {
674
stage_data.vertex_input_binding_mask |= 1 << binding;
675
}
676
}
677
}
678
679
stage_data.is_position_invariant = compiler.is_position_invariant();
680
stage_data.supports_fast_math = !entry_point.flags.get(spv::ExecutionModeSignedZeroInfNanPreserve);
681
stage_data.hash = SHA256Digest(source.c_str(), source.length());
682
stage_data.source_size = source.length();
683
::Vector<uint8_t> binary_data;
684
binary_data.resize(stage_data.source_size);
685
memcpy(binary_data.ptrw(), source.c_str(), stage_data.source_size);
686
687
if (export_mode) {
688
if (compiler_props.is_valid()) {
689
// Try to compile the Metal source code.
690
::Vector<uint8_t> library_data;
691
Error compile_err = compile_metal_source(source.c_str(), stage_data, library_data);
692
if (compile_err == OK) {
693
// If we successfully compiled to a `.metallib`, there are greater restrictions on target platforms,
694
// so we must update the properties.
695
stage_data.library_size = library_data.size();
696
binary_data.resize(stage_data.source_size + stage_data.library_size);
697
memcpy(binary_data.ptrw() + stage_data.source_size, library_data.ptr(), stage_data.library_size);
698
}
699
} else {
700
WARN_PRINT_ONCE("Metal shader baking limited to SPIR-V: Unable to determine toolchain properties to compile .metallib");
701
}
702
}
703
704
uint32_t binary_data_size = binary_data.size();
705
Shader &shader = shaders.write[i];
706
shader.shader_stage = stage;
707
shader.code_decompressed_size = binary_data_size;
708
shader.code_compressed_bytes.resize(binary_data_size);
709
710
uint32_t compressed_size = 0;
711
bool compressed = compress_code(binary_data.ptr(), binary_data_size, shader.code_compressed_bytes.ptrw(), &compressed_size, &shader.code_compression_flags);
712
ERR_FAIL_COND_V_MSG(!compressed, false, vformat("Failed to compress native code to native for SPIR-V #%d.", i));
713
714
shader.code_compressed_bytes.resize(compressed_size);
715
}
716
717
return true;
718
}
719
720
#pragma clang diagnostic pop
721
722
uint32_t RenderingShaderContainerMetal::_to_bytes_reflection_extra_data(uint8_t *p_bytes) const {
723
if (p_bytes != nullptr) {
724
*(HeaderData *)p_bytes = mtl_reflection_data;
725
}
726
return sizeof(HeaderData);
727
}
728
729
uint32_t RenderingShaderContainerMetal::_to_bytes_reflection_binding_uniform_extra_data(uint8_t *p_bytes, uint32_t p_index) const {
730
if (p_bytes != nullptr) {
731
*(UniformData *)p_bytes = mtl_reflection_binding_set_uniforms_data[p_index];
732
}
733
return sizeof(UniformData);
734
}
735
736
uint32_t RenderingShaderContainerMetal::_to_bytes_shader_extra_data(uint8_t *p_bytes, uint32_t p_index) const {
737
if (p_bytes != nullptr) {
738
*(StageData *)p_bytes = mtl_shaders[p_index];
739
}
740
return sizeof(StageData);
741
}
742
743
uint32_t RenderingShaderContainerMetal::_from_bytes_reflection_extra_data(const uint8_t *p_bytes) {
744
mtl_reflection_data = *(HeaderData *)p_bytes;
745
return sizeof(HeaderData);
746
}
747
748
uint32_t RenderingShaderContainerMetal::_from_bytes_reflection_binding_uniform_extra_data_start(const uint8_t *p_bytes) {
749
mtl_reflection_binding_set_uniforms_data.resize(reflection_binding_set_uniforms_data.size());
750
return 0;
751
}
752
753
uint32_t RenderingShaderContainerMetal::_from_bytes_reflection_binding_uniform_extra_data(const uint8_t *p_bytes, uint32_t p_index) {
754
mtl_reflection_binding_set_uniforms_data.ptrw()[p_index] = *(UniformData *)p_bytes;
755
return sizeof(UniformData);
756
}
757
758
uint32_t RenderingShaderContainerMetal::_from_bytes_shader_extra_data_start(const uint8_t *p_bytes) {
759
mtl_shaders.resize(shaders.size());
760
return 0;
761
}
762
763
uint32_t RenderingShaderContainerMetal::_from_bytes_shader_extra_data(const uint8_t *p_bytes, uint32_t p_index) {
764
mtl_shaders.ptrw()[p_index] = *(StageData *)p_bytes;
765
return sizeof(StageData);
766
}
767
768
RenderingShaderContainerMetal::MetalShaderReflection RenderingShaderContainerMetal::get_metal_shader_reflection() const {
769
MetalShaderReflection res;
770
771
uint32_t uniform_set_count = reflection_binding_set_uniforms_count.size();
772
uint32_t start = 0;
773
res.uniform_sets.resize(uniform_set_count);
774
for (uint32_t i = 0; i < uniform_set_count; i++) {
775
Vector<UniformData> &set = res.uniform_sets.ptrw()[i];
776
uint32_t count = reflection_binding_set_uniforms_count.get(i);
777
set.resize(count);
778
memcpy(set.ptrw(), &mtl_reflection_binding_set_uniforms_data.ptr()[start], count * sizeof(UniformData));
779
start += count;
780
}
781
782
return res;
783
}
784
785
uint32_t RenderingShaderContainerMetal::_format() const {
786
return 0x42424242;
787
}
788
789
uint32_t RenderingShaderContainerMetal::_format_version() const {
790
return FORMAT_VERSION;
791
}
792
793
Ref<RenderingShaderContainer> RenderingShaderContainerFormatMetal::create_container() const {
794
Ref<RenderingShaderContainerMetal> result;
795
result.instantiate();
796
result->set_export_mode(export_mode);
797
result->set_device_profile(device_profile);
798
return result;
799
}
800
801
RenderingDeviceCommons::ShaderLanguageVersion RenderingShaderContainerFormatMetal::get_shader_language_version() const {
802
return SHADER_LANGUAGE_VULKAN_VERSION_1_1;
803
}
804
805
RenderingDeviceCommons::ShaderSpirvVersion RenderingShaderContainerFormatMetal::get_shader_spirv_version() const {
806
return SHADER_SPIRV_VERSION_1_6;
807
}
808
809
RenderingShaderContainerFormatMetal::RenderingShaderContainerFormatMetal(const MetalDeviceProfile *p_device_profile, bool p_export) :
810
export_mode(p_export), device_profile(p_device_profile) {
811
}
812
813
String MinOsVersion::to_compiler_os_version() const {
814
if (version == UINT32_MAX) {
815
return "";
816
}
817
818
uint32_t major = version / 10000;
819
uint32_t minor = (version % 10000) / 100;
820
return vformat("%d.%d", major, minor);
821
}
822
823
MinOsVersion::MinOsVersion(const String &p_version) {
824
int pos = p_version.find_char('.');
825
if (pos > 0) {
826
version = (uint32_t)(p_version.substr(0, pos).to_int() * 10000 +
827
p_version.substr(pos + 1).to_int() * 100);
828
} else {
829
version = (uint32_t)(p_version.to_int() * 10000);
830
}
831
832
if (version == 0) {
833
version = UINT32_MAX;
834
}
835
}
836
837