Setup

# 🐑 Clone the repo
git clone https://github.com/alaingalvan/metal-seed --recurse-submodules
# 💿 go inside the folder
cd metal-seed
# 👯 If you forget to `recurse-submodules` you can always run:
git submodule update --init
# 👷 Make a build folder
mkdir build
cd build
# 🖼️ To build your Visual Studio solution on Windows x64
cmake .. -A x64
# 🍎 To build your XCode project on Mac OS
cmake .. -G Xcode
# 🍎📱 To build your XCode project targeting iOS / iPad OS
cmake .. -G Xcode -DCMAKE_SYSTEM_NAME=iOS
# 🐧 To build your .make file on Linux
cmake ..
# 🔨 Build on any platform:
cmake --build .

Overview

Window Creation

#include "CrossWindow/CrossWindow.h"
#include "Renderer.h"
#include <iostream>void xmain(int argc, const char** argv)
{
// 🖼 Create Window
xwin::WindowDesc wdesc;
wdesc.title = "Metal Seed";
wdesc.name = "MainWindow";
wdesc.visible = true;
wdesc.width = 640;
wdesc.height = 640;
wdesc.fullscreen = false;
xwin::Window window;
xwin::EventQueue eventQueue;
if (!window.create(wdesc, eventQueue))
{ return; };
// 🌋 Create a renderer
Renderer renderer(window);
// 🏁 Engine loop
bool isRunning = true;
while (isRunning)
{
bool shouldRender = true;
// ♻️ Update the event queue
eventQueue.update();
// 🎈 Iterate through that queue:
while (!eventQueue.empty())
{
//Update Events
const xwin::Event& event = eventQueue.front();
// 💗 On Resize:
if (event.type == xwin::EventType::Resize)
{
const xwin::ResizeData data = event.data.resize;
renderer.resize(data.width, data.height);
shouldRender = false;
}
// ❌ On Close:
if (event.type == xwin::EventType::Close)
{
window.close();
shouldRender = false;
isRunning = false;
}
eventQueue.pop();
}
// ✨ Update Visuals
if (shouldRender)
{
renderer.render();
}
}
}

Initialize API

Metal Layer

// ☕ Use CrossWindow-Graphics to create Metal Layer
xgfx::createMetalLayer(&window);
xwin::WindowDelegate& del = window.getDelegate();
CAMetalLayer* layer = (CAMetalLayer*)del.layer;

Device

// 👋 Declare handles
MTLCommandBuffer* device;
// 🎮 Create device
layer.device = MTLCreateSystemDefaultDevice();
device = layer.device;

Command Queue

// 👋 Declare handles
MTLCommandQueue* commandQueue;
// 📦 Create the command queue
commandQueue = [device newCommandQueue];

Initialize Resources

Vertex Buffer

// 📈 Describe Position Vertex Buffer Data
float positions[3*3] = { 1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
0.0f, -1.0f, 0.0f };
// 🎨 Describe Color Vertex Buffer Data
float colors[3*3] = { 1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f };
// 👋 Declare handles
MTLBuffer* positionBuffer;
MTLBuffer* colorBuffer;
// ⚪ Create VBO
positionBuffer = [device newBufferWithLength:sizeof(Vertex) * 3
options:MTLResourceOptionCPUCacheModeDefault];
// 💬 Label VBO
[positionBuffer setLabel:@"PositionBuffer"];
// 💾 Push data to VBO
memcpy(positionBuffer.contents, positions, sizeof(float) * 3 * 3);
// ⚪ Create VBO
colorBuffer = [device newBufferWithLength:sizeof(Vertex) * 3
options:MTLResourceOptionCPUCacheModeDefault];
// 💬 Label VBO
[colorBuffer setLabel:@"ColorBuffer"];
// 💾 Push data to VBO
memcpy(colorBuffer.contents, colors, sizeof(float) * 3 * 3);

Index Buffer

// 🗄️ Describe Index Buffer Data
unsigned indexBufferData[3] = { 0, 1, 2 };
// ✋ Declare Index Buffer Handle
MTLBuffer* indexBuffer;
// 🃏 Index Data
indexBuffer = [device newBufferWithLength:sizeof(unsigned) * 3
options:MTLResourceOptionCPUCacheModeDefault];
[indexBuffer setLabel:@"IBO"];
memcpy(indexBuffer.contents, indexBufferData, sizeof(unsigned) * 3);

Uniform Buffer

// 👋 Declare handles
MTLBuffer* uniformBuffer;
// 🗄️ Describe Uniform Data
struct UniformData
{
mat4 projectionMatrix;
mat4 modelMatrix;
mat4 viewMatrix;
} uboVS;
// 🎛️ Create Uniform Buffer
uniformBuffer = [device newBufferWithLength:(sizeof(UniformData) + 255) & ~255
options:MTLResourceOptionCPUCacheModeDefault];
[uniformBuffer setLabel:@"UBO"];
// Update Uniforms...

