Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/metal/rendering_device_driver_metal3.cpp
20919 views
1
/**************************************************************************/
2
/* rendering_device_driver_metal3.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_device_driver_metal3.h"
32
33
#include "pixel_formats.h"
34
#include "rendering_context_driver_metal.h"
35
36
#include "core/config/project_settings.h"
37
#include "core/string/ustring.h"
38
39
namespace MTL3 {
40
41
#pragma mark - FenceEvent / FenceSemaphore
42
43
void RenderingDeviceDriverMetal::FenceEvent::signal(MTL::CommandBuffer *p_cb) {
44
if (p_cb) {
45
value++;
46
p_cb->encodeSignalEvent(event.get(), value);
47
}
48
}
49
50
Error RenderingDeviceDriverMetal::FenceEvent::wait(uint32_t p_timeout_ms) {
51
bool signaled = event->waitUntilSignaledValue(value, p_timeout_ms);
52
if (!signaled) {
53
#ifdef DEBUG_ENABLED
54
ERR_PRINT("timeout waiting for fence");
55
#endif
56
return ERR_TIMEOUT;
57
}
58
return OK;
59
}
60
61
void RenderingDeviceDriverMetal::FenceSemaphore::signal(MTL::CommandBuffer *p_cb) {
62
if (p_cb) {
63
p_cb->addCompletedHandler([this](MTL::CommandBuffer *) {
64
dispatch_semaphore_signal(semaphore);
65
});
66
} else {
67
dispatch_semaphore_signal(semaphore);
68
}
69
}
70
71
Error RenderingDeviceDriverMetal::FenceSemaphore::wait(uint32_t p_timeout_ms) {
72
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, static_cast<int64_t>(p_timeout_ms) * 1000000);
73
long result = dispatch_semaphore_wait(semaphore, timeout);
74
if (result != 0) {
75
return ERR_TIMEOUT;
76
}
77
return OK;
78
}
79
80
#pragma mark - Constructor / Destructor
81
82
RenderingDeviceDriverMetal::RenderingDeviceDriverMetal(RenderingContextDriverMetal *p_context_driver) :
83
::RenderingDeviceDriverMetal(p_context_driver) {
84
}
85
86
RenderingDeviceDriverMetal::~RenderingDeviceDriverMetal() {
87
for (MDCommandBuffer *cb : command_buffers) {
88
memdelete(cb);
89
}
90
}
91
92
#pragma mark - Initialization
93
94
Error RenderingDeviceDriverMetal::_create_device() {
95
Error err = ::RenderingDeviceDriverMetal::_create_device();
96
ERR_FAIL_COND_V(err, err);
97
98
device_queue = NS::TransferPtr(device->newCommandQueue());
99
ERR_FAIL_NULL_V(device_queue.get(), ERR_CANT_CREATE);
100
device_queue->setLabel(MTLSTR("Godot Main Command Queue"));
101
102
return OK;
103
}
104
105
Error RenderingDeviceDriverMetal::initialize(uint32_t p_device_index, uint32_t p_frame_count) {
106
Error err = _initialize(p_device_index, p_frame_count);
107
ERR_FAIL_COND_V(err, err);
108
109
// Barriers are still experimental in Metal 3, so they are disabled by default
110
// and can only be enabled via an environment variable.
111
bool barriers_enabled = OS::get_singleton()->get_environment("GODOT_MTL_FORCE_BARRIERS") == "1";
112
if (__builtin_available(macos 26.0, ios 26.0, tvos 26.0, visionos 26.0, *)) {
113
if (barriers_enabled) {
114
print_line("Metal 3: Resource barriers enabled.");
115
NS::SharedPtr<MTL::ResidencySetDescriptor> rs_desc = NS::TransferPtr(MTL::ResidencySetDescriptor::alloc()->init());
116
rs_desc->setInitialCapacity(250);
117
rs_desc->setLabel(MTLSTR("Main Residency Set"));
118
NS::Error *error = nullptr;
119
NS::SharedPtr<MTL::ResidencySet> mrs = NS::TransferPtr(device->newResidencySet(rs_desc.get(), &error));
120
if (!mrs) {
121
String error_msg = error ? String(error->localizedDescription()->utf8String()) : "Unknown error";
122
print_error(vformat("Resource barriers unavailable. Failed to create main residency set for explicit resource barriers: %s", error_msg));
123
} else {
124
use_barriers = true;
125
base_hazard_tracking = MTL::ResourceHazardTrackingModeUntracked;
126
main_residency_set = mrs;
127
device_queue->addResidencySet(mrs.get());
128
}
129
}
130
} else {
131
if (barriers_enabled) {
132
// Application or user has requested barriers, but the OS doesn't support them.
133
print_verbose("Metal 3: Resource barriers are not supported on this OS version.");
134
barriers_enabled = false;
135
}
136
}
137
138
return OK;
139
}
140
141
#pragma mark - Residency
142
143
void RenderingDeviceDriverMetal::add_residency_set_to_main_queue(MTL::ResidencySet *p_set) {
144
device_queue->addResidencySet(p_set);
145
}
146
147
void RenderingDeviceDriverMetal::remove_residency_set_to_main_queue(MTL::ResidencySet *p_set) {
148
device_queue->removeResidencySet(p_set);
149
}
150
151
#pragma mark - Fences
152
153
RDD::FenceID RenderingDeviceDriverMetal::fence_create() {
154
Fence *fence = memnew(FenceEvent(NS::TransferPtr(device->newSharedEvent())));
155
return FenceID(fence);
156
}
157
158
Error RenderingDeviceDriverMetal::fence_wait(FenceID p_fence) {
159
Fence *fence = (Fence *)(p_fence.id);
160
return fence->wait(1000);
161
}
162
163
void RenderingDeviceDriverMetal::fence_free(FenceID p_fence) {
164
Fence *fence = (Fence *)(p_fence.id);
165
memdelete(fence);
166
}
167
168
#pragma mark - Semaphores
169
170
RDD::SemaphoreID RenderingDeviceDriverMetal::semaphore_create() {
171
if (use_barriers) {
172
Semaphore *sem = memnew(Semaphore(NS::TransferPtr(device->newEvent())));
173
return SemaphoreID(sem);
174
}
175
return SemaphoreID(1);
176
}
177
178
void RenderingDeviceDriverMetal::semaphore_free(SemaphoreID p_semaphore) {
179
if (use_barriers) {
180
Semaphore *sem = (Semaphore *)(p_semaphore.id);
181
memdelete(sem);
182
}
183
}
184
185
#pragma mark - Command Queues
186
187
RDD::CommandQueueID RenderingDeviceDriverMetal::command_queue_create(CommandQueueFamilyID p_cmd_queue_family, bool p_identify_as_main_queue) {
188
return CommandQueueID(1);
189
}
190
191
Error RenderingDeviceDriverMetal::_execute_and_present_barriers(CommandQueueID p_cmd_queue, VectorView<SemaphoreID> p_wait_sem, VectorView<CommandBufferID> p_cmd_buffers, VectorView<SemaphoreID> p_cmd_sem, FenceID p_cmd_fence, VectorView<SwapChainID> p_swap_chains) {
192
uint32_t size = p_cmd_buffers.size();
193
if (size == 0) {
194
return OK;
195
}
196
197
bool changed = false;
198
MTL::ResidencySet *mrs = main_residency_set.get();
199
if (!_residency_add.is_empty()) {
200
mrs->addAllocations(reinterpret_cast<const MTL::Allocation *const *>(_residency_add.ptr()), _residency_add.size());
201
_residency_add.clear();
202
changed = true;
203
}
204
if (!_residency_del.is_empty()) {
205
mrs->removeAllocations(reinterpret_cast<const MTL::Allocation *const *>(_residency_del.ptr()), _residency_del.size());
206
_residency_del.clear();
207
changed = true;
208
}
209
if (changed) {
210
mrs->commit();
211
}
212
213
if (p_wait_sem.size() > 0) {
214
MTL::CommandBuffer *cb = device_queue->commandBuffer();
215
#ifdef DEV_ENABLED
216
cb->setLabel(MTLSTR("Wait Command Buffer"));
217
#endif
218
for (uint32_t i = 0; i < p_wait_sem.size(); i++) {
219
Semaphore *sem = (Semaphore *)p_wait_sem[i].id;
220
cb->encodeWait(sem->event.get(), sem->value);
221
}
222
cb->commit();
223
}
224
225
for (uint32_t i = 0; i < size - 1; i++) {
226
MDCommandBuffer *cmd_buffer = (MDCommandBuffer *)(p_cmd_buffers[i].id);
227
cmd_buffer->commit();
228
}
229
230
// The last command buffer will signal the fence and semaphores.
231
MDCommandBuffer *cmd_buffer = (MDCommandBuffer *)(p_cmd_buffers[size - 1].id);
232
Fence *fence = (Fence *)(p_cmd_fence.id);
233
if (fence != nullptr) {
234
cmd_buffer->end();
235
MTL::CommandBuffer *cb = cmd_buffer->get_command_buffer();
236
fence->signal(cb);
237
}
238
239
struct DrawRequest {
240
NS::SharedPtr<MTL::Drawable> drawable;
241
DisplayServer::VSyncMode vsync_mode;
242
double duration;
243
};
244
245
if (p_swap_chains.size() > 0) {
246
Vector<DrawRequest> drawables;
247
drawables.reserve(p_swap_chains.size());
248
249
for (uint32_t i = 0; i < p_swap_chains.size(); i++) {
250
SwapChain *swap_chain = (SwapChain *)(p_swap_chains[i].id);
251
RenderingContextDriverMetal::Surface *metal_surface = (RenderingContextDriverMetal::Surface *)(swap_chain->surface);
252
MTL::Drawable *drawable = metal_surface->next_drawable();
253
if (drawable) {
254
drawables.push_back(DrawRequest{
255
.drawable = NS::RetainPtr(drawable),
256
.vsync_mode = metal_surface->vsync_mode,
257
.duration = metal_surface->present_minimum_duration,
258
});
259
}
260
}
261
262
MTL::CommandBuffer *cb = cmd_buffer->get_command_buffer();
263
cb->addCompletedHandler([drawables = std::move(drawables)](MTL::CommandBuffer *) {
264
for (const DrawRequest &dr : drawables) {
265
switch (dr.vsync_mode) {
266
case DisplayServer::VSYNC_DISABLED: {
267
dr.drawable->present();
268
} break;
269
default: {
270
dr.drawable->presentAfterMinimumDuration(dr.duration);
271
} break;
272
}
273
}
274
});
275
}
276
277
cmd_buffer->commit();
278
279
if (p_cmd_sem.size() > 0) {
280
MTL::CommandBuffer *cb = device_queue->commandBuffer();
281
for (uint32_t i = 0; i < p_cmd_sem.size(); i++) {
282
Semaphore *sem = (Semaphore *)p_cmd_sem[i].id;
283
sem->value++;
284
cb->encodeSignalEvent(sem->event.get(), sem->value);
285
}
286
cb->commit();
287
}
288
289
return OK;
290
}
291
292
Error RenderingDeviceDriverMetal::_execute_and_present(CommandQueueID p_cmd_queue, VectorView<SemaphoreID> p_wait_sem, VectorView<CommandBufferID> p_cmd_buffers, VectorView<SemaphoreID> p_cmd_sem, FenceID p_cmd_fence, VectorView<SwapChainID> p_swap_chains) {
293
uint32_t size = p_cmd_buffers.size();
294
if (size == 0) {
295
return OK;
296
}
297
298
for (uint32_t i = 0; i < size - 1; i++) {
299
MDCommandBuffer *cmd_buffer = (MDCommandBuffer *)(p_cmd_buffers[i].id);
300
cmd_buffer->commit();
301
}
302
303
// The last command buffer will signal the fence and semaphores.
304
MDCommandBuffer *cmd_buffer = (MDCommandBuffer *)(p_cmd_buffers[size - 1].id);
305
Fence *fence = (Fence *)(p_cmd_fence.id);
306
if (fence != nullptr) {
307
cmd_buffer->end();
308
MTL::CommandBuffer *cb = cmd_buffer->get_command_buffer();
309
fence->signal(cb);
310
}
311
312
for (uint32_t i = 0; i < p_swap_chains.size(); i++) {
313
SwapChain *swap_chain = (SwapChain *)(p_swap_chains[i].id);
314
RenderingContextDriverMetal::Surface *metal_surface = (RenderingContextDriverMetal::Surface *)(swap_chain->surface);
315
metal_surface->present(cmd_buffer);
316
}
317
318
cmd_buffer->commit();
319
320
return OK;
321
}
322
323
Error RenderingDeviceDriverMetal::command_queue_execute_and_present(CommandQueueID p_cmd_queue, VectorView<SemaphoreID> p_wait_sem, VectorView<CommandBufferID> p_cmd_buffers, VectorView<SemaphoreID> p_cmd_sem, FenceID p_cmd_fence, VectorView<SwapChainID> p_swap_chains) {
324
Error res;
325
if (use_barriers) {
326
res = _execute_and_present_barriers(p_cmd_queue, p_wait_sem, p_cmd_buffers, p_cmd_sem, p_cmd_fence, p_swap_chains);
327
} else {
328
res = _execute_and_present(p_cmd_queue, p_wait_sem, p_cmd_buffers, p_cmd_sem, p_cmd_fence, p_swap_chains);
329
}
330
ERR_FAIL_COND_V(res != OK, res);
331
332
if (p_swap_chains.size() > 0) {
333
// Used as a signal that we're presenting, so this is the end of a frame.
334
MTL::CaptureScope *scope = device_scope.get();
335
scope->endScope();
336
scope->beginScope();
337
}
338
339
return OK;
340
}
341
342
void RenderingDeviceDriverMetal::command_queue_free(CommandQueueID p_cmd_queue) {
343
}
344
345
#pragma mark - Command Pools
346
347
RDD::CommandPoolID RenderingDeviceDriverMetal::command_pool_create(CommandQueueFamilyID p_cmd_queue_family, CommandBufferType p_cmd_buffer_type) {
348
DEV_ASSERT(p_cmd_buffer_type == COMMAND_BUFFER_TYPE_PRIMARY);
349
return CommandPoolID(reinterpret_cast<uint64_t>(device_queue.get()));
350
}
351
352
bool RenderingDeviceDriverMetal::command_pool_reset(CommandPoolID p_cmd_pool) {
353
return true;
354
}
355
356
void RenderingDeviceDriverMetal::command_pool_free(CommandPoolID p_cmd_pool) {
357
// Nothing to free - the device_queue is managed by SharedPtr.
358
}
359
360
#pragma mark - Command Buffers
361
362
RDD::CommandBufferID RenderingDeviceDriverMetal::command_buffer_create(CommandPoolID p_cmd_pool) {
363
MTL::CommandQueue *queue = reinterpret_cast<MTL::CommandQueue *>(p_cmd_pool.id);
364
MDCommandBuffer *obj = memnew(MDCommandBuffer(queue, this));
365
command_buffers.push_back(obj);
366
return CommandBufferID(obj);
367
}
368
369
} // namespace MTL3
370
371