Teken op een Mac met Apple silicon via een bitmap-CGContext in een gedeelde MTLBuffer en maak vervolgens een Metal-textuur die door diezelfde buffer wordt ondersteund. De essentiële afspraken zijn overeenkomende RGBA-bytes, uitgelijnde rijafstanden, behouden opslag en geordende CPU/GPU-toegang.

Deze gids maakt een synthetische rode rechthoek en blauwe ovaal en controleert vervolgens of Metal de tekening via de textuur kan lezen. De gids is bedoeld voor een Mac-ontwikkelaar die gegenereerde afbeeldingen voorbereidt voor de Metal-renderer van een desktopapp. Het deelmechanisme wordt beschreven door de makeTexture(descriptor:offset:bytesPerRow:)-API van Apple.

Welk deelpad moet je kiezen?

Wijs eerst de opslag toe met Metal. Haal het CPU-adres op met buffer.contents(), gebruik dat adres als bestemming van de bitmapcontext en maak de textuur vanuit de buffer. Apple documenteert dat bitmaptekeningen het opgegeven geheugen bijwerken en dat een bufferondersteunde textuur de toewijzing van de buffer deelt. Samen vormen deze API's de brug zonder een tussenliggende afbeelding te uploaden. Graphics Contexts, MTLBuffer.contents(), MTLTexture.

Het voorbeeld vereist expliciet een GPU uit de Apple-familie. Volgens de huidige opslagdocumentatie van Apple zijn gedeelde texturen niet beschikbaar op GPU's die niet tot de Apple-familie behoren. Behandel GPU-ondersteuning als een vereiste en ga er niet van uit dat elke Mac met Metal-ondersteuning deze specifieke toewijzingsstrategie ondersteunt. MTLStorageMode.shared.

Je vereiste Geschikte aanpak
Core Graphics tekent; Metal leest dezelfde toewijzing op Apple silicon Gedeelde buffer met bufferondersteunde textuur, zoals hieronder
Een afzonderlijk toegewezen textuur moet de tekening ontvangen Upload met replace(region:mipmapLevel:withBytes:bytesPerRow:) naar een ondersteunde, CPU-toegankelijke textuur
Herhaald GPU-gebruik is gebaat bij een afzonderlijke privét​​extuur Kopieer vanuit een staging-resource met een blit-encoder
De geselecteerde GPU kan geen gedeelde texturen maken Gebruik in plaats van dit voorbeeld met gedeelde textuur een ondersteund upload- of staging-kopieerpad

Deze alternatieven verschillen in eigenaarschap en kopieergedrag. Apple documenteert replace als een onmiddellijke CPU-kopie, terwijl blit-encoders gegevens verplaatsen tussen resources, waaronder CPU-toegankelijke en private opslag. Het directe deelpad vermijdt die uploadkopie, maar Apple waarschuwt dat bufferondersteunde texturen mogelijk minder textuuroptimalisaties krijgen. Replacing texture pixels, MTLBlitCommandEncoder, Buffer-backed texture restrictions.

Leg afspraken vast voor pixels, afmetingen en eigenaarschap

Gebruik een expliciete byte-indeling: vier bytes per pixel, in volgorde van oplopende adressen R, G, B, A. Combineer voor Core Graphics premultipliedLast met byteOrder32Big; gebruik voor Metal rgba8Unorm. De instelling voor de bytevolgorde maakt de interpretatie van het geheugen expliciet, onafhankelijk van hoe een native integer zou worden weergegeven. Apple documenteert de alfapositie, de instellingen voor bytevolgorde en de vier genormaliseerde RGBA-componenten van Metal afzonderlijk. CGImageAlphaInfo.premultipliedLast, CGBitmapInfo, MTLPixelFormat.rgba8Unorm.

Bij voorgemultipliceerde alfa bevatten de opgeslagen kleurkanalen de alfa al. Ter illustratie: halftransparant volrood boven transparante opslag is ongeveer 128, 0, 0, 128, met enige afronding door kwantisering. Een downstream-shader moet die interpretatie behouden; als de gesamplede RGB opnieuw met alfa wordt vermenigvuldigd, wordt de kleur donkerder. Het voorbeeld gebruikt ondoorzichtige vormen om de bytecontrole eenvoudig te houden. Bitmap Images and Image Masks.

