Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[macOS, iOS] Dynamically load MetalFX. #101590

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions drivers/metal/metal_device_properties.mm
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@

#import "metal_device_properties.h"

#import "rendering_device_driver_metal.h"

#import <Metal/Metal.h>
#import <MetalFX/MetalFX.h>
#import <spirv_cross.hpp>
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -311,13 +315,15 @@

limits.maxDrawIndexedIndexValue = std::numeric_limits<uint32_t>::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];
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions drivers/metal/rendering_device_driver_metal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
37 changes: 37 additions & 0 deletions drivers/metal/rendering_device_driver_metal.mm
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,47 @@

#import <Metal/MTLTexture.h>
#import <Metal/Metal.h>
#import <dlfcn.h>
#import <os/log.h>
#import <os/signpost.h>
#import <spirv.hpp>
#import <spirv_msl.hpp>
#import <spirv_parser.hpp>

#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;
Expand Down Expand Up @@ -4094,6 +4129,8 @@ bool isArrayTexture(MTLTextureType p_type) {
for (KeyValue<SHA256Digest, ShaderCacheEntry *> &kv : _shader_cache) {
memdelete(kv.value);
}

unload_MetalFX();
}

#pragma mark - Initialization
Expand Down
1 change: 0 additions & 1 deletion platform/macos/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]:
Expand Down
10 changes: 4 additions & 6 deletions servers/display_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
202 changes: 97 additions & 105 deletions servers/rendering/renderer_rd/effects/metal_fx.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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<MTLTexture> src_texture = rid::get(p_userdata->src);
id<MTLTexture> dst_texture = rid::get(p_userdata->dst);

__block id<MTLFXSpatialScaler> 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<MTLCommandBuffer> _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);
Comment on lines +54 to +55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to change this and perform availability checks every frame, as higher-level code already guarantees that we won't use the upscaler on older OSs

obj->end();

id<MTLTexture> src_texture = rid::get(p_userdata->src);
id<MTLTexture> dst_texture = rid::get(p_userdata->dst);

__block id<MTLFXSpatialScaler> 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<MTLCommandBuffer> _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<RenderSceneBuffersRD> p_render_buffers) {
Expand All @@ -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<MTLDevice> 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<MTLFXSpatialScaler> 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();
Comment on lines +99 to +100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, the higher-level code already guarantees that we won't use the upscaler on older OSs

PixelFormats &pf = rdd->get_pixel_formats();
id<MTLDevice> 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<MTLFXSpatialScaler> scaler = [desc newSpatialScalerWithDevice:dev];
MFXSpatialContext *context = memnew(MFXSpatialContext);
context->scaler = scaler;

return context;
} else {
return nullptr;
}
}

#pragma mark - Temporal Scaler
Expand All @@ -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<MTLDevice> 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();
Comment on lines +134 to +135
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here, this change seems unnecessary, as we guarantee it won't be executed on older OSs

PixelFormats &pf = rdd->get_pixel_formats();
id<MTLDevice> 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<MTLFXTemporalScaler> 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<MTLFXTemporalScaler> 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) {
Expand All @@ -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<MTLTexture> src_texture = rid::get(p_userdata->src);
id<MTLTexture> depth = rid::get(p_userdata->depth);
id<MTLTexture> motion = rid::get(p_userdata->motion);
id<MTLTexture> exposure = rid::get(p_userdata->exposure);

id<MTLTexture> dst_texture = rid::get(p_userdata->dst);

__block id<MTLFXTemporalScaler> 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<MTLCommandBuffer> _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<MTLTexture> src_texture = rid::get(p_userdata->src);
id<MTLTexture> depth = rid::get(p_userdata->depth);
id<MTLTexture> motion = rid::get(p_userdata->motion);
id<MTLTexture> exposure = rid::get(p_userdata->exposure);

id<MTLTexture> dst_texture = rid::get(p_userdata->dst);

__block id<MTLFXTemporalScaler> 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<MTLCommandBuffer> _Nonnull) {
// This block retains a reference to the scaler until the command buffer.
// completes.
scaler = nil;
}];

CallbackArgs::free(&p_userdata);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading