Optimization · Tips & Tricks · Uncategorized

Boost Mac performance with Metal and Delphi 10.4

Is your Mac app a little unresponsive or does it feel sluggish? Does your MacBook laptop turn into a lap-heater when running your app? Then the new Metal support in Delphi 10.4 may be just the thing for you and your customers!

I have been given permission by Embarcadero to blog about this new feature in the upcoming Delphi 10.4 release.

Rendering Technologies in Delphi

But before we get into that, let first look at the technologies that Delphi uses to render the GUI. These are split into 3 areas: canvas drawing, text layout and 3D rendering. Each of these 3 areas can use a different technology based on operating system and some global configuration flags. For the default configuration, the technologies used on Windows and macOS are:

TechnologyWindowsmacOS
CanvasDirect2DCoreGraphics (Quartz)
Text layoutDirectWriteCoreText
3D RenderingDirect3DOpenGL

I am limiting this article to Windows and macOS. Maybe I will talk about mobile performance in a future article.

This is the default configuration that is used if available. Note that Delphi may fallback to other technologies if the default is not available. For example, it may fallback to a GDI+ canvas if Direct2D is not available (although that will almost never be the case anymore these days).

You can check which technologies are currently used in your app by checking the type of the TCanvas, TTextLayout and TContext class.

GPU Based Rendering

Now the issue is that the CoreGraphics (or Quartz) rendering technology of macOS is quite slow, especially when used to render the GUI manually (as FireMonkey does).

Until now, you could try to switch to GPU based rendering by setting the GlobalUseGPUCanvas variable to True at application startup. This changes the table to:

TechnologyWindowsmacOS
CanvasDirect3DOpenGL
Text layoutCustomCustom
3D RenderingDirect3DOpenGL

Now, 2D user interfaces are rendered using the same 3D context that is used for 3D applications. For calculating and rendering text layouts, Delphi uses its own implementation in this case (called TGPUTextLayout) that builds an atlas of character glyphs and renders these with the same 3D context.

However, using GlobalUseGPUCanvas on Windows results glitches and issues when using high-DPI displays. And on macOS, this flag doesn’t work at all and results in a EContext3DException when used. It works quite well for iOS and Android though…

So you were pretty much stuck with CoreGraphics on macOS. Until now that is…

Metal to the Rescue

Apple has deprecated OpenGL for a while now, and may drop support for it altogether in one of the next macOS/iOS versions. Instead, you have to use their own graphics API called Metal.

The trends in graphics APIs is to become more low-level and closer to the metal (no pun intended). We have seen this with DirectX 12, Vulkan and Metal. These APIs are considerable more difficult to work with than their predecessors, but provide greater performance in return. It is up to library/framework/engine developers now to provide more user-friendly access to these APIs.

Fortunately, Delphi 10.4 has full support for Metal now. But is still defaults to using CoreGraphics, CoreText and OpenGL for backwards compatibility reasons.

You may think that Metal is especially important for 3D applications, and doesn’t make much of a difference for 2D apps. However, the opposite is true (as you will see later): 2D applications in particular can take advantage of Metal because it eliminates the need for the slow CoreGraphics technology.

If you want to take advantage of Metal (and you should), then all you have to do is to set the GlobalUseMetal variable to True before doing anything else. The best place to do that is inside the .dpr file, before you create your forms. For example:

program MetalApp;

uses
  System.StartUpCopy,
  FMX.Forms,
  FMX.Types,
  FMain in 'FMain.pas' {FormMain};

{$R *.res}

begin
  GlobalUseMetal := True;
  Application.Initialize;
  Application.CreateForm(TFormMain, FormMain);
  Application.Run;
end.

Make sure you add the FMX.Types unit to the uses clause.

Performance Gains

To measure the difference in performance between CoreGraphics and Metal, I created a small 2D FireMonkey application that stress tests the rendering engine. It is basically a form with a whole lot of controls on it, mostly copied from the ControlsDemo sample app that ships with Delphi. You can find it in our JustAddCode respository on GitHub.

