Apple silicon bir Mac’te, bitmap CGContext aracılığıyla paylaşılan bir MTLBuffer’a çizim yapın ve ardından aynı arabellek tarafından desteklenen bir Metal dokusu oluşturun. Temel koşullar; eşleşen RGBA baytları, hizalanmış satır adımları, belleğin yaşam süresinin korunması ve sıralı CPU/GPU erişimidir.

Bu kılavuzda yapay bir kırmızı dikdörtgen ve mavi oval çiziliyor, ardından Metal’in dokudaki çizimi okuyabildiği denetleniyor. Masaüstü uygulamasının Metal işleyicisi için oluşturulmuş grafikler hazırlayan Mac geliştiricilerine yöneliktir. Paylaşım mekanizması Apple’ın makeTexture(descriptor:offset:bytesPerRow:) API’sinde belgelenmiştir.

Hangi paylaşım yolunu seçmelisiniz?

Belleği önce Metal ile ayırın. buffer.contents() ile CPU adresini alın, bitmap bağlamının hedefi olarak bu adresi kullanın ve dokuyu arabellekten oluşturun. Apple, bitmap çiziminin sağlanan belleği güncellediğini ve arabellek destekli bir dokunun arabelleğin bellek tahsisini paylaştığını belgeliyor. Bu API’ler birlikte, ara bir görüntü yüklemesine gerek kalmadan köprü sağlar. Grafik Bağlamları, MTLBuffer.contents(), MTLTexture.

Örnek, Apple ailesinden bir GPU gerektirir. Apple’ın güncel depolama belgelerine göre paylaşılan dokular Apple ailesinden olmayan GPU’larda kullanılamaz. Metal’i destekleyen her Mac’in bu özel bellek tahsis stratejisini desteklediğini varsaymak yerine GPU desteğini ön koşul olarak ele alın. MTLStorageMode.shared.

Gereksiniminiz Uygun yaklaşım
Core Graphics çizim yapıyor; Metal, Apple silicon üzerinde aynı bellek tahsisini okuyor Aşağıdaki gibi paylaşılan arabellek ve arabellek destekli doku
Ayrı olarak tahsis edilmiş bir dokuya çizim aktarılmalı Desteklenen, CPU erişimli bir dokuya replace(region:mipmapLevel:withBytes:bytesPerRow:) ile yükleyin
GPU’da tekrarlanan kullanım, ayrı bir özel dokudan yararlanıyor Blit kodlayıcısıyla bir hazırlama kaynağından kopyalayın
Seçilen GPU paylaşılan dokular oluşturamıyor Bu paylaşılan doku örneği yerine desteklenen bir yükleme veya hazırlama kopyası yolu kullanın

Bu alternatiflerin sahiplik ve kopyalama davranışları farklıdır. Apple, replace işlemini anında yapılan bir CPU kopyası olarak belgeliyor; blit kodlayıcıları ise CPU erişimli ve özel depolama dâhil kaynaklar arasında veri taşır. Doğrudan paylaşım yolu bu yükleme kopyasını önler, ancak Apple arabellek destekli dokuların daha az doku optimizasyonundan yararlanabileceği konusunda uyarıyor. Doku piksellerini değiştirme, MTLBlitCommandEncoder, Arabellek destekli doku kısıtlamaları.

Piksel, boyut ve sahiplik koşullarını tanımlayın

Açık bir bayt düzeni kullanın: artan bellek adresi sırasına göre piksel başına dört bayt R, G, B, A. Core Graphics için premultipliedLast ile byteOrder32Big öğelerini birleştirin; Metal için rgba8Unorm kullanın. Bayt sırası ayarı, yerel bir tamsayının nasıl temsil edileceğinden bağımsız olarak belleğin yorumlanma biçimini açık hale getirir. Apple; alfa konumunu, bayt sırası denetimlerini ve Metal’in dört normalize RGBA bileşenini ayrı ayrı belgeliyor. CGImageAlphaInfo.premultipliedLast, CGBitmapInfo, MTLPixelFormat.rgba8Unorm.

Premultiplied alfa, depolanan renk kanallarına alfanın önceden dâhil edilmiş olduğu anlamına gelir. Örnek bir hesaplamada, saydam depolama üzerine çizilen yarı saydam, tam kırmızı yaklaşık olarak 128, 0, 0, 128 değerindedir; nicemleme farkları olabilir. Sonraki aşamadaki bir gölgelendirici bu yorumu korumalıdır; örneklenen RGB değerlerini alfa ile tekrar çarpmak görüntüyü koyulaştırır. Bayt denetimini kolaylaştırmak için örnekte opak şekiller kullanılıyor. Bitmap Görüntüler ve Görüntü Maskeleri.