Shader Libraries

// Load all the shader files with a .msl file extension in the projectNSError* err = nil;// 📂 Load shader files, add null terminator to the end.
std::vector<char> vertSource = readFile("triangle.vert.msl");
vertSource.emplace_back(0);
std::vector<char> fragSource = readFile("triangle.frag.msl");
fragSource.emplace_back(0);
NSString* vertPath = [NSString stringWithCString:vertSource.data() encoding:[NSString defaultCStringEncoding]];
MTLLibrary* vertLibrary = [device newLibraryWithSource:vertPath options:nil error:&err];
[vertPath dealloc];
NSString* fragPath = [NSString stringWithCString:fragSource.data() encoding:[NSString defaultCStringEncoding]];
MTLLibrary* fragLibrary = [device newLibraryWithSource:fragPath options:nil error:&err];
[fragPath dealloc];
// Load the vertex function from the library
MTLFunction* vertexFunction = [vertLibrary newFunctionWithName:@"main0"];
// Load the fragment function from the library
MTLFunction* fragmentFunction = [fragLibrary newFunctionWithName:@"main0"];

Pipeline State

// 👋 Declare handles
MTLRenderPipelineState* pipelineState;
// ⚗️ Graphics Pipeline
MTLRenderPipelineDescriptor* pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"Simple Pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = layer.pixelFormat;
// 🔣 Input Assembly
MTLVertexDescriptor* vertexDesc = [MTLVertexDescriptor vertexDescriptor];
vertexDesc.attributes[0].format = MTLVertexFormatFloat3;
vertexDesc.attributes[0].offset = 0;
vertexDesc.attributes[0].bufferIndex = 0;
vertexDesc.attributes[1].format = MTLVertexFormatFloat3;
vertexDesc.attributes[1].offset = sizeof(float) * 3;
vertexDesc.attributes[1].bufferIndex = 0;
vertexDesc.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
vertexDesc.layouts[0].stride = sizeof(Vertex);
pipelineStateDescriptor.vertexDescriptor = vertexDesc;
NSError* error = nil;
// 🌟 Create Pipeline State Object
pipelineState = [device
newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
error:&error];
if (!pipelineState)
{
NSLog(@"Failed to created pipeline state, error %@", error);
}

Rendering

Render Pass

// 👋 Declare Handles
CAMetalLayer* layer;
// 🤵 Build renderPassDescriptor generated from the view's drawable textures
CAMetalDrawable* drawable = layer.nextDrawable;
MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDescriptor.colorAttachments[0].texture = drawable.texture;
renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
MTLClearColor clearCol;
clearCol.red = 0.2;
clearCol.green = 0.2;
clearCol.blue = 0.2;
clearCol.alpha = 1.0;
renderPassDescriptor.colorAttachments[0].clearColor = clearCol;

Command Buffer

// 👋 Declare Metal Handle
MTLCommandBuffer* commandBuffer;
unsigned viewportSize[2];
if (commandBuffer != nil)
{ [commandBuffer release]; }
mCommandBuffer = [(commandQueue commandBuffer];
(commandBuffer).label = @"MyCommand";
if(renderPassDescriptor != nil)
{
// Create a render command encoder so we can render into something
id<MTLRenderCommandEncoder> renderEncoder =
[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
renderEncoder.label = @"MyRenderEncoder";
// Set the region of the drawable to which we'll draw.
[renderEncoder setViewport:(MTLViewport){0.0, 0.0, static_cast<float>(viewportSize[0]), static_cast<float>(viewportSize[1]), 0.1, 1000.0 }];
[renderEncoder setRenderPipelineState: pipelineState]; [renderEncoder setCullMode:MTLCullModeNone]; [renderEncoder setVertexBuffer:positionBuffer offset:0 atIndex:0]; [renderEncoder setVertexBuffer:colorBuffer offset:0 atIndex:1]; [renderEncoder setVertexBuffer:uniformBuffer offset:0 atIndex:2]; [renderEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexCount:3 indexType:MTLIndexTypeUInt32 indexBuffer:indexBuffer indexBufferOffset:0]; [renderEncoder endEncoding]; [commandBuffer presentDrawable:drawable]; [commandBuffer commit];
}

Destroying Handles

void destroyAPI()
{
if (commandBuffer != nil)
{
[commandBuffer release];
}

[commandQueue release];

[device release];
}
void destroyResources()
{
[fragmentFunction release];
[vertexFunction release];

[vertLibrary release];
[fragLibrary release];
[positionBuffer release];
[colorBuffer release];
[indexBuffer release];
[uniformBuffer release];

[pipelineState release];
}

Conclusion

https://Alain.xyz | Graphics Software Engineer @ AMD, Previously @ Marmoset.co. Guest lecturer talking about 🛆 Computer Graphics, ✍ tech author.