Metal-cpp is a low-overhead C++ interface for Metal that helps developers add Metal functionality to graphics apps, games, and game engines that are written in C++.

Overview

About

metal-cpp is a low overhead and header only C++ interface for Metal that helps developers add Metal functionality to graphics applications that are written in C++ (such as game engines). metal-cpp removes the need to create a shim and allows developers to call Metal functions directly from anywhere in their existing C++ code.

Highlights

  • Drop in C++ alternative interface to the Metal Objective-C headers.
  • Direct mapping of all Metal Objective-C classes, constants and enums to C++ in the MTL C++ namespace.
  • No measurable overhead compared to calling Metal Objective-C headers, due to inlining of C++ function calls.
  • No usage of wrapper containers that require additional allocations.
  • Requires C++17 due to the usage of constexpr in NS::Object.
  • Identical header files and function/constant/enum availability for iOS, macOS and tvOS.
  • Backwards compatibility: All bool MTL::Device::supports...() functions check if their required selectors exist and automatically return false if not.
  • String (ErrorDomain) constants are weak linked and automatically set to nullptr if not available.

Memory Allocation Policy

metal-cpp follows the object allocation policies of Cocoa and Cocoa Touch. Understanding those rules is especially important when using metal-cpp, as C++ objects are not eligible for automatic reference counting (ARC).

metal-cpp objects are reference counted. To help convey and manage object lifecycles, the following conventions are observed:

  1. You own any object returned by methods whose name begins with alloc , new , copy , or mutableCopy. The method returns these objects with retainCount equals to 1.
  2. You can take ownership of an object by calling its retain() method. A received object is normally guaranteed to remain valid within the method it was received in. You use retain in two situations: (1) In the implementation of an accessor method (a setter) or to take ownership of an object; and (2) To prevent an object from being deallocated as a side-effect of some other operation.
  3. When you no longer need it, you must relinquish ownership of an object you own. You relinquish ownership by calling its release() or autorelease() method.
  4. You must not relinquish ownership of an object you do not own.

When an object's retainCount reaches 0, the object is immediately deallocated. It is illegal to call methods on a deallocated object and it may lead to an application crash.

AutoreleasePools and Objects

Several methods that create temporary objects in metal-cpp add them to an AutoreleasePool to help manage their lifetimes. In these situations, after metal-cpp creates the object, it adds it to an AutoreleasePool, which will release its objects when you release (or drain) it.

By adding temporary objects to an AutoreleasePool, you do not need to explicitly call release() to deallocate them. Instead, you can rely on the AutoreleasePool to implicitly manage those lifetimes.

If you create an object with a method that does not begin with alloc, new, copy, or mutableCopy, the creating method adds the object to an autorelease pool.

The typical scope of an AutoreleasePool is one frame of rendering for the main thread of the program. When the thread returns control to the RunLoop (an object responsible for receiving input and events from the windowing system), the pool is drained, releasing its objects.

You can create and manage additional AutoreleasePools at smaller scopes to reduce your program's working set, and you are required to do so for any additional threads your program creates.

If an object's lifecycle needs to be extended beyond the AutoreleasePool's scope, you can claim ownership of it (avoiding its release beyond the pool's scope) by calling its retain() method before its pool is drained. In these cases, you will be responsible for making the appropriate release() call on the object after you no longer need it.

You can find a more-detailed introduction to the memory management rules here: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html.

For more details about the application's RunLoop, please find its documentation here: https://developer.apple.com/documentation/foundation/nsrunloop

Use and debug AutoreleasePools

When you create an autoreleased object and there is no enclosing AutoreleasePool, the object is leaked.

To prevent this, you normally create an AutoreleasePool in your program's main function, and in the entry function for every thread you create. You may also create additional AutoreleasePools to avoid growing your program's high memory watermark when you create several autoreleased objects, such as when rendering.

Use the Environment Variable OBJC_DEBUG_MISSING_POOLS=YES to print a runtime warning when an autoreleased object is leaked because no enclosing AutoreleasePool is available for its thread.

You can also run leaks --autoreleasePools on a memgraph file or a process ID (macOS only) to view a listing of your program's AutoreleasePools and all objects they contain.

nullptr

Similar to Objective-C, it is legal to call any method, including retain() and release(), on nullptr "objects". While calling methods on nullptr still does incur in function call overhead, the effective result is equivalent of a NOP.

Conversely, do not assume that because calling a method on a pointer did not result in a crash, that the pointed-to object is valid.

Adding metal-cpp to a Project

Simply include Metal/Metal.hpp. To ensure that the selector and class symbols are linked, add to one of your cpp files:

#define NS_PRIVATE_IMPLEMENTATION
#define MTL_PRIVATE_IMPLEMENTATION

#include "Metal/Metal.hpp"

If you want to use the QuartzCore wrapper, add:

#define CA_PRIVATE_IMPLEMENTATION

#include "QuartzCore/QuartzCore.hpp"

Generating a Single Header File

Purely optional: You can generate a single header file that contains all metal-cpp headers via:

./SingleHeader/MakeSingleHeader.py Foundation/Foundation.hpp QuartzCore/QuartzCore.hpp Metal/Metal.hpp

By default the generated header file will be written to ./SingleHeader/Metal.hpp

Examples

Creating the device

Objective-C (with automatic reference counting)
id< MTLDevice > device = MTLCreateSystemDefaultDevice();

// ...
Objective-C
id< MTLDevice > device = MTLCreateSystemDefaultDevice();

// ...

[device release];
C++
MTL::Device* pDevice = MTL::CreateSystemDefaultDevice();

// ...

pDevice->release();

Metal function calls map directly to C++

Objective-C (with automatic reference counting)
MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init];