Örnek, Apple’ın doğrusal aktarım işlevli sRGB kolorimetrisi olarak tanımladığı CGColorSpace.linearSRGB renk uzayını seçiyor. Bu, çizimin renk yorumunu açıkça belirler; belleği paylaşmak kendi başına renk uzayı dönüşümü yapmaz. Dokuyu bir işleyiciye entegre ederken bu yorumu tutarlı biçimde koruyun. CGColorSpace.linearSRGB.

Ayırmadan önce satır adımını hesaplayın

Görünen satır uzunluğu width × 4 şeklindedir, ancak ayrılan satır adımı daha büyük olabilir. Seçili aygıt ve biçim için minimumLinearTextureAlignment(for:) değerini sorgulayın, ardından görünen satır uzunluğunu bu hizalamanın katına yukarı yuvarlayın. Core Graphics’e ve Metal’e aynı bytesPerRow değerini verin. minimumLinearTextureAlignment(for:).

Yalnızca açıklama amacı taşıyan 256 baytlık aygıt hizalaması için 257 × 128 boyutundaki bir görüntünün satır başına görünen 1,028 bayta, 1,280 baytlık hizalanmış bir satır adımına ve toplam 163,840 baytlık tahsise ihtiyacı vardır. Satır başına ek 252 bayt, dolgu alanıdır; ek piksel değildir. Program bu açıklayıcı değeri varsaymak yerine gerçek hizalamayı sorgular.

En az bytesPerRow × height bayt ayırın. Bellekte x sütununda ve y satırında bulunan bir pikselin bayt konumu y × bytesPerRow + x × 4 şeklindedir. Sabit örneği değişken boyutlara uyarlarken pozitif olmayan boyutları reddedin ve ayırmadan önce çarpma ile yuvarlama işlemlerinde tamsayı taşması olup olmadığını denetleyin. Core Graphics, satır adımı ile yükseklik çarpımı kadar depolama alanı gerektiğini belgeliyor. Bitmap Grafik Bağlamı Oluşturma.

Bellek tahsisini tüm kullanıcılar bitirene kadar yaşatın

Bu tasarımda belleği Metal ayırır. Bitmap bağlamı bu belleğin adresini ödünç alır; arabelleği bağlamın tüm yaşam süresi boyunca canlı tutun. Eşzamansız GPU işleri için kaynakları bu işler tamamlanana kadar tutun. buffer.contents() belleğini kendiniz serbest bırakmayın ve geçici bir Swift dizisine erişim sırasında alınmış bir işaretçiyi onun yerine kullanmayın.

Program, GPU işini göndermeden önce çizim bağlamını tamamlar ve serbest bırakır; ardından arabellekleri ve dokuyu işlem tamamlanana kadar kullanılabilir durumda tutar. Bu, çağıranın sağladığı bitmap belleği modelini ve Metal’in belgelenmiş CPU/GPU sıralama gereksinimlerini izler. Grafik Bağlamları, CPU ve GPU işini eşzamanlama.

Eksiksiz bir yapay çizim örneği oluşturun

Bir macOS komut satırı projesi oluşturun veya aşağıdaki programın tamamını main.swift adıyla kaydedin. Güncel bir Xcode macOS SDK’sı kullanın: kodda, bitmapInfo parametresi CGBitmapInfo olan güncel CGContext başlatıcısı kullanılıyor. Eski SDK’larda bunun yerine bitmapInfo.rawValue bekleyen eski başlatıcı bulunabilir. CGContext bitmap başlatıcısı.

Çizimde doğrudan Core Graphics çağrılarıyla bir AppKit ovali birleştiriliyor. NSGraphicsContext(cgContext:flipped:) mevcut bağlamı sarmalar; böylece her ikisi de aynı hedefe çizim yapar. AppKit’in grafik durumunu kaydedip geri yüklemek, çizimden sonra önceki geçerli bağlamı yeniden etkinleştirir. NSGraphicsContext başlatıcısı, saveGraphicsState(), restoreGraphicsState().

import AppKit
import CoreGraphics
import Metal

