Metal provides low-level access to GPU for high-performance graphics and compute. Learn to create stunning visual effects.
Metal Setup
Initialize Metal Device
swift
import Metal
import MetalKit
class MetalRenderer {
var device: MTLDevice
var commandQueue: MTLCommandQueue
init() {
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("Metal is not supported on this device")
}
self.device = device
self.commandQueue = device.makeCommandQueue()!
}
}Basic Rendering
Metal View Setup
swift
struct MetalView: UIViewRepresentable {
func makeUIView(context: Context) -> MTKView {
let mtkView = MTKView()
guard let device = MTLCreateSystemDefaultDevice() else {
return mtkView
}
mtkView.device = device
mtkView.delegate = context.coordinator
mtkView.enableSetNeedsDisplay = false
mtkView.isPaused = false
context.coordinator.setupMetal(view: mtkView)
return mtkView
}
func updateUIView(_ uiView: MTKView, context: Context) {}
func makeCoordinator() -> Renderer {
Renderer()
}
class Renderer: NSObject, MTKViewDelegate {
var device: MTLDevice?
var commandQueue: MTLCommandQueue?
var pipelineState: MTLRenderPipelineState?
func setupMetal(view: MTKView) {
device = view.device
commandQueue = device?.makeCommandQueue()
view.clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
setupPipelineState(view: view)
}
func setupPipelineState(view: MTKView) {
guard let device = device,
let library = device.makeLibrary(source: """
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
float4 color;
};
vertex VertexOut vertex_main(uint vertexID [[vertex_id]]) {
VertexOut out;
out.position = float4(0.0, 0.0, 0.0, 1.0);
out.color = float4(1.0, 0.0, 0.0, 1.0);
return out;
}
fragment float4 fragment_main(VertexOut in [[stage_in]]) {
return in.color;
}
""", options: []) else {
return
}
let vertexFunction = library.makeFunction(name: "vertex_main")
let fragmentFunction = library.makeFunction(name: "fragment_main")
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
pipelineDescriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat
pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineDescriptor)
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}
func draw(in view: MTKView) {
guard let drawable = view.currentDrawable,
let descriptor = view.currentRenderPassDescriptor,
let commandBuffer = commandQueue?.makeCommandBuffer(),
let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor),
let pipelineState = pipelineState else {
return
}
encoder.setRenderPipelineState(pipelineState)
encoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
}
}
}Shaders
Vertex and Fragment Shaders
swift
// Metal Shaders (.metal file)
#include <metal_stdlib>
using namespace metal;
struct VertexIn {
float3 position [[attribute(0)]];
float4 color [[attribute(1)]];
};
struct VertexOut {
float4 position [[position]];
float4 color;
};
vertex VertexOut vertex_main(VertexIn in [[stage_in]]) {
VertexOut out;
out.position = float4(in.position, 1.0);
out.color = in.color;
return out;
}
fragment float4 fragment_main(VertexOut in [[stage_in]]) {
return in.color;
}Lighting Shader
swift
// Advanced Metal shader with lighting
struct Light {
float3 position;
float3 color;
float intensity;
};
struct Material {
float3 ambient;
float3 diffuse;
float3 specular;
float shininess;
};
fragment float4 lighting_fragment_main(
VertexOut in [[stage_in]],
constant Light& light [[buffer(0)]],
constant Material& material [[buffer(1)]]
) {
// Ambient
float3 ambient = material.ambient * light.color * light.intensity;
// Diffuse
float3 lightDir = normalize(light.position - in.position.xyz);
float diff = max(dot(normalize(in.normal), lightDir), 0.0);
float3 diffuse = material.diffuse * light.color * diff * light.intensity;
// Specular
float3 viewDir = normalize(-in.position.xyz);
float3 reflectDir = reflect(-lightDir, normalize(in.normal));
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
float3 specular = material.specular * light.color * spec * light.intensity;
return float4(ambient + diffuse + specular, 1.0);
}Compute Shaders
GPU Computing
swift
class ComputeProcessor {
var device: MTLDevice
var commandQueue: MTLCommandQueue
init() {
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("Metal not supported")
}
self.device = device
self.commandQueue = device.makeCommandQueue()!
}
func processTexture(texture: MTLTexture) {
guard let commandBuffer = commandQueue.makeCommandBuffer(),
let computeEncoder = commandBuffer.makeComputeCommandEncoder() else {
return
}
let computePipelineState = createComputePipeline()
computeEncoder.setComputePipelineState(computePipelineState)
computeEncoder.setTexture(texture, index: 0)
let threadsPerThreadgroup = MTLSize(width: 16, height: 16, depth: 1)
let threadgroupsPerGrid = MTLSize(
width: (texture.width + 15) / 16,
height: (texture.height + 15) / 16,
depth: 1
)
computeEncoder.dispatchThreadgroups(
threadgroupsPerGrid,
threadsPerThreadgroup: threadsPerThreadgroup
)
computeEncoder.endEncoding()
commandBuffer.commit()
}
private func createComputePipeline() -> MTLComputePipelineState {
let source = """
#include <metal_stdlib>
using namespace metal;
kernel void texture_process(
texture2d<float, access::read> input [[texture(0)]],
texture2d<float, access::write> output [[texture(1)]],
uint2 gid [[thread_position_in_grid]]
) {
float4 color = input.read(gid);
float gray = dot(color.rgb, float3(0.299, 0.587, 0.114));
output.write(float4(gray, gray, gray, color.a), gid);
}
"""
let library = try! device.makeLibrary(source: source, options: nil)
let function = library.makeFunction(name: "texture_process")
let computeDescriptor = MTLComputePipelineDescriptor()
computeDescriptor.computeFunction = function
return try! device.makeComputePipelineState(descriptor: computeDescriptor)
}
}MetalKit Integration
MTKViewDelegate
swift
class GameRenderer: NSObject, MTKViewDelegate {
var device: MTLDevice
var commandQueue: MTLCommandQueue
var pipelineState: MTLRenderPipelineState?
init(mtkView: MTKView) {
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("Metal not supported")
}
self.device = device
self.commandQueue = device.makeCommandQueue()!
super.init()
mtkView.device = device
mtkView.delegate = self
mtkView.clearColor = MTLClearColor(
red: 0.1,
green: 0.1,
blue: 0.1,
alpha: 1.0
)
setupPipeline(view: mtkView)
}
func setupPipeline(view: MTKView) {
// Setup render pipeline
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
// Handle resize
}
func draw(in view: MTKView) {
guard let drawable = view.currentDrawable,
let descriptor = view.currentRenderPassDescriptor,
let commandBuffer = commandQueue.makeCommandBuffer(),
let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
return
}
// Render code
encoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
}
}Performance Optimization
Best Practices
- 1Batching: Batch draw calls
- 2Culling: Use frustum culling
- 3LOD: Level of detail for distant objects
- 4Memory: Manage GPU memory carefully
- 5Profiling: Use Metal performance tools
- 6Shaders: Optimize shader code
- 7State Changes: Minimize state changes
- 8Multithreading: Use command buffers efficiently
Metal enables stunning graphics and high-performance computing!