[samplerDescriptor setSAddressMode: MTLSamplerAddressModeRepeat];
[samplerDescriptor setTAddressMode: MTLSamplerAddressModeRepeat];
[samplerDescriptor setRAddressMode: MTLSamplerAddressModeRepeat];
[samplerDescriptor setMagFilter: MTLSamplerMinMagFilterLinear];
[samplerDescriptor setMinFilter: MTLSamplerMinMagFilterLinear];
[samplerDescriptor setMipFilter: MTLSamplerMipFilterLinear];
[samplerDescriptor setSupportArgumentBuffers: YES];

id< MTLSamplerState > samplerState = [device newSamplerStateWithDescriptor:samplerDescriptor];
Objective-C
MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init];

[samplerDescriptor setSAddressMode: MTLSamplerAddressModeRepeat];
[samplerDescriptor setTAddressMode: MTLSamplerAddressModeRepeat];
[samplerDescriptor setRAddressMode: MTLSamplerAddressModeRepeat];
[samplerDescriptor setMagFilter: MTLSamplerMinMagFilterLinear];
[samplerDescriptor setMinFilter: MTLSamplerMinMagFilterLinear];
[samplerDescriptor setMipFilter: MTLSamplerMipFilterLinear];
[samplerDescriptor setSupportArgumentBuffers: YES];

id< MTLSamplerState > samplerState = [device newSamplerStateWithDescriptor:samplerDescriptor];

[samplerDescriptor release];

// ...

[samplerState release];
C++
MTL::SamplerDescriptor* pSamplerDescriptor = MTL::SamplerDescriptor::alloc()->init();

pSamplerDescriptor->setSAddressMode( MTL::SamplerAddressModeRepeat );
pSamplerDescriptor->setTAddressMode( MTL::SamplerAddressModeRepeat );
pSamplerDescriptor->setRAddressMode( MTL::SamplerAddressModeRepeat );
pSamplerDescriptor->setMagFilter( MTL::SamplerMinMagFilterLinear );
pSamplerDescriptor->setMinFilter( MTL::SamplerMinMagFilterLinear );
pSamplerDescriptor->setMipFilter( MTL::SamplerMipFilterLinear );
pSamplerDescriptor->setSupportArgumentBuffers( true );

MTL::SamplerState* pSamplerState = pDevice->newSamplerState( pSamplerDescriptor );

pSamplerDescriptor->release();

// ...

pSamplerState->release();

A subset of bindings for Foundation classes is provided for seamless integration

Objective-C (with automatic reference counting)
NSAutoreleasePool*  pool   = [[NSAutoreleasePool alloc] init];
NSString*           string = [NSString stringWithCString: "Hello World" encoding: NSASCIIStringEncoding];

printf( "string = \"%s\"\n", [string cStringUsingEncoding: NSASCIIStringEncoding] );
Objective-C
NSAutoreleasePool* pool   = [[NSAutoreleasePool alloc] init];
NSString*          string = [NSString stringWithCString: "Hello World" encoding: NSASCIIStringEncoding];
								
printf( "string = \"%s\"\n", [string cStringUsingEncoding: NSASCIIStringEncoding] );

[pool release];
C++
NS::AutoreleasePool* pPool   = NS::AutoreleasePool::alloc()->init();
NS::String*          pString = NS::String::string( "Hello World", NS::ASCIIStringEncoding );

printf( "pString = \"%s\"\n", pString->cString( NS::ASCIIStringEncoding ) );

pPool->release();

Interoperability with CoreFoundation

MTL::AccelerationStructureTriangleGeometryDescriptor* pGeoDescriptor  = MTL::AccelerationStructureTriangleGeometryDescriptor::alloc()->init();
CFTypeRef                                             descriptors[]   = { ( CFTypeRef )( pGeoDescriptor ) };
NS::Array*                                            pGeoDescriptors = ( NS::Array* )( CFArrayCreate( kCFAllocatorDefault, descriptors, SIZEOF_ARRAY( descriptors), &kCFTypeArrayCallBacks ) );

// ...

pGeoDescriptors->release();

Accessing a CAMetalDrawable

#import <QuartzCore/CAMetalLayer.h>
#import <QuartzCore/QuartzCore.hpp>

// ...

CAMetalLayer*         metalLayer         = /* get your layer from your view */;
id< CAMetalDrawable > metalDrawable      = [metalLayer nextDrawable];
CA::MetalDrawable*    pMetalCppDrawable  = ( __bridge CA::MetalDrawable* ) metalDrawable;