// Fixed, small dimensions keep all size arithmetic bounded.
let width = 257
let height = 128
let format: MTLPixelFormat = .rgba8Unorm

guard let device = MTLCreateSystemDefaultDevice(),
      device.supportsFamily(.apple1) else {
    fatalError("This example requires an Apple-family GPU.")
}
let alignment = device.minimumLinearTextureAlignment(for: format)
guard alignment > 0 else { fatalError("Invalid alignment.") }
let rowBytes = ((width * 4 + alignment - 1) / alignment) * alignment
let byteCount = rowBytes * height

guard let buffer = device.makeBuffer(
    length: byteCount, options: .storageModeShared
) else { fatalError("Drawing buffer allocation failed.") }

let descriptor = MTLTextureDescriptor.texture2DDescriptor(
    pixelFormat: format, width: width, height: height, mipmapped: false
)
descriptor.storageMode = .shared
descriptor.usage = .shaderRead

guard let texture = buffer.makeTexture(
    descriptor: descriptor, offset: 0, bytesPerRow: rowBytes
) else { fatalError("Buffer-backed texture creation failed.") }
precondition(texture.buffer === buffer)

// This scope finishes CPU drawing before GPU submission.
do {
    guard let space = CGColorSpace(name: CGColorSpace.linearSRGB) else {
        fatalError("Color space creation failed.")
    }
    let info = CGBitmapInfo(
        rawValue: CGImageAlphaInfo.premultipliedLast.rawValue
    ).union(.byteOrder32Big)
    guard let cg = CGContext(
        data: buffer.contents(), width: width, height: height,
        bitsPerComponent: 8, bytesPerRow: rowBytes,
        space: space, bitmapInfo: info
    ) else { fatalError("Bitmap context creation failed.") }

    cg.setFillColor(red: 1, green: 0, blue: 0, alpha: 1)
    cg.fill(CGRect(x: 0, y: 0,
                   width: CGFloat(width), height: CGFloat(height)))

    NSGraphicsContext.saveGraphicsState()
    defer { NSGraphicsContext.restoreGraphicsState() }
    NSGraphicsContext.current = NSGraphicsContext(
        cgContext: cg, flipped: false
    )
    NSColor.blue.setFill()
    NSBezierPath(ovalIn: NSRect(x: 32, y: 24, width: 48, height: 48)).fill()
}

// Diagnostic copy: prove the GPU can read through the texture.
guard let readback = device.makeBuffer(
    length: byteCount, options: .storageModeShared
), let queue = device.makeCommandQueue(),
   let command = queue.makeCommandBuffer(),
   let blit = command.makeBlitCommandEncoder() else {
    fatalError("Readback setup failed.")
}
blit.copy(
    from: texture, sourceSlice: 0, sourceLevel: 0,
    sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
    sourceSize: MTLSize(width: width, height: height, depth: 1),
    to: readback, destinationOffset: 0,
    destinationBytesPerRow: rowBytes, destinationBytesPerImage: 0
)
blit.endEncoding()
command.commit()
command.waitUntilCompleted()
guard command.status == .completed else {
    fatalError("GPU copy failed: \(String(describing: command.error))")
}
let pixel = readback.contents().assumingMemoryBound(to: UInt8.self)
precondition(pixel[0] == 255 && pixel[1] == 0 &&
             pixel[2] == 0 && pixel[3] == 255)
withExtendedLifetime(buffer) {
    print("Shared RGBA texture verified; row stride: \(rowBytes) bytes")
}

Hedef Mac’te derleyip çalıştırın:

xcrun swiftc main.swift -o SharedRGBA -framework AppKit -framework CoreGraphics -framework Metal
./SharedRGBA

Dokunun buffer özelliği, dokunun dayandığı kaynağı denetler. Tanılama kodu daha sonra doku piksellerini ayrı bir paylaşılan arabelleğe kopyalar ve GPU işlemi tamamlandıktan sonra dokunulmamış kırmızı bir köşeyi denetler. Bu geri okuma, bilerek yapılan bir test kopyasıdır; paylaşılan çizim belleğinden ayrıdır. Apple, dokudan arabelleğe kopyalama işlemini belgeliyor ve 2B kopyalama için destinationBytesPerImage: 0 belirtilmesini şart koşuyor. MTLTexture.buffer, Dokudan arabelleğe kopyalama.