Het voorbeeld kiest CGColorSpace.linearSRGB, dat Apple definieert als sRGB-kleurmetrie met een lineaire overdrachtsfunctie. Zo wordt de kleurinterpretatie van de tekening expliciet vastgelegd; het delen van geheugen zelf voert geen kleurconversie uit. Houd die interpretatie consistent wanneer je de textuur in een renderer integreert. CGColorSpace.linearSRGB.

Bereken de rijafstand vóór de toewijzing

De zichtbare rijlengte is width × 4, maar de toegewezen rijafstand kan groter zijn. Vraag minimumLinearTextureAlignment(for:) op voor het geselecteerde apparaat en formaat en rond de zichtbare rijlengte vervolgens naar boven af op een veelvoud van die uitlijning. Geef Core Graphics en Metal dezelfde resulterende bytesPerRow. minimumLinearTextureAlignment(for:).

Bij een uitdrukkelijk illustratieve apparaatuitlijning van 256 bytes heeft een afbeelding van 257 × 128 per rij 1,028 zichtbare bytes nodig, een uitgelijnde rijafstand van 1,280 bytes en 163,840 toegewezen bytes. De extra 252 bytes per rij zijn opvulling, geen extra pixels. Het programma vraagt de echte uitlijning op in plaats van deze illustratieve waarde aan te nemen.

Wijs minstens bytesPerRow × height bytes toe. Voor een pixel op geheugenkolom x en rij y is de byte-offset y × bytesPerRow + x × 4. Als je het vaste voorbeeld aanpast voor variabele afmetingen, wijs dan niet-positieve afmetingen af en controleer vóór de toewijzing of vermenigvuldigen en afronden tot een gehele-getaloverloop leiden. Core Graphics documenteert dat de opslag minimaal de volledige grootte van rijafstand maal hoogte moet hebben. Creating a Bitmap Graphics Context.

Houd de toewijzing in leven zolang die wordt gebruikt

In dit ontwerp wijst Metal het geheugen toe. De bitmapcontext leent het adres; houd de buffer in leven zolang de context bestaat. Bij asynchroon GPU-werk moet je de resources ook behouden totdat dat werk is voltooid. Maak buffer.contents() niet zelf vrij en vervang de pointer niet door een pointer die is verkregen tijdens tijdelijke toegang tot een Swift-array.

Het programma voltooit en geeft de tekencontext vrij voordat GPU-werk wordt ingediend en houdt de buffers en textuur beschikbaar totdat dat werk is voltooid. Dit volgt het model van bitmapgeheugen dat door de aanroeper wordt geleverd en de gedocumenteerde vereisten van Metal voor CPU/GPU-volgorde. Graphics Contexts, Synchronizing CPU and GPU work.

Bouw een volledig voorbeeld van een synthetische tekening

Maak een macOS-project voor de commandoregel of sla het volgende volledige programma op als main.swift. Gebruik een actuele macOS SDK van Xcode: de code gebruikt de huidige CGContext-initializer waarvan het argument bitmapInfo een CGBitmapInfo is. Oudere SDK's bieden mogelijk de verouderde initializer die in plaats daarvan bitmapInfo.rawValue verwacht. CGContext bitmap initializer.

De tekening combineert directe Core Graphics-aanroepen met een AppKit-ovaal. NSGraphicsContext(cgContext:flipped:) verpakt de bestaande context, zodat beide naar dezelfde bestemming tekenen. Door de grafische status van AppKit op te slaan en terug te zetten, wordt na het tekenen de vorige huidige context hersteld. NSGraphicsContext initializer, 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")
}

Compileer en voer het uit op de doel-Mac:

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

De eigenschap buffer van de textuur controleert de onderliggende resource. De diagnostiek kopieert daarna de textuurpixels naar een aparte gedeelde buffer en controleert een onaangeroerde rode hoek nadat de GPU klaar is. Die uitleeskopie is een opzettelijke testkopie, los van de gedeelde tekentoewijzing. Apple documenteert de bewerking van textuur naar buffer en schrijft destinationBytesPerImage: 0 voor bij een 2D-kopie. MTLTexture.buffer, Texture-to-buffer copy.