// ...
You might also like...
Yet another Chip-8 interpreter, this time written in C++ using GLFW and OpenGL as its graphics library 💻
Yet another Chip-8 interpreter, this time written in C++ using GLFW and OpenGL as its graphics library 💻

Yet another Chip-8 interpreter, but this time with a beautiful interface 💻

Vire is a C++ voxel rendering engine. It is written in C++14 and uses OpenGL for graphics.

Vire Vire is a C++ voxel rendering engine. It is written in C++14 and uses OpenGL for graphics. Downloads If you'd just like to just download and try,

A library for high-performance, modern 2D graphics with SDL written in C.

SDL_gpu, a library for making hardware-accelerated 2D graphics easy. by Jonathan Dearborn SDL_gpu is licensed under the terms of the MIT License. See

[WIP] A media playback library for Dart & Flutter apps on Windows & Linux. Based on libVLC & libVLC++.
[WIP] A media playback library for Dart & Flutter apps on Windows & Linux. Based on libVLC & libVLC++.

dart_vlc Bringing power of VLC to Flutter & Dart apps on Windows & Linux Installation dependencies: ... dart_vlc: ^0.0.1 Documentation Create a ne

Open-source, cross-platform, C++ game engine for creating 2D/3D games.

GamePlay v3.0.0 GamePlay is an open-source, cross-platform, C++ game framework/engine for creating 2D/3D mobile and desktop games. Website Wiki API De

Deno gl - WIP Low-level OpenGL (GLFW) bindings and WebGL API implementation for Deno.

deno_gl WIP Low-level OpenGL (GLFW) bindings and WebGL API implementation for Deno. Building Make dist directory if it doesn't exist. Build gl helper

A low-level, cross-platform GPU library

vgpu is cross-platform low-level GPU library. Features Support for Windows, Linux, macOS. Modern rendering using Vulkan and Direct3D12. Dependencies U

Brand new engine with new and QoL features. Grafex is Psych engine with some additions and Better graphics

Friday Night Funkin' - Graphex Engine Credits: Grafex Mod aka Psych Graphic Rework: Xale - Lead Coding, Artist PurpleSnake - Second Coder Psych Engine

Releases(metal-cpp_macOS12_iOS15)
Owner
Бранимир Караџић
кодер (gamedev, open source, ex-demoscene) ★
Бранимир Караџић
Low Level Graphics Library (LLGL) is a thin abstraction layer for the modern graphics APIs OpenGL, Direct3D, Vulkan, and Metal

Low Level Graphics Library (LLGL) Documentation NOTE: This repository receives bug fixes only, but no major updates. Pull requests may still be accept

Lukas Hermanns 1.4k Sep 23, 2022
A basic 3D scene implemented with various engines, frameworks or APIs.

Here be dragons Hic sunt dracones. This repository contains multiple implementations of the same 3D scene, using different APIs and frameworks on vari

Simon Rodriguez 1.6k Sep 25, 2022
A modern cross-platform low-level graphics library and rendering framework

Diligent Engine A Modern Cross-Platform Low-Level 3D Graphics Library Diligent Engine is a lightweight cross-platform graphics API abstraction library

Diligent Graphics 2.5k Sep 24, 2022
A terminal-based graphics library for both 2D and 3D graphics.

TermGL A terminal-based graphics library for both 2D and 3D graphics. Written in C, created for terminals supporting ANSI escape codes. Table of Conte

null 207 Sep 21, 2022
Lightweight and modular C++11 graphics middleware for games and data visualization

Magnum — Lightweight and modular C++11/C++14 graphics middleware for games and data visualization Looking for an open-source library that gives you gr

Vladimír Vondruš 4.2k Sep 25, 2022
Cocos2d-x is a suite of open-source, cross-platform, game-development tools used by millions of developers all over the world.

Cocos2d-x is a suite of open-source, cross-platform, game-development tools used by millions of developers all over the world.

cocos2d 16.5k Sep 17, 2022
kaun is a replacement for löve's built-in love.graphics module intended for 3D graphics

kaun kaun is a replacement for löve's built-in love.graphics module intended for 3D graphics. It is a Lua module you can require from a shared library

Joel Schumacher 4 Apr 5, 2021
This repo contains the DirectX Graphics samples that demonstrate how to build graphics intensive applications on Windows.

DirectX-Graphics-Samples This repo contains the DirectX 12 Graphics samples that demonstrate how to build graphics intensive applications for Windows

Microsoft 4.7k Sep 22, 2022
NVRHI (NVIDIA Rendering Hardware Interface) is a library that implements a common abstraction layer over multiple graphics APIs

NVRHI Introduction NVRHI (NVIDIA Rendering Hardware Interface) is a library that implements a common abstraction layer over multiple graphics APIs (GA

NVIDIA GameWorks 406 Sep 13, 2022
Lightweight OpenCL-Wrapper to greatly simplify OpenCL software development with C++ while keeping functionality and performance

OpenCL-Wrapper OpenCL is the most powerful programming language ever created. Yet the OpenCL C++ bindings are very cumbersome and the code overhead pr

Moritz Lehmann 51 Sep 24, 2022