Metal, Apple'ın GPU programlama framework'üdür. OpenGL ES'in yerini aldı ve iOS, macOS, tvOS, visionOS — tüm Apple platformlarında çalışır. Oyunlardan makine öğrenmesine, görüntü işlemeden AR'a kadar GPU gücünü kullanman gereken her yerde Metal vardır.
💡 Hızlı Not: Metal, A7 chip (iPhone 5s) ile tanıtıldı. Bugün Apple Silicon (M1-M4) ile masaüstü GPU'larla yarışır seviyeye ulaştı.
İçindekiler
- Metal Nedir ve Neden?
- Metal Pipeline Anatomisi
- Device ve Command Queue
- Render Pipeline: Üçgen Çizelim
- Vertex ve Fragment Shader'lar
- Compute Shader'lar
- Texture Handling
- Metal Performance Shaders
- Metal + SwiftUI
- GPU Debugging
Metal Nedir ve Neden? {#metal-nedir}
Özellik | Metal | OpenGL ES |
|---|---|---|
API overhead | Ultra düşük | Yüksek |
Shader language | MSL (C++ based) | GLSL |
Compute support | ✅ Native | ⚠️ Sınırlı |
Draw call overhead | ~10x daha düşük | Yüksek |
Apple Silicon optimize | ✅ Tam | ❌ Hayır |
Ray tracing | ✅ (A15+) | ❌ |
Metal Pipeline Anatomisi {#pipeline}
swift
1Application → Command Buffer → Command Encoder → GPU2 ↓3 Vertex Shader → Rasterizer → Fragment Shader → FramebufferDevice ve Command Queue {#device}
swift
1import Metal2import MetalKit3 4class MetalRenderer: NSObject {5 let device: MTLDevice6 let commandQueue: MTLCommandQueue7 var pipelineState: MTLRenderPipelineState!8 9 init?(metalView: MTKView) {10 // 1. GPU device11 guard let device = MTLCreateSystemDefaultDevice() else { return nil }12 self.device = device13 metalView.device = device14 15 // 2. Command queue16 guard let queue = device.makeCommandQueue() else { return nil }17 self.commandQueue = queue18 19 super.init()20 setupPipeline(metalView: metalView)21 }22 23 private func setupPipeline(metalView: MTKView) {24 // 3. Shader library25 let library = device.makeDefaultLibrary()!26 let vertexFunction = library.makeFunction(name: "vertex_main")27 let fragmentFunction = library.makeFunction(name: "fragment_main")28 29 // 4. Pipeline descriptor30 let descriptor = MTLRenderPipelineDescriptor()31 descriptor.vertexFunction = vertexFunction32 descriptor.fragmentFunction = fragmentFunction33 descriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat34 35 pipelineState = try! device.makeRenderPipelineState(descriptor: descriptor)36 }37}Render Pipeline: Üçgen Çizelim {#render-pipeline}
swift
1// Vertex data2struct Vertex {3 var position: SIMD3<Float>4 var color: SIMD4<Float>5}6 7let vertices: [Vertex] = [8 Vertex(position: SIMD3(0, 0.5, 0), color: SIMD4(1, 0, 0, 1)), // Üst - kırmızı9 Vertex(position: SIMD3(-0.5, -0.5, 0), color: SIMD4(0, 1, 0, 1)), // Sol alt - yeşil10 Vertex(position: SIMD3(0.5, -0.5, 0), color: SIMD4(0, 0, 1, 1)), // Sağ alt - mavi11]12 13// Render14extension MetalRenderer: MTKViewDelegate {15 func draw(in view: MTKView) {16 guard let drawable = view.currentDrawable,17 let descriptor = view.currentRenderPassDescriptor,18 let commandBuffer = commandQueue.makeCommandBuffer(),19 let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { return }20 21 encoder.setRenderPipelineState(pipelineState)22 encoder.setVertexBytes(vertices, length: MemoryLayout<Vertex>.stride * vertices.count, index: 0)23 encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)24 encoder.endEncoding()25 26 commandBuffer.present(drawable)27 commandBuffer.commit()28 }29 30 func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}31}Vertex ve Fragment Shader'lar {#shaders}
Metal Shading Language (MSL) — C++ tabanlı shader dili:
cpp
1// Shaders.metal2#include 3using namespace metal;4 5struct VertexIn {6 float3 position [[attribute(0)]];7 float4 color [[attribute(1)]];8};9 10struct VertexOut {11 float4 position [[position]];12 float4 color;13};14 15// Vertex shader - her vertex için çalışır16vertex VertexOut vertex_main(17 const device VertexIn* vertices [[buffer(0)]],18 uint vertexID [[vertex_id]]19) {20 VertexOut out;21 out.position = float4(vertices[vertexID].position, 1.0);22 out.color = vertices[vertexID].color;23 return out;24}25 26// Fragment shader - her pixel için çalışır27fragment float4 fragment_main(VertexOut in [[stage_in]]) {28 return in.color; // Interpolated color29}Compute Shader'lar {#compute}
GPU'yu genel amaçlı hesaplama için kullan (GPGPU):
swift
1// Görüntü işleme compute shader2func applyGrayscaleFilter(to texture: MTLTexture) {3 let library = device.makeDefaultLibrary()!4 let function = library.makeFunction(name: "grayscale_kernel")!5 let pipeline = try! device.makeComputePipelineState(function: function)6 7 let commandBuffer = commandQueue.makeCommandBuffer()!8 let encoder = commandBuffer.makeComputeCommandEncoder()!9 10 encoder.setComputePipelineState(pipeline)11 encoder.setTexture(texture, index: 0)12 13 let threadGroupSize = MTLSize(width: 16, height: 16, depth: 1)14 let threadGroups = MTLSize(15 width: (texture.width + 15) / 16,16 height: (texture.height + 15) / 16,17 depth: 118 )19 encoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupSize)20 encoder.endEncoding()21 22 commandBuffer.commit()23 commandBuffer.waitUntilCompleted()24}cpp
1// grayscale_kernel.metal2kernel void grayscale_kernel(3 texture2d texture [[texture(0)]], 4 uint2 gid [[thread_position_in_grid]]5) {6 float4 color = texture.read(gid);7 float gray = dot(color.rgb, float3(0.299, 0.587, 0.114));8 texture.write(float4(gray, gray, gray, color.a), gid);9}Texture Handling {#textures}
swift
1// UIImage → MTLTexture2func makeTexture(from image: UIImage) -> MTLTexture? {3 let loader = MTKTextureLoader(device: device)4 return try? loader.newTexture(cgImage: image.cgImage!, options: [5 .textureUsage: MTLTextureUsage.shaderRead.rawValue,6 .textureStorageMode: MTLStorageMode.shared.rawValue,7 ])8}Metal Performance Shaders {#mps}
Apple'ın optimize edilmiş GPU kernel'leri:
swift
1import MetalPerformanceShaders2 3// Gaussian Blur4let blur = MPSImageGaussianBlur(device: device, sigma: 5.0)5blur.encode(commandBuffer: commandBuffer, sourceTexture: source, destinationTexture: destination)6 7// Image convolution, histogram, morphology...8// Machine learning: MPSCNNConvolution, MPSNNGraph...Metal + SwiftUI {#metal-swiftui}
swift
1// MetalKit view'ı SwiftUI'a sar2struct MetalView: UIViewRepresentable {3 func makeUIView(context: Context) -> MTKView {4 let view = MTKView()5 view.device = MTLCreateSystemDefaultDevice()6 view.delegate = context.coordinator7 return view8 }9 10 func updateUIView(_ uiView: MTKView, context: Context) {}11 12 func makeCoordinator() -> MetalRenderer {13 MetalRenderer()14 }15}16 17// SwiftUI'da kullan18struct ContentView: View {19 var body: some View {20 MetalView()21 .frame(height: 300)22 .clipShape(RoundedRectangle(cornerRadius: 16))23 }24}GPU Debugging {#debugging}
- Xcode GPU Frame Capture: Debug bar'daki kamera ikonuna tıkla
- Metal System Trace: Instruments'ta GPU timeline analizi
- Shader Debugger: Vertex/fragment shader'ları adım adım debug
- GPU Performance Counters: Occupancy, bandwidth, stall'lar
Triple Buffering Pattern {#triple-buffering}
Her frame'de buffer oluşturmak yerine, üç buffer arasında dönüşümlü çalışarak CPU-GPU paralelliğini maksimize et:
swift
1class TripleBufferedRenderer {2 static let maxFramesInFlight = 33 private let inflightSemaphore = DispatchSemaphore(value: maxFramesInFlight)4 private var uniformBuffers: [MTLBuffer] = []5 private var currentBufferIndex = 06 7 init(device: MTLDevice) {8 // 3 ayrı uniform buffer oluştur9 for _ in 0..<Self.maxFramesInFlight {10 let buffer = device.makeBuffer(11 length: MemoryLayout<Uniforms>.stride,12 options: .storageModeShared13 )!14 uniformBuffers.append(buffer)15 }16 }17 18 func draw(in view: MTKView) {19 // Semaphore ile GPU'nun bitirmesini bekle20 inflightSemaphore.wait()21 22 // Mevcut buffer'ı güncelle (CPU yazıyor)23 let buffer = uniformBuffers[currentBufferIndex]24 let uniforms = buffer.contents().bindMemory(to: Uniforms.self, capacity: 1)25 uniforms.pointee.modelMatrix = calculateModelMatrix()26 uniforms.pointee.viewProjection = camera.viewProjectionMatrix27 uniforms.pointee.time = Float(CACurrentMediaTime())28 29 guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }30 31 // GPU bitince semaphore'u serbest bırak32 commandBuffer.addCompletedHandler { [weak self] _ in33 self?.inflightSemaphore.signal()34 }35 36 // Render pass37 guard let descriptor = view.currentRenderPassDescriptor,38 let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { return }39 40 encoder.setRenderPipelineState(pipelineState)41 encoder.setVertexBuffer(buffer, offset: 0, index: 1)42 encoder.drawIndexedPrimitives(43 type: .triangle,44 indexCount: mesh.indexCount,45 indexType: .uint16,46 indexBuffer: mesh.indexBuffer,47 indexBufferOffset: 048 )49 encoder.endEncoding()50 51 commandBuffer.present(view.currentDrawable!)52 commandBuffer.commit()53 54 // Sonraki buffer'a geç55 currentBufferIndex = (currentBufferIndex + 1) % Self.maxFramesInFlight56 }57}58 59struct Uniforms {60 var modelMatrix: float4x461 var viewProjection: float4x462 var time: Float63 var padding: Float = 0 // 16-byte alignment64}Buffer Stratejisi Karşılaştırma
Strateji | CPU-GPU Paralellik | Frame Time | Bellek |
|---|---|---|---|
Single Buffer | Seri (bekleme var) | ~33ms | 1x |
Double Buffer | Kısmi paralel | ~20ms | 2x |
Triple Buffer | Tam paralel | ~12ms | 3x |
Indirect Command Buffers {#indirect-commands}
GPU-driven rendering ile draw call'ları GPU'da oluştur — büyük sahnelerde dramatik performans artışı:
swift
1// Indirect Command Buffer oluştur2func createIndirectCommandBuffer(device: MTLDevice, maxCommands: Int) -> MTLIndirectCommandBuffer {3 let descriptor = MTLIndirectCommandBufferDescriptor()4 descriptor.commandTypes = [.draw, .drawIndexed]5 descriptor.inheritPipelineState = true6 descriptor.inheritBuffers = false7 descriptor.maxVertexBufferBindCount = 38 descriptor.maxFragmentBufferBindCount = 29 10 return device.makeIndirectCommandBuffer(11 descriptor: descriptor,12 maxCommandCount: maxCommands,13 options: .storageModeShared14 )!15}16 17// Compute shader ile draw komutlarını GPU'da üret18// Bu, CPU'da binlerce draw call göndermekten çok daha hızlıcpp
1// IndirectCommands.metal - GPU'da frustum culling + draw call2kernel void encode_draw_commands(3 device DrawCommand* drawCommands [[buffer(0)]],4 device MeshData* meshes [[buffer(1)]],5 constant CameraData& camera [[buffer(2)]],6 device command_buffer icb [[buffer(3)]],7 uint tid [[thread_position_in_grid]]8) {9 MeshData mesh = meshes[tid];10 11 // GPU'da frustum culling yap12 if (is_visible(mesh.boundingSphere, camera.frustumPlanes)) {13 // Görünür mesh için draw komutu ekle14 render_command cmd(icb, tid);15 cmd.set_vertex_buffer(meshes, 1);16 cmd.draw_indexed_primitives(17 primitive_type::triangle,18 mesh.indexCount,19 mesh.indexBuffer,20 mesh.indexBufferOffset21 );22 }23}GPU-Driven Rendering Kazanımları
Sahne Karmaşıklığı | CPU Draw Calls | GPU-Driven | Kazanım |
|---|---|---|---|
1.000 mesh | 2.1ms | 0.3ms | 7x |
10.000 mesh | 18ms | 0.8ms | 22x |
100.000 mesh | CPU bottleneck | 2.1ms | 50x+ |
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var: **Kaynaklar:** - [Apple: Metal Documentation](https://developer.apple.com/metal/) - [Metal Best Practices Guide](https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/) - [WWDC23: Discover Metal for immersive apps](https://developer.apple.com/videos/play/wwdc2023/10089/)

