Skip to content

Instantly share code, notes, and snippets.

@mhamilt
Last active December 13, 2024 03:46
Show Gist options
  • Save mhamilt/a5c2bbb02684e5db362712c9be7a02ca to your computer and use it in GitHub Desktop.
Save mhamilt/a5c2bbb02684e5db362712c9be7a02ca to your computer and use it in GitHub Desktop.
Metal Compute Shader Example in Swift and Objective C
#include <metal_stdlib>
using namespace metal;
kernel void add(const device float2 *in [[ buffer(0) ]],
device float *out [[ buffer(1) ]],
uint id [[ thread_position_in_grid ]]) {
out[id] = in[id].x + in[id].y;
}
@import MetalKit;
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//----------------------------------------------------------------------
// Setup
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
id<MTLCommandQueue> commandQueue = [device newCommandQueue];
id<MTLLibrary> library = [device newDefaultLibrary];
id<MTLFunction> kernelFunction = [library newFunctionWithName:@"add"];
//----------------------------------------------------------------------
// pipeline
NSError *error = NULL;
[commandQueue commandBuffer];
id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
id<MTLComputeCommandEncoder> encoder = [commandBuffer computeCommandEncoder];
[encoder setComputePipelineState:[device newComputePipelineStateWithFunction:kernelFunction error:&error]];
//----------------------------------------------------------------------
// Set Data
float input[] = {1,2};
NSInteger dataSize = sizeof(input);
[encoder setBuffer:[device newBufferWithBytes:input length:dataSize options:0]
offset:0
atIndex:0];
id<MTLBuffer> outputBuffer = [device newBufferWithLength:sizeof(float) options:0];
[encoder setBuffer:outputBuffer offset:0 atIndex:1];
//----------------------------------------------------------------------
// Run Kernel
MTLSize numThreadgroups = {1,1,1};
MTLSize numgroups = {1,1,1};
[encoder dispatchThreadgroups:numThreadgroups threadsPerThreadgroup:numgroups];
[encoder endEncoding];
[commandBuffer commit];
[commandBuffer waitUntilCompleted];
//----------------------------------------------------------------------
// Results
float *output = [outputBuffer contents];
printf("result = %f\n", output[0]);
}
return 0;
}
import MetalKit
//----------------------------------------------------------------------
// Setup
let device = MTLCreateSystemDefaultDevice()!
let commandQueue = device.makeCommandQueue()!
let library = try device.makeLibrary(filepath: "compute.metallib")
//----------------------------------------------------------------------
// pipeline
let commandBuffer = commandQueue.makeCommandBuffer()!
let encoder = commandBuffer.makeComputeCommandEncoder()!
encoder.setComputePipelineState(try device.makeComputePipelineState(function: library.makeFunction(name: "add")!))
//----------------------------------------------------------------------
// Set Data
let input: [Float] = [1.0, 2.0]
encoder.setBuffer(device.makeBuffer(bytes: input as [Float], length: MemoryLayout<Float>.stride * input.count, options: []),
offset: 0, index: 0)
let outputBuffer = device.makeBuffer(length: MemoryLayout<Float>.stride, options: [])!
encoder.setBuffer(outputBuffer, offset: 0, index: 1)
//----------------------------------------------------------------------
// Run Kernel
let numThreadgroups = MTLSize(width: 1, height: 1, depth: 1)
let threadsPerThreadgroup = MTLSize(width: 1, height: 1, depth: 1)
encoder.dispatchThreadgroups(numThreadgroups, threadsPerThreadgroup: threadsPerThreadgroup)
encoder.endEncoding()
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
//----------------------------------------------------------------------
// Results
let result = outputBuffer.contents().load(as: Float.self)
print(String(format: "%f + %f = %f", input[0], input[1], result))

Notes

Create a command line target in xcode either in Swift or Obj-C. add a metal file to the project and copy and paste the respective code into each file.

@greggman
Copy link

thanks for the example.

Swift/ErrorType.swift:200: Fatal error: Error raised at top level: Error Domain=MTLLibraryErrorDomain Code=6 "library not found" UserInfo={NSLocalizedDescription=library not found}

I think maybe there's some common mac dev knowledge here not specified? Or maybe this is out of date?

Also:

'makeLibrary(filepath:)' was deprecated in macOS 13.0: Use -newLibraryWithURL:error: instead

@mhamilt
Copy link
Author

mhamilt commented Aug 23, 2024

Thanks @greggman,

This is horrendously out of date I think (from about 2019) so the moving target that is Swift has scurried somewhere else once again.

I think (and I say this without any testing) the Obj-C side should be correct still [citation needed]

@mhamilt
Copy link
Author

mhamilt commented Aug 23, 2024

@greggman

Also, as an aside, I had many happy memories playing M.C. Kids and I love the blog.

@komakai
Copy link

komakai commented Dec 13, 2024

I put a gist here that is working now (late 2024)
https://gist.github.com/komakai/f0046a57a904c7052dab50a95b206589

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment