Sign in Join
Wiki copytemporary

Metal Framework

(macOS only)

Overview

Graphics processors are designed to quickly render graphics and perform data-parallel calculations. The Apple Metal framework allows you to communicate directly with the graphics processor available on a device. Applications that render complex scenes or that perform advanced scientific calculations can use this power to achieve maximum performance. Such applications include:

Metal works hand-in-hand with other Apple frameworks that supplement its capability. MetalKit simplifies the task of getting your Metal content onscreen. Metal Performance Shaders implement custom rendering functions or to take advantage of a large library of existing functions.

Many high level Apple frameworks are built on top of Metal to take advantage of its performance, including Core Image, SpriteKit, and SceneKit. Using one of these high-level frameworks shields you from the details of graphics processor programming, but writing custom Metal code enables you to achieve the highest level of performance.

Metal and Lazarus

Metal support in Lazarus uses Ryan Joseph's lazmetalcontrol.

Requirements

Errata

The Lazarus 2.0.6 release for macOS contained a regression for OpenGL support. See Bugtracker Issue #35797 GLCocoaNSContext.pas now requires "cocoa_extra" in the Uses section.

Lazarus Example Code

ChrisR has created a few Lazarus projects that can be compiled for Apple's Metal Framework. The projects can be downloaded from GitHub and will compile on Lazarus provided you have met the requirements outlined above.

Unfortunately the documentation is sparse, but both the main page and the line project include some useful information.

Most of the projects can also be compiled to support OpenGL 3.3 Core - providing support for Windows, Linux and older Macs. By keeping a thin layer for either OpenGL or Metal, one can retain most of the Lazarus write once, compile everywhere magic.

These example projects all use Lazarus. However, Ryan's repository also includes some examples of how to use Metal directly from Free Pascal without Lazarus.

See this Forum post with demo project for offscreen rendering in Metal.

Metal debugging in Xcode

If your Lazarus application uses Metal graphics, Igor Kokarev explains below how you can save a GPU trace to a file and then open it in Xcode to check the performance of Metal, view much useful information about your 3D commands and even see a visualization of your 3D scene. Note that this requires macOS 10.15 Catalina or newer and FPC 3.2.0.

Step 1 Download Ryan Joseph's lazmetalcontrol.

Step 2 Replace the file MTLCaptureManager.inc with this one:

pascal
{ Parsed from Metal.framework /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/Metal.framework/Headers/MTLCaptureManager.h }

{$ifdef TYPES}
type
  MTLCaptureDescriptorPtr = ^MTLCaptureDescriptor;
  MTLCaptureManagerPtr = ^MTLCaptureManager;
{$endif}

{$ifdef EXTERNAL_SYMBOLS}
//var
//  MTLCaptureErrorDomain: NSErrorDomain; cvar; external;
{$endif}

{$ifdef TYPES}
type
  MTLCaptureError = NSInteger;
  MTLCaptureErrorPtr = ^MTLCaptureError;

const
  MTLCaptureErrorNotSupported = 1;
  MTLCaptureErrorAlreadyCapturing = 2;
  MTLCaptureErrorInvalidDescriptor = 3;

type
  MTLCaptureDestination = NSInteger;
  MTLCaptureDestinationPtr = ^MTLCaptureDestination;

const
  MTLCaptureDestinationDeveloperTools = 1;
  MTLCaptureDestinationGPUTraceDocument = 2;
{$endif}

{$ifdef CLASSES}

type
  MTLCaptureDescriptor = objcclass external (NSObject, NSCopyingProtocol)
  private
    _captureObject: id;
    _destination: MTLCaptureDestination;
    _outputURL: NSURL;
  public
    procedure setCaptureObject(newValue: id); message 'setCaptureObject:';
    function captureObject: id; message 'captureObject';
    procedure setDestination(newValue: MTLCaptureDestination); message 'setDestination:';
    function destination: MTLCaptureDestination; message 'destination';
    procedure setOutputURL(newValue: NSURL); message 'setOutputURL:';
    function outputURL: NSURL; message 'outputURL';

    { Adopted protocols }
    function copyWithZone (zone: NSZonePtr): id; message 'copyWithZone:';
  end;

