Metal, Cupertino’s own graphics API, is sort of a middle-ground in complexity between OpenGL and Vulkan. I’ve wanted to try it for a while, but the somewhat tight integration into Apple’s ecosystem (ObjectiveC/Swift and XCode) has so far prevented that. My graphics projects are usually using C++ and CMake, so I wanted a solution that worked with that. Apple released Metal-cpp last year and newer SDL2 versions (since 2.0.14) can create a window that supports drawing to it with metal. Here’s how to weld that together (with minimal ObjectiveC).
metal-cpp
I get the metal-cpp code from the linked website (the download is at step 1). I add a library in CMake that builds a single source file that compiles the metal-cpp implementation with the ??_PRIVATE_IMPLEMENTATION
macros as described on the page (see step 3). That target also exports the includes to be used later.
SDL window and view
Next, I use conan to install SDL2. After SDL_Init
, I call SDL_CreateWindow
to create my window. I do not specify SDL_WINDOW_OPENGL
(or in the SDL_CreateWindow
‘s flags
, or next step will fail. After that, I use SDL_Metal_CreateView
from SDL_metal.h
to create a metal view. This is where things get a little bit icky. I create a metal device using MTL::CreateSystemDefaultDevice();
but I still need to assign it to the view I just created. I’m doing that in ObjectiveC++. In a new .mm
file I add a small function to do that:
void assign_device(void* layer, MTL::Device* device) { CAMetalLayer* metalLayer = (CAMetalLayer*) layer; metalLayer.device = (__bridge id<MTLDevice>)(device); }
I use a small .h
file to expose this function to my C++ code like any other free function. There’s another helper I create in the .mm
file:
CA::MetalDrawable* next_drawable(void* layer) { CAMetalLayer* metalLayer = (CAMetalLayer*) layer; id <CAMetalDrawable> metalDrawable = [metalLayer nextDrawable]; CA::MetalDrawable* pMetalCppDrawable = ( __bridge CA::MetalDrawable*) metalDrawable; return pMetalCppDrawable; }
At the beginning of each frame, I use that together with SDL_Metal_GetLayer
to get a texture to render to:
auto surface = next_drawable(SDL_Metal_GetLayer(view));
Next I create a render pass descriptor that starts by clearing that drawable with our fancy red:
MTL::ClearColor clear_color(152.0/255.0, 23.0/255.0, 42.0/255.0, 1.0); auto pass_descriptor = MTL::RenderPassDescriptor::alloc()->init(); auto attachment = pass_descriptor->colorAttachments()->object(0); attachment->setClearColor(clear_color); attachment->setLoadAction(MTL::LoadActionClear); attachment->setTexture(surface->texture());
And fire that off to the GPU using a command buffer and render encoder:
auto buffer = queue->commandBuffer();
auto encoder = buffer->renderCommandEncoder(pass_descriptor);
encoder->endEncoding();
buffer->presentDrawable(surface);
buffer->commit();
There you have it, a minimal running metal application. Still a long ways from the traditional “Hello Triangle”, but most metal examples that show how to do that can easily be translated to the C++ API. Note that you probably have to take some extra steps to compile metal shaders (aka MSL). You can either load them from source or precompile them using the command line tools.
Is it possible to post a completed set of code for this example, like with a github link or something? I’m new to all this and the minor missing details are preventing me from following the example.
Also, what do you pass as the flag to SDL_CreateWindow? “SDL_WINDOW_METAL”?
Oh sorry, I do not have the isolated code up anywhere. You do not need to pass any other flags – I’m only passing SDL_WINDOW_RESIZABLE for metal. SDL_WINDOW_METAL is for when you want to use SDL for drawing, I think.