From 753695d745b1bb81ae0fc9a023fe40d5afe3368c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:38:40 +0200 Subject: [PATCH] [macOS, iOS] Dynamically load MetalFX. --- drivers/metal/metal_device_properties.mm | 22 +- drivers/metal/rendering_device_driver_metal.h | 11 + .../metal/rendering_device_driver_metal.mm | 37 ++++ platform/macos/detect.py | 1 - servers/display_server.cpp | 10 +- .../rendering/renderer_rd/effects/metal_fx.mm | 202 +++++++++--------- .../render_forward_clustered.cpp | 6 +- .../renderer_rd/renderer_scene_render_rd.cpp | 4 +- 8 files changed, 171 insertions(+), 122 deletions(-) diff --git a/drivers/metal/metal_device_properties.mm b/drivers/metal/metal_device_properties.mm index d4d6d6bcd328..a9c2d1917ccc 100644 --- a/drivers/metal/metal_device_properties.mm +++ b/drivers/metal/metal_device_properties.mm @@ -50,6 +50,8 @@ #import "metal_device_properties.h" +#import "rendering_device_driver_metal.h" + #import #import #import @@ -121,8 +123,10 @@ } if (@available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) { - features.metal_fx_spatial = [MTLFXSpatialScalerDescriptor supportsDevice:p_device]; - features.metal_fx_temporal = [MTLFXTemporalScalerDescriptor supportsDevice:p_device]; + if (get_MetalFX()) { + features.metal_fx_spatial = [get_MTLFXSpatialScalerDescriptor() supportsDevice:p_device]; + features.metal_fx_temporal = [get_MTLFXTemporalScalerDescriptor() supportsDevice:p_device]; + } } MTLCompileOptions *opts = [MTLCompileOptions new]; @@ -311,13 +315,15 @@ limits.maxDrawIndexedIndexValue = std::numeric_limits::max() - 1; + // Defaults taken from macOS 14+ + limits.temporalScalerInputContentMinScale = 1.0; + limits.temporalScalerInputContentMaxScale = 3.0; + if (@available(macOS 14.0, iOS 17.0, tvOS 17.0, *)) { - limits.temporalScalerInputContentMinScale = (double)[MTLFXTemporalScalerDescriptor supportedInputContentMinScaleForDevice:p_device]; - limits.temporalScalerInputContentMaxScale = (double)[MTLFXTemporalScalerDescriptor supportedInputContentMaxScaleForDevice:p_device]; - } else { - // Defaults taken from macOS 14+ - limits.temporalScalerInputContentMinScale = 1.0; - limits.temporalScalerInputContentMaxScale = 3.0; + if (get_MetalFX()) { + limits.temporalScalerInputContentMinScale = (double)[get_MTLFXTemporalScalerDescriptor() supportedInputContentMinScaleForDevice:p_device]; + limits.temporalScalerInputContentMaxScale = (double)[get_MTLFXTemporalScalerDescriptor() supportedInputContentMaxScaleForDevice:p_device]; + } } } diff --git a/drivers/metal/rendering_device_driver_metal.h b/drivers/metal/rendering_device_driver_metal.h index 5ac1bab4c1b7..04c5137008bf 100644 --- a/drivers/metal/rendering_device_driver_metal.h +++ b/drivers/metal/rendering_device_driver_metal.h @@ -44,6 +44,17 @@ #endif #endif +#ifdef __OBJC__ + +#pragma mark - MetalFX dynamic loading + +void *get_MetalFX(); +Class get_MTLFXSpatialScalerDescriptor(); +Class get_MTLFXTemporalScalerDescriptor(); +void unload_MetalFX(); + +#endif + class RenderingContextDriverMetal; class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) RenderingDeviceDriverMetal : public RenderingDeviceDriver { diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm index 2e32e99c2ebb..70970112db72 100644 --- a/drivers/metal/rendering_device_driver_metal.mm +++ b/drivers/metal/rendering_device_driver_metal.mm @@ -60,12 +60,47 @@ #import #import +#import #import #import #import #import #import +#pragma mark - MetalFX dynamic loading + +static void *metalfx_handle = nullptr; + +void *get_MetalFX() { + if (!metalfx_handle) { + metalfx_handle = dlopen("/System/Library/Frameworks/MetalFX.framework/MetalFX", RTLD_LAZY); + } + return metalfx_handle; +} + +Class get_MTLFXSpatialScalerDescriptor() { + static Class cls = nil; + if (!cls) { + cls = (__bridge Class)dlsym(get_MetalFX(), "OBJC_CLASS_$_MTLFXSpatialScalerDescriptor"); + } + return cls; +} + +Class get_MTLFXTemporalScalerDescriptor() { + static Class cls = nil; + if (!cls) { + cls = (__bridge Class)dlsym(get_MetalFX(), "OBJC_CLASS_$_MTLFXTemporalScalerDescriptor"); + } + return cls; +} + +void unload_MetalFX() { + if (metalfx_handle) { + dlclose(metalfx_handle); + metalfx_handle = nullptr; + } +} + #pragma mark - Logging os_log_t LOG_DRIVER; @@ -4094,6 +4129,8 @@ bool isArrayTexture(MTLTextureType p_type) { for (KeyValue &kv : _shader_cache) { memdelete(kv.value); } + + unload_MetalFX(); } #pragma mark - Initialization diff --git a/platform/macos/detect.py b/platform/macos/detect.py index 227738877d05..510671db6a57 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -245,7 +245,6 @@ def configure(env: "SConsEnvironment"): env.AppendUnique(CPPDEFINES=["METAL_ENABLED", "RD_ENABLED"]) extra_frameworks.add("Metal") extra_frameworks.add("MetalKit") - extra_frameworks.add("MetalFX") env.Prepend(CPPPATH=["#thirdparty/spirv-cross"]) if env["vulkan"]: diff --git a/servers/display_server.cpp b/servers/display_server.cpp index dc3402bdf88b..5b60e4874de6 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -1307,12 +1307,10 @@ bool DisplayServer::can_create_rendering_device() { } #endif #ifdef METAL_ENABLED - if (rcd == nullptr) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - // Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer". - rcd = memnew(RenderingContextDriverMetal); -#pragma clang diagnostic pop + if (__builtin_available(macOS 11.0, iOS 14.0, *)) { + if (rcd == nullptr) { + rcd = memnew(RenderingContextDriverMetal); + } } #endif diff --git a/servers/rendering/renderer_rd/effects/metal_fx.mm b/servers/rendering/renderer_rd/effects/metal_fx.mm index e2273d446395..94ea308a0ca1 100644 --- a/servers/rendering/renderer_rd/effects/metal_fx.mm +++ b/servers/rendering/renderer_rd/effects/metal_fx.mm @@ -51,29 +51,26 @@ } void MFXSpatialEffect::callback(RDD *p_driver, RDD::CommandBufferID p_command_buffer, CallbackArgs *p_userdata) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - - MDCommandBuffer *obj = (MDCommandBuffer *)(p_command_buffer.id); - obj->end(); - - id src_texture = rid::get(p_userdata->src); - id dst_texture = rid::get(p_userdata->dst); - - __block id scaler = p_userdata->ctx.scaler; - scaler.colorTexture = src_texture; - scaler.outputTexture = dst_texture; - [scaler encodeToCommandBuffer:obj->get_command_buffer()]; - // TODO(sgc): add API to retain objects until the command buffer completes - [obj->get_command_buffer() addCompletedHandler:^(id _Nonnull) { - // This block retains a reference to the scaler until the command buffer. - // completes. - scaler = nil; - }]; - - CallbackArgs::free(&p_userdata); - -#pragma clang diagnostic pop + if (@available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) { + MDCommandBuffer *obj = (MDCommandBuffer *)(p_command_buffer.id); + obj->end(); + + id src_texture = rid::get(p_userdata->src); + id dst_texture = rid::get(p_userdata->dst); + + __block id scaler = p_userdata->ctx.scaler; + scaler.colorTexture = src_texture; + scaler.outputTexture = dst_texture; + [scaler encodeToCommandBuffer:obj->get_command_buffer()]; + // TODO(sgc): add API to retain objects until the command buffer completes + [obj->get_command_buffer() addCompletedHandler:^(id _Nonnull) { + // This block retains a reference to the scaler until the command buffer. + // completes. + scaler = nil; + }]; + + CallbackArgs::free(&p_userdata); + } } void MFXSpatialEffect::ensure_context(Ref p_render_buffers) { @@ -99,30 +96,29 @@ MFXSpatialContext *MFXSpatialEffect::create_context(CreateParams p_params) const { DEV_ASSERT(RD::get_singleton()->has_feature(RD::SUPPORTS_METALFX_SPATIAL)); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - - RenderingDeviceDriverMetal *rdd = (RenderingDeviceDriverMetal *)RD::get_singleton()->get_device_driver(); - PixelFormats &pf = rdd->get_pixel_formats(); - id dev = rdd->get_device(); - - MTLFXSpatialScalerDescriptor *desc = [MTLFXSpatialScalerDescriptor new]; - desc.inputWidth = (NSUInteger)p_params.input_size.width; - desc.inputHeight = (NSUInteger)p_params.input_size.height; - - desc.outputWidth = (NSUInteger)p_params.output_size.width; - desc.outputHeight = (NSUInteger)p_params.output_size.height; - - desc.colorTextureFormat = pf.getMTLPixelFormat(p_params.input_format); - desc.outputTextureFormat = pf.getMTLPixelFormat(p_params.output_format); - desc.colorProcessingMode = MTLFXSpatialScalerColorProcessingModeLinear; - id scaler = [desc newSpatialScalerWithDevice:dev]; - MFXSpatialContext *context = memnew(MFXSpatialContext); - context->scaler = scaler; - -#pragma clang diagnostic pop - - return context; + if (@available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) { + RenderingDeviceDriverMetal *rdd = (RenderingDeviceDriverMetal *)RD::get_singleton()->get_device_driver(); + PixelFormats &pf = rdd->get_pixel_formats(); + id dev = rdd->get_device(); + + MTLFXSpatialScalerDescriptor *desc = [get_MTLFXSpatialScalerDescriptor() new]; + desc.inputWidth = (NSUInteger)p_params.input_size.width; + desc.inputHeight = (NSUInteger)p_params.input_size.height; + + desc.outputWidth = (NSUInteger)p_params.output_size.width; + desc.outputHeight = (NSUInteger)p_params.output_size.height; + + desc.colorTextureFormat = pf.getMTLPixelFormat(p_params.input_format); + desc.outputTextureFormat = pf.getMTLPixelFormat(p_params.output_format); + desc.colorProcessingMode = MTLFXSpatialScalerColorProcessingModeLinear; + id scaler = [desc newSpatialScalerWithDevice:dev]; + MFXSpatialContext *context = memnew(MFXSpatialContext); + context->scaler = scaler; + + return context; + } else { + return nullptr; + } } #pragma mark - Temporal Scaler @@ -135,38 +131,37 @@ MFXTemporalContext *MFXTemporalEffect::create_context(CreateParams p_params) const { DEV_ASSERT(RD::get_singleton()->has_feature(RD::SUPPORTS_METALFX_TEMPORAL)); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - - RenderingDeviceDriverMetal *rdd = (RenderingDeviceDriverMetal *)RD::get_singleton()->get_device_driver(); - PixelFormats &pf = rdd->get_pixel_formats(); - id dev = rdd->get_device(); - - MTLFXTemporalScalerDescriptor *desc = [MTLFXTemporalScalerDescriptor new]; - desc.inputWidth = (NSUInteger)p_params.input_size.width; - desc.inputHeight = (NSUInteger)p_params.input_size.height; + if (@available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) { + RenderingDeviceDriverMetal *rdd = (RenderingDeviceDriverMetal *)RD::get_singleton()->get_device_driver(); + PixelFormats &pf = rdd->get_pixel_formats(); + id dev = rdd->get_device(); - desc.outputWidth = (NSUInteger)p_params.output_size.width; - desc.outputHeight = (NSUInteger)p_params.output_size.height; + MTLFXTemporalScalerDescriptor *desc = [get_MTLFXTemporalScalerDescriptor() new]; + desc.inputWidth = (NSUInteger)p_params.input_size.width; + desc.inputHeight = (NSUInteger)p_params.input_size.height; - desc.colorTextureFormat = pf.getMTLPixelFormat(p_params.input_format); - desc.depthTextureFormat = pf.getMTLPixelFormat(p_params.depth_format); - desc.motionTextureFormat = pf.getMTLPixelFormat(p_params.motion_format); - desc.autoExposureEnabled = NO; + desc.outputWidth = (NSUInteger)p_params.output_size.width; + desc.outputHeight = (NSUInteger)p_params.output_size.height; - desc.outputTextureFormat = pf.getMTLPixelFormat(p_params.output_format); + desc.colorTextureFormat = pf.getMTLPixelFormat(p_params.input_format); + desc.depthTextureFormat = pf.getMTLPixelFormat(p_params.depth_format); + desc.motionTextureFormat = pf.getMTLPixelFormat(p_params.motion_format); + desc.autoExposureEnabled = NO; - id scaler = [desc newTemporalScalerWithDevice:dev]; - MFXTemporalContext *context = memnew(MFXTemporalContext); - context->scaler = scaler; + desc.outputTextureFormat = pf.getMTLPixelFormat(p_params.output_format); - scaler.motionVectorScaleX = p_params.motion_vector_scale.x; - scaler.motionVectorScaleY = p_params.motion_vector_scale.y; - scaler.depthReversed = true; // Godot uses reverse Z per https://github.com/godotengine/godot/pull/88328 + id scaler = [desc newTemporalScalerWithDevice:dev]; + MFXTemporalContext *context = memnew(MFXTemporalContext); + context->scaler = scaler; -#pragma clang diagnostic pop + scaler.motionVectorScaleX = p_params.motion_vector_scale.x; + scaler.motionVectorScaleY = p_params.motion_vector_scale.y; + scaler.depthReversed = true; // Godot uses reverse Z per https://github.com/godotengine/godot/pull/88328 - return context; + return context; + } else { + return nullptr; + } } void MFXTemporalEffect::process(RendererRD::MFXTemporalContext *p_ctx, RendererRD::MFXTemporalEffect::Params p_params) { @@ -189,37 +184,34 @@ } void MFXTemporalEffect::callback(RDD *p_driver, RDD::CommandBufferID p_command_buffer, CallbackArgs *p_userdata) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - - MDCommandBuffer *obj = (MDCommandBuffer *)(p_command_buffer.id); - obj->end(); - - id src_texture = rid::get(p_userdata->src); - id depth = rid::get(p_userdata->depth); - id motion = rid::get(p_userdata->motion); - id exposure = rid::get(p_userdata->exposure); - - id dst_texture = rid::get(p_userdata->dst); - - __block id scaler = p_userdata->ctx.scaler; - scaler.reset = p_userdata->reset; - scaler.colorTexture = src_texture; - scaler.depthTexture = depth; - scaler.motionTexture = motion; - scaler.exposureTexture = exposure; - scaler.jitterOffsetX = p_userdata->jitter_offset.x; - scaler.jitterOffsetY = p_userdata->jitter_offset.y; - scaler.outputTexture = dst_texture; - [scaler encodeToCommandBuffer:obj->get_command_buffer()]; - // TODO(sgc): add API to retain objects until the command buffer completes - [obj->get_command_buffer() addCompletedHandler:^(id _Nonnull) { - // This block retains a reference to the scaler until the command buffer. - // completes. - scaler = nil; - }]; - - CallbackArgs::free(&p_userdata); - -#pragma clang diagnostic pop + if (@available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) { + MDCommandBuffer *obj = (MDCommandBuffer *)(p_command_buffer.id); + obj->end(); + + id src_texture = rid::get(p_userdata->src); + id depth = rid::get(p_userdata->depth); + id motion = rid::get(p_userdata->motion); + id exposure = rid::get(p_userdata->exposure); + + id dst_texture = rid::get(p_userdata->dst); + + __block id scaler = p_userdata->ctx.scaler; + scaler.reset = p_userdata->reset; + scaler.colorTexture = src_texture; + scaler.depthTexture = depth; + scaler.motionTexture = motion; + scaler.exposureTexture = exposure; + scaler.jitterOffsetX = p_userdata->jitter_offset.x; + scaler.jitterOffsetY = p_userdata->jitter_offset.y; + scaler.outputTexture = dst_texture; + [scaler encodeToCommandBuffer:obj->get_command_buffer()]; + // TODO(sgc): add API to retain objects until the command buffer completes + [obj->get_command_buffer() addCompletedHandler:^(id _Nonnull) { + // This block retains a reference to the scaler until the command buffer. + // completes. + scaler = nil; + }]; + + CallbackArgs::free(&p_userdata); + } } diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 70b3a89c4a68..2c1199c212f5 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1750,7 +1750,11 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co break; case RS::VIEWPORT_SCALING_3D_MODE_METALFX_TEMPORAL: #ifdef METAL_ENABLED - scale_type = SCALE_MFX; + if (RD::get_singleton()->has_feature(RD::SUPPORTS_METALFX_TEMPORAL)) { + scale_type = SCALE_MFX; + } else { + scale_type = SCALE_NONE; + } #else scale_type = SCALE_NONE; #endif diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 4d7e87534a73..43b55f74d32c 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -437,7 +437,9 @@ void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(const Rende spatial_upscaler = fsr; } else if (scale_mode == RS::VIEWPORT_SCALING_3D_MODE_METALFX_SPATIAL) { #if METAL_ENABLED - spatial_upscaler = mfx_spatial; + if (RD::get_singleton()->has_feature(RD::SUPPORTS_METALFX_SPATIAL)) { + spatial_upscaler = mfx_spatial; + } #endif } }