De berekeningen van de rijafstand zijn gecontroleerd met deterministische voorbeeldinvoer. Het Swift-programma is vergeleken met de opgehaalde API's, maar kon in de onderzoeksomgeving niet worden gecompileerd of uitgevoerd; die omgeving heeft Linux en geen Swift of Apple-frameworks.

Coördineer CPU-tekenen en GPU-gebruik

Gedeelde opslag vereist nog steeds een toegangsvolgorde. Voltooi het CPU-tekenen voordat je opdrachten indient die de textuur lezen. Laat die toewijzing na indiening ongewijzigd totdat de GPU klaar is met het gebruik ervan. Apple vereist deze coördinatie uitdrukkelijk voor gedeelde resources. MTLStorageMode.shared.

Voor het eenmalige voorbeeld maakt waitUntilCompleted() de overdracht eenvoudig te controleren: de functie blokkeert totdat de commandobuffer en de bijbehorende voltooiingshandlers klaar zijn. Vermijd in een interactieve renderer zo'n blokkerende wachttijd bij elk frame. Beheer in plaats daarvan meerdere buffer- en textuurparen en geef elk paar pas terug aan de beschikbare pool nadat de GPU een voltooiingsmelding heeft afgegeven. Apple laat dit patroon met een resourcepool zien in het synchronisatievoorbeeld. waitUntilCompleted(), Synchronizing CPU and GPU work.

Als je de GPU later via de ene alias laat schrijven en via een andere laat lezen, controleer dan de synchronisatie opnieuw. Apple merkt op dat gegevens van een bufferondersteunde textuur coherent kunnen zijn tussen renderpasses, zonder wijzigingen via een alias binnen één pass direct zichtbaar te maken. Het voorbeeld vermijdt die complicatie door het CPU-tekenen af te ronden vóór de enige GPU-leesbewerking. Buffer-backed texture coherence.

Spoor indelingsproblemen op voordat je de renderer aanpast

Gebruik de volgende volgorde om vast te stellen welke afspraak niet is nagekomen. Deze controles volgen uit de hierboven beschreven regels voor pixelindeling, onderliggende resources, rijafstand en synchronisatie.

  1. Het maken van de textuur mislukt: controleer of de GPU gedeelde texturen ondersteunt, beide resources .shared gebruiken en de offset en rijafstand voldoen aan de opgevraagde uitlijning. Houd de textuur 2D, met één sample, één mipniveau en één arrayelement.
  2. Rood wordt blauw weergegeven: inspecteer afzonderlijke bytes. Het voorbeeld vereist dat de RGBA-bytevolgorde, premultipliedLast, byteOrder32Big en .rgba8Unorm samen worden gebruikt.
  3. Rijen lijken scheef: vergelijk de rijafstanden van de context en de textuur. Beide moeten rowBytes gebruiken, inclusief de opvulling.
  4. De afbeelding lijkt omgekeerd: controleer de tekentransformatie en de mapping van textuurcoördinaten in de renderer. Het voorbeeld gebruikt een niet-omgekeerde tekencontext. Het argument flipped van AppKit beschrijft de aanvankelijke omgekeerde status; het herschikt de opgeslagen rijen niet.
  5. Updates verschijnen onregelmatig: controleer of CPU-tekenen samenvalt met GPU-toegang tot dezelfde toewijzing. Houd de voltooiing bij voordat je die opnieuw gebruikt.

De beperkingen bij het maken van de textuur komen uit de documentatie van Apple over lineaire texturen; het onderscheid rond de omgekeerde status komt uit de AppKit-initializer. Houd de oriëntatieverwerking expliciet wanneer je je eigen weergaveshader toevoegt. Linear-texture requirements, AppKit flipped-state parameter.

Als de onderliggende buffer, byte-indeling, opgevulde rijafstand en toegangsvolgorde overeenkomen, is de synthetische tekening via de gedeelde textuur beschikbaar voor Metal. Behoud die afspraken wanneer je de afbeelding van afmeting verandert of meer tekenbewerkingen toevoegt; kies een staging-kopie wanneer de doel-GPU of de vereisten van de uiteindelijke textuur om afzonderlijke opslag vragen.

Bronnen