Tüm Yazılar
KategoriiOS
Okuma Süresi
23 dk
Yayın Tarihi
...
Kelime Sayısı
1.645kelime

Kahveni hazırla - bu içerikli bir makale!

Metal framework ile GPU programlama, render pipeline, shader'lar, compute kernel'ler, texture handling ve Metal + SwiftUI entegrasyonu.

Metal Framework: iOS'ta GPU Programming ve Grafik İşleme

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

  1. Metal Nedir ve Neden?
  2. Metal Pipeline Anatomisi
  3. Device ve Command Queue
  4. Render Pipeline: Üçgen Çizelim
  5. Vertex ve Fragment Shader'lar
  6. Compute Shader'lar
  7. Texture Handling
  8. Metal Performance Shaders
  9. Metal + SwiftUI
  10. 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 → GPU
2
3 Vertex Shader → Rasterizer → Fragment Shader → Framebuffer

Device ve Command Queue {#device}

swift
1import Metal
2import MetalKit
3 
4class MetalRenderer: NSObject {
5 let device: MTLDevice
6 let commandQueue: MTLCommandQueue
7 var pipelineState: MTLRenderPipelineState!
8 
9 init?(metalView: MTKView) {
10 // 1. GPU device
11 guard let device = MTLCreateSystemDefaultDevice() else { return nil }
12 self.device = device
13 metalView.device = device
14 
15 // 2. Command queue
16 guard let queue = device.makeCommandQueue() else { return nil }
17 self.commandQueue = queue
18 
19 super.init()
20 setupPipeline(metalView: metalView)
21 }
22 
23 private func setupPipeline(metalView: MTKView) {
24 // 3. Shader library
25 let library = device.makeDefaultLibrary()!
26 let vertexFunction = library.makeFunction(name: "vertex_main")
27 let fragmentFunction = library.makeFunction(name: "fragment_main")
28 
29 // 4. Pipeline descriptor
30 let descriptor = MTLRenderPipelineDescriptor()
31 descriptor.vertexFunction = vertexFunction
32 descriptor.fragmentFunction = fragmentFunction
33 descriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
34 
35 pipelineState = try! device.makeRenderPipelineState(descriptor: descriptor)
36 }
37}

Render Pipeline: Üçgen Çizelim {#render-pipeline}

swift
1// Vertex data
2struct 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şil
10 Vertex(position: SIMD3(0.5, -0.5, 0), color: SIMD4(0, 0, 1, 1)), // Sağ alt - mavi
11]
12 
13// Render
14extension 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.metal
2#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ışır
16vertex 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ışır
27fragment float4 fragment_main(VertexOut in [[stage_in]]) {
28 return in.color; // Interpolated color
29}

Compute Shader'lar {#compute}

GPU'yu genel amaçlı hesaplama için kullan (GPGPU):

swift
1// Görüntü işleme compute shader
2func 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: 1
18 )
19 encoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupSize)
20 encoder.endEncoding()
21 
22 commandBuffer.commit()
23 commandBuffer.waitUntilCompleted()
24}
cpp
1// grayscale_kernel.metal
2kernel 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 → MTLTexture
2func 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 MetalPerformanceShaders
2 
3// Gaussian Blur
4let 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 sar
2struct MetalView: UIViewRepresentable {
3 func makeUIView(context: Context) -> MTKView {
4 let view = MTKView()
5 view.device = MTLCreateSystemDefaultDevice()
6 view.delegate = context.coordinator
7 return view
8 }
9 
10 func updateUIView(_ uiView: MTKView, context: Context) {}
11 
12 func makeCoordinator() -> MetalRenderer {
13 MetalRenderer()
14 }
15}
16 
17// SwiftUI'da kullan
18struct ContentView: View {
19 var body: some View {
20 MetalView()
21 .frame(height: 300)
22 .clipShape(RoundedRectangle(cornerRadius: 16))
23 }
24}

GPU Debugging {#debugging}

  1. Xcode GPU Frame Capture: Debug bar'daki kamera ikonuna tıkla
  2. Metal System Trace: Instruments'ta GPU timeline analizi
  3. Shader Debugger: Vertex/fragment shader'ları adım adım debug
  4. 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 = 3
3 private let inflightSemaphore = DispatchSemaphore(value: maxFramesInFlight)
4 private var uniformBuffers: [MTLBuffer] = []
5 private var currentBufferIndex = 0
6 
7 init(device: MTLDevice) {
8 // 3 ayrı uniform buffer oluştur
9 for _ in 0..<Self.maxFramesInFlight {
10 let buffer = device.makeBuffer(
11 length: MemoryLayout<Uniforms>.stride,
12 options: .storageModeShared
13 )!
14 uniformBuffers.append(buffer)
15 }
16 }
17 
18 func draw(in view: MTKView) {
19 // Semaphore ile GPU'nun bitirmesini bekle
20 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.viewProjectionMatrix
27 uniforms.pointee.time = Float(CACurrentMediaTime())
28 
29 guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }
30 
31 // GPU bitince semaphore'u serbest bırak
32 commandBuffer.addCompletedHandler { [weak self] _ in
33 self?.inflightSemaphore.signal()
34 }
35 
36 // Render pass
37 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: 0
48 )
49 encoder.endEncoding()
50 
51 commandBuffer.present(view.currentDrawable!)
52 commandBuffer.commit()
53 
54 // Sonraki buffer'a geç
55 currentBufferIndex = (currentBufferIndex + 1) % Self.maxFramesInFlight
56 }
57}
58 
59struct Uniforms {
60 var modelMatrix: float4x4
61 var viewProjection: float4x4
62 var time: Float
63 var padding: Float = 0 // 16-byte alignment
64}

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ştur
2func createIndirectCommandBuffer(device: MTLDevice, maxCommands: Int) -> MTLIndirectCommandBuffer {
3 let descriptor = MTLIndirectCommandBufferDescriptor()
4 descriptor.commandTypes = [.draw, .drawIndexed]
5 descriptor.inheritPipelineState = true
6 descriptor.inheritBuffers = false
7 descriptor.maxVertexBufferBindCount = 3
8 descriptor.maxFragmentBufferBindCount = 2
9 
10 return device.makeIndirectCommandBuffer(
11 descriptor: descriptor,
12 maxCommandCount: maxCommands,
13 options: .storageModeShared
14 )!
15}
16 
17// Compute shader ile draw komutlarını GPU'da üret
18// Bu, CPU'da binlerce draw call göndermekten çok daha hızlı
cpp
1// IndirectCommands.metal - GPU'da frustum culling + draw call
2kernel 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 yap
12 if (is_visible(mesh.boundingSphere, camera.frustumPlanes)) {
13 // Görünür mesh için draw komutu ekle
14 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.indexBufferOffset
21 );
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/)

Etiketler

#metal#gpu#graphics#shaders#compute#ios#performance
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz