Path: blob/master/drivers/metal/rendering_device_driver_metal3.cpp
20919 views
/**************************************************************************/1/* rendering_device_driver_metal3.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "rendering_device_driver_metal3.h"3132#include "pixel_formats.h"33#include "rendering_context_driver_metal.h"3435#include "core/config/project_settings.h"36#include "core/string/ustring.h"3738namespace MTL3 {3940#pragma mark - FenceEvent / FenceSemaphore4142void RenderingDeviceDriverMetal::FenceEvent::signal(MTL::CommandBuffer *p_cb) {43if (p_cb) {44value++;45p_cb->encodeSignalEvent(event.get(), value);46}47}4849Error RenderingDeviceDriverMetal::FenceEvent::wait(uint32_t p_timeout_ms) {50bool signaled = event->waitUntilSignaledValue(value, p_timeout_ms);51if (!signaled) {52#ifdef DEBUG_ENABLED53ERR_PRINT("timeout waiting for fence");54#endif55return ERR_TIMEOUT;56}57return OK;58}5960void RenderingDeviceDriverMetal::FenceSemaphore::signal(MTL::CommandBuffer *p_cb) {61if (p_cb) {62p_cb->addCompletedHandler([this](MTL::CommandBuffer *) {63dispatch_semaphore_signal(semaphore);64});65} else {66dispatch_semaphore_signal(semaphore);67}68}6970Error RenderingDeviceDriverMetal::FenceSemaphore::wait(uint32_t p_timeout_ms) {71dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, static_cast<int64_t>(p_timeout_ms) * 1000000);72long result = dispatch_semaphore_wait(semaphore, timeout);73if (result != 0) {74return ERR_TIMEOUT;75}76return OK;77}7879#pragma mark - Constructor / Destructor8081RenderingDeviceDriverMetal::RenderingDeviceDriverMetal(RenderingContextDriverMetal *p_context_driver) :82::RenderingDeviceDriverMetal(p_context_driver) {83}8485RenderingDeviceDriverMetal::~RenderingDeviceDriverMetal() {86for (MDCommandBuffer *cb : command_buffers) {87memdelete(cb);88}89}9091#pragma mark - Initialization9293Error RenderingDeviceDriverMetal::_create_device() {94Error err = ::RenderingDeviceDriverMetal::_create_device();95ERR_FAIL_COND_V(err, err);9697device_queue = NS::TransferPtr(device->newCommandQueue());98ERR_FAIL_NULL_V(device_queue.get(), ERR_CANT_CREATE);99device_queue->setLabel(MTLSTR("Godot Main Command Queue"));100101return OK;102}103104Error RenderingDeviceDriverMetal::initialize(uint32_t p_device_index, uint32_t p_frame_count) {105Error err = _initialize(p_device_index, p_frame_count);106ERR_FAIL_COND_V(err, err);107108// Barriers are still experimental in Metal 3, so they are disabled by default109// and can only be enabled via an environment variable.110bool barriers_enabled = OS::get_singleton()->get_environment("GODOT_MTL_FORCE_BARRIERS") == "1";111if (__builtin_available(macos 26.0, ios 26.0, tvos 26.0, visionos 26.0, *)) {112if (barriers_enabled) {113print_line("Metal 3: Resource barriers enabled.");114NS::SharedPtr<MTL::ResidencySetDescriptor> rs_desc = NS::TransferPtr(MTL::ResidencySetDescriptor::alloc()->init());115rs_desc->setInitialCapacity(250);116rs_desc->setLabel(MTLSTR("Main Residency Set"));117NS::Error *error = nullptr;118NS::SharedPtr<MTL::ResidencySet> mrs = NS::TransferPtr(device->newResidencySet(rs_desc.get(), &error));119if (!mrs) {120String error_msg = error ? String(error->localizedDescription()->utf8String()) : "Unknown error";121print_error(vformat("Resource barriers unavailable. Failed to create main residency set for explicit resource barriers: %s", error_msg));122} else {123use_barriers = true;124base_hazard_tracking = MTL::ResourceHazardTrackingModeUntracked;125main_residency_set = mrs;126device_queue->addResidencySet(mrs.get());127}128}129} else {130if (barriers_enabled) {131// Application or user has requested barriers, but the OS doesn't support them.132print_verbose("Metal 3: Resource barriers are not supported on this OS version.");133barriers_enabled = false;134}135}136137return OK;138}139140#pragma mark - Residency141142void RenderingDeviceDriverMetal::add_residency_set_to_main_queue(MTL::ResidencySet *p_set) {143device_queue->addResidencySet(p_set);144}145146void RenderingDeviceDriverMetal::remove_residency_set_to_main_queue(MTL::ResidencySet *p_set) {147device_queue->removeResidencySet(p_set);148}149150#pragma mark - Fences151152RDD::FenceID RenderingDeviceDriverMetal::fence_create() {153Fence *fence = memnew(FenceEvent(NS::TransferPtr(device->newSharedEvent())));154return FenceID(fence);155}156157Error RenderingDeviceDriverMetal::fence_wait(FenceID p_fence) {158Fence *fence = (Fence *)(p_fence.id);159return fence->wait(1000);160}161162void RenderingDeviceDriverMetal::fence_free(FenceID p_fence) {163Fence *fence = (Fence *)(p_fence.id);164memdelete(fence);165}166167#pragma mark - Semaphores168169RDD::SemaphoreID RenderingDeviceDriverMetal::semaphore_create() {170if (use_barriers) {171Semaphore *sem = memnew(Semaphore(NS::TransferPtr(device->newEvent())));172return SemaphoreID(sem);173}174return SemaphoreID(1);175}176177void RenderingDeviceDriverMetal::semaphore_free(SemaphoreID p_semaphore) {178if (use_barriers) {179Semaphore *sem = (Semaphore *)(p_semaphore.id);180memdelete(sem);181}182}183184#pragma mark - Command Queues185186RDD::CommandQueueID RenderingDeviceDriverMetal::command_queue_create(CommandQueueFamilyID p_cmd_queue_family, bool p_identify_as_main_queue) {187return CommandQueueID(1);188}189190Error 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) {191uint32_t size = p_cmd_buffers.size();192if (size == 0) {193return OK;194}195196bool changed = false;197MTL::ResidencySet *mrs = main_residency_set.get();198if (!_residency_add.is_empty()) {199mrs->addAllocations(reinterpret_cast<const MTL::Allocation *const *>(_residency_add.ptr()), _residency_add.size());200_residency_add.clear();201changed = true;202}203if (!_residency_del.is_empty()) {204mrs->removeAllocations(reinterpret_cast<const MTL::Allocation *const *>(_residency_del.ptr()), _residency_del.size());205_residency_del.clear();206changed = true;207}208if (changed) {209mrs->commit();210}211212if (p_wait_sem.size() > 0) {213MTL::CommandBuffer *cb = device_queue->commandBuffer();214#ifdef DEV_ENABLED215cb->setLabel(MTLSTR("Wait Command Buffer"));216#endif217for (uint32_t i = 0; i < p_wait_sem.size(); i++) {218Semaphore *sem = (Semaphore *)p_wait_sem[i].id;219cb->encodeWait(sem->event.get(), sem->value);220}221cb->commit();222}223224for (uint32_t i = 0; i < size - 1; i++) {225MDCommandBuffer *cmd_buffer = (MDCommandBuffer *)(p_cmd_buffers[i].id);226cmd_buffer->commit();227}228229// The last command buffer will signal the fence and semaphores.230MDCommandBuffer *cmd_buffer = (MDCommandBuffer *)(p_cmd_buffers[size - 1].id);231Fence *fence = (Fence *)(p_cmd_fence.id);232if (fence != nullptr) {233cmd_buffer->end();234MTL::CommandBuffer *cb = cmd_buffer->get_command_buffer();235fence->signal(cb);236}237238struct DrawRequest {239NS::SharedPtr<MTL::Drawable> drawable;240DisplayServer::VSyncMode vsync_mode;241double duration;242};243244if (p_swap_chains.size() > 0) {245Vector<DrawRequest> drawables;246drawables.reserve(p_swap_chains.size());247248for (uint32_t i = 0; i < p_swap_chains.size(); i++) {249SwapChain *swap_chain = (SwapChain *)(p_swap_chains[i].id);250RenderingContextDriverMetal::Surface *metal_surface = (RenderingContextDriverMetal::Surface *)(swap_chain->surface);251MTL::Drawable *drawable = metal_surface->next_drawable();252if (drawable) {253drawables.push_back(DrawRequest{254.drawable = NS::RetainPtr(drawable),255.vsync_mode = metal_surface->vsync_mode,256.duration = metal_surface->present_minimum_duration,257});258}259}260261MTL::CommandBuffer *cb = cmd_buffer->get_command_buffer();262cb->addCompletedHandler([drawables = std::move(drawables)](MTL::CommandBuffer *) {263for (const DrawRequest &dr : drawables) {264switch (dr.vsync_mode) {265case DisplayServer::VSYNC_DISABLED: {266dr.drawable->present();267} break;268default: {269dr.drawable->presentAfterMinimumDuration(dr.duration);270} break;271}272}273});274}275276cmd_buffer->commit();277278if (p_cmd_sem.size() > 0) {279MTL::CommandBuffer *cb = device_queue->commandBuffer();280for (uint32_t i = 0; i < p_cmd_sem.size(); i++) {281Semaphore *sem = (Semaphore *)p_cmd_sem[i].id;282sem->value++;283cb->encodeSignalEvent(sem->event.get(), sem->value);284}285cb->commit();286}287288return OK;289}290291Error 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) {292uint32_t size = p_cmd_buffers.size();293if (size == 0) {294return OK;295}296297for (uint32_t i = 0; i < size - 1; i++) {298MDCommandBuffer *cmd_buffer = (MDCommandBuffer *)(p_cmd_buffers[i].id);299cmd_buffer->commit();300}301302// The last command buffer will signal the fence and semaphores.303MDCommandBuffer *cmd_buffer = (MDCommandBuffer *)(p_cmd_buffers[size - 1].id);304Fence *fence = (Fence *)(p_cmd_fence.id);305if (fence != nullptr) {306cmd_buffer->end();307MTL::CommandBuffer *cb = cmd_buffer->get_command_buffer();308fence->signal(cb);309}310311for (uint32_t i = 0; i < p_swap_chains.size(); i++) {312SwapChain *swap_chain = (SwapChain *)(p_swap_chains[i].id);313RenderingContextDriverMetal::Surface *metal_surface = (RenderingContextDriverMetal::Surface *)(swap_chain->surface);314metal_surface->present(cmd_buffer);315}316317cmd_buffer->commit();318319return OK;320}321322Error 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) {323Error res;324if (use_barriers) {325res = _execute_and_present_barriers(p_cmd_queue, p_wait_sem, p_cmd_buffers, p_cmd_sem, p_cmd_fence, p_swap_chains);326} else {327res = _execute_and_present(p_cmd_queue, p_wait_sem, p_cmd_buffers, p_cmd_sem, p_cmd_fence, p_swap_chains);328}329ERR_FAIL_COND_V(res != OK, res);330331if (p_swap_chains.size() > 0) {332// Used as a signal that we're presenting, so this is the end of a frame.333MTL::CaptureScope *scope = device_scope.get();334scope->endScope();335scope->beginScope();336}337338return OK;339}340341void RenderingDeviceDriverMetal::command_queue_free(CommandQueueID p_cmd_queue) {342}343344#pragma mark - Command Pools345346RDD::CommandPoolID RenderingDeviceDriverMetal::command_pool_create(CommandQueueFamilyID p_cmd_queue_family, CommandBufferType p_cmd_buffer_type) {347DEV_ASSERT(p_cmd_buffer_type == COMMAND_BUFFER_TYPE_PRIMARY);348return CommandPoolID(reinterpret_cast<uint64_t>(device_queue.get()));349}350351bool RenderingDeviceDriverMetal::command_pool_reset(CommandPoolID p_cmd_pool) {352return true;353}354355void RenderingDeviceDriverMetal::command_pool_free(CommandPoolID p_cmd_pool) {356// Nothing to free - the device_queue is managed by SharedPtr.357}358359#pragma mark - Command Buffers360361RDD::CommandBufferID RenderingDeviceDriverMetal::command_buffer_create(CommandPoolID p_cmd_pool) {362MTL::CommandQueue *queue = reinterpret_cast<MTL::CommandQueue *>(p_cmd_pool.id);363MDCommandBuffer *obj = memnew(MDCommandBuffer(queue, this));364command_buffers.push_back(obj);365return CommandBufferID(obj);366}367368} // namespace MTL3369370371