type
  MTLCaptureManager = objcclass external (NSObject)
  private
    _isCapturing: objcbool;
    _defaultCaptureScope: MTLCaptureScopeProtocol;
  public
    class function sharedCaptureManager: MTLCaptureManager; message 'sharedCaptureManager';
    function init: id; message 'init'; { unavailable in macos, ios }
    function newCaptureScopeWithDevice (device: id): id; message 'newCaptureScopeWithDevice:';
    function newCaptureScopeWithCommandQueue (commandQueue: id): id; message 'newCaptureScopeWithCommandQueue:';
    function supportsDestination (destination: MTLCaptureDestination): objcbool; message 'supportsDestination:'; { available in macos(10.15), ios(13.0) }
    function startCaptureWithDescriptor_error (descriptor: MTLCaptureDescriptor; error: NSErrorPtr): objcbool; message 'startCaptureWithDescriptor:error:'; { available in macos(10.15), ios(13.0) }
    procedure startCaptureWithDevice (device: id); message 'startCaptureWithDevice:'; deprecated 'Use startCaptureWithDescriptor:error: instead in macos(10.13, 10.15), ios(11.0, 13.0)';
    procedure startCaptureWithCommandQueue (commandQueue: id); message 'startCaptureWithCommandQueue:'; deprecated 'Use startCaptureWithDescriptor:error: instead in macos(10.13, 10.15), ios(11.0, 13.0)';
    procedure startCaptureWithScope (captureScope: id); message 'startCaptureWithScope:'; deprecated 'Use startCaptureWithDescriptor:error: instead in macos(10.13, 10.15), ios(11.0, 13.0)';
    procedure stopCapture; message 'stopCapture';
    procedure setDefaultCaptureScope(newValue: id); message 'setDefaultCaptureScope:';
    function defaultCaptureScope: id; message 'defaultCaptureScope';
    function isCapturing: objcbool; message 'isCapturing';
  end;
{$endif}

This updated header includes a new Cocoa interface - MTLCaptureDescriptor.

Step 3 Insert a new text line in the existing file DefinedClassesMetal.pas:

pascal
MTLCaptureDescriptor = objcclass external;

Ryan will probably include both changes (Steps 2 and 3) in a future update to his Metal framework.

Step 4 Add this key and value to the Info.plist file in your application bundle:

xml
<key>MetalCaptureEnabled</key>
  <true/>

Note: remove this key when you do a release build for your application because this key has a small negative effect on Metal performance.

Step 5 Add the following code for your Metal application:

pascal
captureManager:    MTLCaptureManager;
captureDescriptor: MTLCaptureDescriptor;
pascal
procedure TMetalContext.StartMetalDebug;
var
  nserr:   NSError;
  //str:     string;
begin
  // GPU trace for debugging of Metal
  captureManager := MTLCaptureManager.sharedCaptureManager;
  if captureManager.supportsDestination(MTLCaptureDestinationGPUTraceDocument) then 
    begin
      captureDescriptor := MTLCaptureDescriptor.alloc.init.autorelease;
      captureDescriptor.SetCaptureObject(device);
      captureDescriptor.SetDestination(MTLCaptureDestinationGPUTraceDocument);
      captureDescriptor.setOutputURL(NSURL.URLWithString(StrToNSStr('file:///Users/mac/Development/myapp.gputrace')));

      if not captureManager.startCaptureWithDescriptor_error(captureDescriptor, @nserr) then 
        begin
          // Metal captiring failed.
          exit;
        end;
     
      //str := NSStrToStr(nserr.localizedDescription);
    end;
end;

procedure TMetalContext.StopMetalDebug;
begin
  captureManager.stopCapture;
end;

A saved file with GPU trace should use the .gputrace file extension.

When you call stopCapture() the application will save the *.gputrace file. Double click on this file in the Finder and it will be opened in Xcode for you to explore the Metal performance and 3D data.

As it is only possible to capture one frame at a time, just call the start and stop commands before and after you render each frame.

More information is available in the Apple documentation:

External links


From the Free Pascal wiki, page "Metal Framework", as it stood on 2022-02-13 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Revision 1, last changed by WikiArchive .

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.