The application tries to repaint the window as fast as possible. To make sure the entire form is repainted, the background color is constantly changed. The application also tests the impact of rendering a 3D control (the globe) inside a 2D form. You can hide this control with the checkbox in the top-left corner to see the difference.

You need to build one of these 3 Release configurations:

  • Default: uses the default rendering technology (CoreGraphics, CoreText and OpenGL on macOS).
  • Metal: uses Metal for all rendering. This sets GlobalUseMetal to True.
  • GPUCanvas: sets GlobalUseGPUCanvas to True. This uses the Direct3D on Windows and OpenGL on macOS for all rendering. Note that this configuration will not work on macOS and may lead to glitches on Windows.

I measured the performance on my 13″ MacBook Pro from 2019. I used the Activity Monitor app to check CPU and GPU usage (you may want to use Instruments for more detailed results). The results are:

ConfigurationFramerate (FPS)CPU%GPU%
Default, without 3D control201040.0
Default, with 3D control16982.8
Metal, without 3D control609510.8
Metal, with 3D control579915.1

Yes, CPU percentages can exceed 100%! Activity Monitor shows the sum of all CPU cores here. Since my MacBook has 4 CPU cores, this percentage could go up to 400%.

The results speak for themselves. With CoreGraphics, this app can’t achieve more than 20 fps on a very recent MacBook. With Metal, this jumps to 60 fps, and even has a bit smaller CPU load. It could probably even exceed 60 fps, but by default Metal doesn’t render faster than the refresh rate of the display.

The Metal configurations use 10-15% of the GPU in this example, which I think is a good trade-off for the gain in performance and responsiveness.

For 3D applications, the benefit is less clear. You will also find this 3D app in our GitHub repo:

It just renders and animates a whole bunch of globes (with lots of vertices for stress testing). It also tests the impact of rendering a 2D control (a panel) inside a 3D form. Again, you can hide this control with the checkbox in the top-left corner.

The results are:

ConfigurationFramerate (FPS)CPU%GPU%
Default, without 2D control309633
Default, with 2D control309433
Metal, without 2D control308825
Metal, with 2D control308925

Metal performs about the same as the default (OpenGL) in this case. It seems to use slightly less CPU and a bit less GPU. The frame rates are nearly the same though.

Of course, you still get the benefit of using Metal instead of the deprecated OpenGL. Which means your app will still work after Apple has completely removed support for OpenGL.

Caveats

As always, there are some caveats, but these are relatively minor. First is the fact that Delphi 10.4 is the first Delphi version that uses Metal. As such, there may still be some bugs to work out, although I haven’t found any show stoppers.

Second, using Metal will disable the use of CoreText for text layout. Instead, it uses Delphi’s own TGPUTextLayout. While this works fine for western languages, TGPUTextLayout doesn’t support advanced text layout features such as text shaping (and ligatures), bi-directional text and language-specific line breaking. These features are important for many Arabic, Indic and Eastern languages.

And finally, if you are using custom OpenGL or DirectX shaders, then you will need to write Metal versions of these. Metal has its own shader language (MSL) that is a bit different to what you may be used to. Fortunately, the basic principles behind all shader languages are pretty much the same, so converting to MSL should be doable. Since most of you will probably not use custom shaders, this will not be an issue. I will have tackle this though for the Metal version of our Lumicademy app…

Conclusion

With Delphi 10.4’s new exciting language and IDE features, the addition of Metal support stays a bit under the radar. I hope this article changes this since switching to Metal can have a huge impact on performance and responsiveness of your macOS apps. And save battery life at the same time.

5 thoughts on “Boost Mac performance with Metal and Delphi 10.4

    1. I haven’t tested mobile yet, but I suspect that Metal has less as an impact on iOS then it does on macOS since FireMonkey already uses a GPU (OpenGL) canvas on iOS.

      Like

    1. Maybe I’ll give an update once I have tested on iOS. But as I said, I don’t expect a performance boost on iOS, since it already uses a GPU canvas…

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s