Satır adımı hesaplamaları, belirlenimci örnek girdilerle denetlendi. Swift programı, alınan API belgelerine göre incelendi; ancak araştırma ortamı Swift veya Apple çerçeveleri içermeyen Linux olduğundan derlenip çalıştırılamadı.

CPU çizimini ve GPU kullanımını eşgüdümleyin

Paylaşılan bellek yine de bir erişim sırası gerektirir. Dokuyu okuyacak komutları göndermeden önce CPU çizimini tamamlayın. Gönderimden sonra GPU bu belleği kullanmayı bitirene kadar onu değiştirmeyin. Apple, paylaşılan kaynaklar için bu eşgüdümün gerekli olduğunu açıkça belirtiyor. MTLStorageMode.shared.

Tek seferlik örnekte waitUntilCompleted(), komut arabelleği ve tamamlama işleyicileri bitene kadar bekleyerek aktarımın izlenmesini kolaylaştırır. Etkileşimli bir işleyicide bu engelleyici beklemeyi her karede yapmaktan kaçının. Bunun yerine birden fazla arabellek ve doku çifti bulundurun, her çifti yalnızca GPU tamamlama bildirimi geldikten sonra kullanılabilir havuza geri koyun. Apple, bu kaynak havuzu düzenini eşzamanlama örneğinde gösteriyor. waitUntilCompleted(), CPU ve GPU işini eşzamanlama.

Daha sonra GPU’nun bir takma ad üzerinden yazıp başka bir takma ad üzerinden okumasını sağlarsanız eşzamanlamayı yeniden gözden geçirin. Apple, arabellek destekli doku verilerinin çizim geçişleri arasında tutarlı olabileceğini, ancak tek bir geçiş içindeki takma ad değişikliklerinin hemen yansımayabileceğini belirtiyor. Örnek, GPU’nun tek okuma işleminden önce CPU çizimini tamamlayarak bu karmaşıklıktan kaçınıyor. Arabellek destekli doku tutarlılığı.

İşleyiciyi değiştirmeden önce düzen sorunlarını tanılayın

Başarısız olan koşulu yalıtmak için şu sırayı izleyin. Bu denetimler yukarıda açıklanan piksel düzeni, destek kaynak, satır adımı ve eşzamanlama kurallarına dayanır.

  1. Doku oluşturma başarısız oluyor: GPU’nun paylaşılan dokuları desteklediğini, her iki kaynağın da .shared kullandığını ve ofset ile satır adımının sorgulanan hizalamayı karşıladığını doğrulayın. Dokuyu 2B, tek örneklemeli, tek mip düzeyli ve tek dizi öğeli tutun.
  2. Kırmızı mavi görünüyor: Tek tek baytları inceleyin. Örnek, RGBA bayt sırasının yanı sıra premultipliedLast, byteOrder32Big ve .rgba8Unorm değerlerinin birlikte kullanılmasını gerektiriyor.
  3. Satırlar kaymış görünüyor: Bağlamın ve dokunun satır adımlarını karşılaştırın. Dolgu alanı dâhil ikisi de rowBytes kullanmalıdır.
  4. Görüntü ters görünüyor: Çizim dönüşümünü ve işleyicinin doku koordinatı eşlemesini denetleyin. Örnek, çevrilmemiş bir çizim bağlamı kullanıyor. AppKit’in flipped bağımsız değişkeni ilk çevrilmiş durumunu belirtir; depolanan satırları yeniden düzenlemez.
  5. Güncellemeler tutarsız görünüyor: CPU çiziminin aynı bellek tahsisine GPU erişimiyle çakışıp çakışmadığını denetleyin. Belleği yeniden kullanmadan önce tamamlanma takibi kullanın.

Oluşturma kısıtları Apple’ın doğrusal doku belgelerinden, çevrilmiş durum ayrımı ise AppKit başlatıcısından gelir. Kendi görüntüleme gölgelendiricinizi eklerken yön bilgisini açıkça ele alın. Doğrusal doku gereksinimleri, AppKit çevrilmiş durum parametresi.

Destek arabelleği, bayt düzeni, dolgulu satır adımı ve erişim sırası uyumlu olduğunda yapay çizim paylaşılan doku üzerinden Metal’e sunulur. Görüntüyü yeniden boyutlandırırken veya yeni çizim işlemleri eklerken bu koşulları koruyun; hedef GPU ya da sonraki doku gereksinimleri ayrı depolama gerektiriyorsa hazırlama kopyası kullanın.

Kaynaklar