Su un Mac con Apple silicon, disegna in un MTLBuffer condiviso tramite un CGContext bitmap, quindi crea una texture Metal basata sullo stesso buffer. I requisiti essenziali sono byte RGBA corrispondenti, stride di riga allineati, memoria mantenuta in vita e accessi CPU/GPU ordinati.
Questa guida crea un rettangolo rosso e un ovale blu sintetici, quindi verifica che Metal possa leggere il disegno tramite la texture. È pensata per uno sviluppatore Mac che prepara grafica generata per il renderer Metal di un’app desktop. Il meccanismo di condivisione è documentato dall’API Apple makeTexture(descriptor:offset:bytesPerRow:).
Quale percorso di condivisione scegliere?
Alloca prima la memoria con Metal. Ottieni il suo indirizzo CPU con buffer.contents(), usa quell’indirizzo come destinazione del contesto bitmap e crea la texture dal buffer. Apple documenta che il disegno bitmap aggiorna la memoria fornita e che una texture basata su un buffer ne condivide l’allocazione. Insieme, queste API forniscono il collegamento senza un caricamento intermedio dell’immagine. Contesti grafici, MTLBuffer.contents(), MTLTexture.
L’esempio richiede esplicitamente una GPU della famiglia Apple. La documentazione Apple attuale sullo storage afferma che le texture condivise non sono disponibili sulle GPU non appartenenti alla famiglia Apple. Considera il supporto della GPU un prerequisito, invece di presumere che ogni Mac compatibile con Metal supporti questa specifica strategia di allocazione. MTLStorageMode.shared.
| Requisito | Approccio appropriato |
|---|---|
| Core Graphics disegna; Metal legge la stessa allocazione su Apple silicon | Buffer condiviso più texture basata sul buffer, come nell’esempio seguente |
| Una texture allocata separatamente deve ricevere il disegno | Carica con replace(region:mipmapLevel:withBytes:bytesPerRow:) in una texture supportata accessibile dalla CPU |
| L’uso ripetuto da parte della GPU trae vantaggio da una texture privata separata | Copia da una risorsa di staging con un blit encoder |
| La GPU selezionata non può creare texture condivise | Usa un percorso di caricamento o copia di staging supportato invece di questo esempio con texture condivisa |
Queste alternative hanno comportamenti diversi per proprietà e copia. Apple documenta replace come una copia immediata eseguita dalla CPU, mentre i blit encoder spostano dati tra risorse, incluse quelle accessibili dalla CPU e quelle private. Il percorso di condivisione diretta evita quella copia di caricamento, ma Apple avverte che le texture basate su buffer potrebbero beneficiare di un numero inferiore di ottimizzazioni per le texture. Sostituzione dei pixel di una texture, MTLBlitCommandEncoder, Limitazioni delle texture basate su buffer.
Definire i requisiti di pixel, dimensioni e proprietà
Usa un layout di byte esplicito: quattro byte per pixel, in ordine di indirizzo crescente R, G, B, A. Per Core Graphics, combina premultipliedLast con byteOrder32Big; per Metal, usa rgba8Unorm. L’impostazione dell’ordine dei byte rende esplicita l’interpretazione della memoria, indipendentemente da come verrebbe rappresentato un intero nativo. Apple documenta separatamente la posizione dell’alfa, i controlli sull’ordine dei byte e i quattro componenti RGBA normalizzati di Metal. CGImageAlphaInfo.premultipliedLast, CGBitmapInfo, MTLPixelFormat.rgba8Unorm.
Con l’alfa premoltiplicato, i canali di colore memorizzati includono già l’alfa. A titolo di calcolo illustrativo, il rosso pieno semitrasparente su memoria trasparente produce circa 128, 0, 0, 128, tenendo conto della quantizzazione. Uno shader a valle deve preservare questa interpretazione: moltiplicare di nuovo per l’alfa i valori RGB campionati li scurirebbe. L’esempio usa forme opache per semplificare il controllo dei byte. Immagini bitmap e maschere immagine.
L’esempio sceglie CGColorSpace.linearSRGB, definito da Apple come colorimetria sRGB con una funzione di trasferimento lineare. Questo stabilisce esplicitamente l’interpretazione del colore del disegno; la condivisione della memoria non esegue di per sé alcuna conversione dello spazio colore. Mantieni coerente questa interpretazione quando integri la texture in un renderer. CGColorSpace.linearSRGB.
Calcolare lo stride di riga prima dell’allocazione
La lunghezza visibile della riga è width × 4, ma lo stride allocato può essere maggiore. Interroga minimumLinearTextureAlignment(for:) per il dispositivo e il formato selezionati, quindi arrotonda per eccesso la lunghezza visibile della riga a un multiplo di tale allineamento. Fornisci a Core Graphics e Metal lo stesso bytesPerRow risultante. minimumLinearTextureAlignment(for:).
Per un allineamento del dispositivo di 256 byte, usato esplicitamente a titolo illustrativo, un’immagine di 257 × 128 richiede 1,028 byte visibili per riga, uno stride allineato di 1,280 byte e 163,840 byte allocati. Gli ulteriori 252 byte per riga sono padding, non pixel aggiuntivi. Il programma interroga l’allineamento effettivo invece di presumere questo valore illustrativo.
Alloca almeno bytesPerRow × height byte. Per un pixel alla colonna di memoria x e alla riga y, l’offset in byte è y × bytesPerRow + x × 4. Quando adatti l’esempio, che usa dimensioni fisse, a dimensioni variabili, rifiuta i valori non positivi e controlla che moltiplicazioni e arrotondamenti non causino overflow interi prima dell’allocazione. Core Graphics documenta il requisito di memoria pari allo stride completo moltiplicato per l’altezza. Creazione di un contesto grafico bitmap.
Mantenere viva l’allocazione per tutta la durata di ogni utilizzo
In questo progetto, la memoria viene allocata da Metal. Il contesto bitmap ne prende in prestito l’indirizzo; mantieni vivo il buffer per tutta la durata del contesto. Per il lavoro GPU asincrono, mantieni inoltre le risorse finché tale lavoro non è terminato. Non liberare buffer.contents() manualmente e non sostituirlo con un puntatore ottenuto durante l’accesso temporaneo a un array Swift.
Il programma termina il disegno e rilascia il contesto prima di inviare il lavoro alla GPU, quindi mantiene disponibili buffer e texture fino al completamento. Questo segue il modello della memoria bitmap fornita dal chiamante e i requisiti documentati da Metal per l’ordinamento degli accessi CPU/GPU. Contesti grafici, Sincronizzazione del lavoro di CPU e GPU.
Creare un esempio completo di disegno sintetico
Crea un progetto macOS da riga di comando oppure salva il programma completo seguente come main.swift. Usa un SDK macOS di Xcode aggiornato: il codice usa l’inizializzatore CGContext attuale, il cui argomento bitmapInfo è un CGBitmapInfo. Gli SDK meno recenti potrebbero esporre invece l’inizializzatore legacy, che si aspetta bitmapInfo.rawValue. Inizializzatore bitmap di CGContext.
Il disegno combina chiamate dirette a Core Graphics con un ovale AppKit. NSGraphicsContext(cgContext:flipped:) avvolge il contesto esistente, così entrambi disegnano sulla stessa destinazione. Salvare e ripristinare lo stato grafico di AppKit ripristina il contesto corrente precedente al termine del disegno. Inizializzatore NSGraphicsContext, 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")
}
Compilalo ed eseguilo sul Mac di destinazione:
xcrun swiftc main.swift -o SharedRGBA -framework AppKit -framework CoreGraphics -framework Metal
./SharedRGBA
La proprietà buffer della texture controlla la risorsa che la supporta. La diagnostica copia poi i pixel della texture in un buffer condiviso separato e controlla un angolo rosso non modificato dopo il completamento della GPU. Questa lettura di ritorno è una copia di test intenzionale, distinta dall’allocazione condivisa del disegno. Apple documenta l’operazione di copia da texture a buffer e specifica destinationBytesPerImage: 0 per una copia 2D. MTLTexture.buffer, Copia da texture a buffer.
I calcoli dello stride sono stati verificati con valori di input di esempio deterministici. Il programma Swift è stato controllato rispetto alle API reperite, ma non è stato possibile compilarlo o eseguirlo nell’ambiente di ricerca, che usa Linux e non dispone di Swift o dei framework Apple.
Coordinare il disegno della CPU e l’uso della GPU
Anche la memoria condivisa richiede una pianificazione degli accessi. Termina il disegno della CPU prima di confermare i comandi che leggono la texture. Dopo l’invio, non modificare quell’allocazione finché la GPU non ha terminato di usarla. Apple richiede esplicitamente questo coordinamento per le risorse condivise. MTLStorageMode.shared.
Nell’esempio eseguito una sola volta, waitUntilCompleted() rende facile verificare il passaggio di consegne: blocca l’esecuzione finché il command buffer e i relativi gestori di completamento non hanno terminato. In un renderer interattivo, evita di inserire questa attesa bloccante in ogni frame. Mantieni invece più coppie di buffer e texture e rimetti ogni coppia nel pool delle risorse disponibili solo dopo la notifica di completamento della GPU. Apple illustra questo modello di pool di risorse nel proprio esempio sulla sincronizzazione. waitUntilCompleted(), Sincronizzazione del lavoro di CPU e GPU.
Se in seguito fai scrivere la GPU tramite un alias e leggere tramite un altro, ricontrolla la sincronizzazione. Apple osserva che i dati delle texture basate su buffer possono essere coerenti tra passaggi di rendering senza riflettere immediatamente le modifiche agli alias all’interno di un singolo passaggio. L’esempio evita questa complicazione completando il disegno della CPU prima dell’unica operazione di lettura della GPU. Coerenza delle texture basate su buffer.
Diagnosticare i problemi di layout prima di modificare il renderer
Segui questa sequenza per isolare il requisito che non è stato rispettato. Questi controlli derivano dalle regole documentate sopra per layout dei pixel, risorsa di supporto, stride e sincronizzazione.
- La creazione della texture non riesce: verifica che la GPU supporti le texture condivise, che entrambe le risorse usino
.sharede che offset e stride rispettino l’allineamento interrogato. Mantieni la texture 2D, con un solo campione, un livello mipmap e un elemento dell’array. - Il rosso appare blu: controlla i singoli byte. L’esempio richiede insieme l’ordine dei byte RGBA,
premultipliedLast,byteOrder32Bige.rgba8Unorm. - Le righe appaiono inclinate: confronta gli stride del contesto e della texture. Entrambi devono usare
rowBytes, padding incluso. - L’immagine appare capovolta: controlla la trasformazione di disegno e la mappatura delle coordinate della texture nel renderer. L’esempio usa un contesto di disegno non capovolto. L’argomento
flippeddi AppKit descrive il suo stato iniziale di capovolgimento; non riordina le righe memorizzate. - Gli aggiornamenti appaiono incoerenti: controlla se il disegno della CPU si sovrappone all’accesso della GPU alla stessa allocazione. Tieni traccia del completamento prima di riutilizzarla.
I vincoli di creazione derivano dalla documentazione Apple sulle texture lineari; la distinzione sullo stato di capovolgimento deriva dall’inizializzatore AppKit. Mantieni esplicita la gestione dell’orientamento quando aggiungi il tuo shader di visualizzazione. Requisiti delle texture lineari, Parametro dello stato di capovolgimento di AppKit.
Quando buffer di supporto, layout dei byte, stride con padding e pianificazione degli accessi sono coerenti, il disegno sintetico è disponibile a Metal tramite la texture condivisa. Mantieni questi requisiti quando ridimensioni l’immagine o aggiungi altre operazioni di disegno; scegli una copia di staging quando la GPU di destinazione o i requisiti della texture a valle richiedono una memoria separata.
Fonti
MTLTexture — Apple
MTLStorageMode.shared — Apple
contents() — Apple
Contesti grafici — Apple
CGBitmapInfo — Apple
MTLPixelFormat.rgba8Unorm — Apple
linearSRGB — Apple
init(data:width:height:bitsPerComponent:bytesPerRow:space:bitmapInfo:) — Apple
init(cgContext:flipped:) — Apple
saveGraphicsState() — Apple
restoreGraphicsState() — Apple
buffer — Apple
waitUntilCompleted() — Apple
MTLBlitCommandEncoder — Apple