På en Mac med Apple-chip ritar du till en delad MTLBuffer via en bitmappsbaserad CGContext och skapar sedan en Metal-textur som använder samma buffert som lagring. De viktigaste kraven är matchande RGBA-byte, justerade radsteg, bevarad lagring och samordnad CPU-/GPU-åtkomst.

Den här guiden skapar en syntetisk röd rektangel och en blå oval och kontrollerar sedan att Metal kan läsa ritningen via texturen. Den riktar sig till Mac-utvecklare som förbereder genererad grafik för en skrivbordsapps Metal-renderare. Apples makeTexture(descriptor:offset:bytesPerRow:)-API dokumenterar delningsmekanismen.

Vilken delningsväg ska du välja?

Allokera lagringen med Metal först. Hämta dess CPU-adress med buffer.contents(), använd adressen som bitmapkontextens mål och skapa texturen från bufferten. Apple dokumenterar att bitmappsritning uppdaterar det angivna minnet och att en buffertstödd textur delar buffertens allokering. Tillsammans ger dessa API:er en brygga utan någon mellanliggande bilduppladdning. Graphics Contexts, MTLBuffer.contents(), MTLTexture.

Exemplet kräver uttryckligen en GPU i Apple-familjen. Apples aktuella dokumentation om lagring anger att delade texturer inte är tillgängliga på GPU:er utanför Apple-familjen. Se GPU-stöd som ett krav i stället för att anta att alla Metal-kompatibla Mac-datorer stöder just den här allokeringsstrategin. MTLStorageMode.shared.

Ditt krav Lämpligt tillvägagångssätt
Core Graphics ritar; Metal läser samma allokering på Apple-chip Delad buffert plus buffertstödd textur, som nedan
En separat allokerad textur ska ta emot ritningen Ladda upp med replace(region:mipmapLevel:withBytes:bytesPerRow:) till en textur som stöder CPU-åtkomst
Upprepad GPU-användning gynnas av en separat privat textur Kopiera från en mellanlagringsresurs med en blit-kodare
Den valda GPU:n kan inte skapa delade texturer Använd en stödd uppladdnings- eller mellanlagringskopieringsväg i stället för det här exemplet med delad textur

Dessa alternativ har olika beteenden för ägarskap och kopiering. Apple dokumenterar replace som en omedelbar CPU-kopia, medan blit-kodare flyttar data mellan resurser, även mellan CPU-åtkomlig och privat lagring. Den direkta delningsvägen undviker uppladdningskopian, men Apple varnar för att buffertstödda texturer kan få färre texturoptimeringar. Replacing texture pixels, MTLBlitCommandEncoder, Buffer-backed texture restrictions.

Definiera kraven för pixlar, storlek och ägarskap

Använd en uttrycklig byte-layout: fyra byte per pixel i stigande adressordning R, G, B, A. För Core Graphics kombinerar du premultipliedLast med byteOrder32Big; för Metal använder du rgba8Unorm. Inställningen för byteordning gör minnestolkningen tydlig och oberoende av hur ett inbyggt heltal skulle representeras. Apple dokumenterar separat alfapositionen, kontrollerna för byteordning och Metals fyra normaliserade RGBA-komponenter. CGImageAlphaInfo.premultipliedLast, CGBitmapInfo, MTLPixelFormat.rgba8Unorm.

Premultiplicerad alfa innebär att de lagrade färgkanalerna redan innehåller alfa. Som ett illustrativt räkneexempel blir lagrade värden för helt rött med halvt opakhet mot transparent lagring ungefär 128, 0, 0, 128, med reservation för kvantisering. En nedströms shader måste bevara den tolkningen; om RGB-värdet som samplats multipliceras med alfa en gång till blir färgen mörkare. Exemplet använder opaka former för att göra bytekontrollen enkel. Bitmap Images and Image Masks.

Exemplet väljer CGColorSpace.linearSRGB, som Apple definierar som sRGB-kolorimetri med en linjär överföringsfunktion. Därmed fastställs ritningens färgtolkning uttryckligen; minnesdelningen i sig utför ingen färgrymdskonvertering. Behåll samma tolkning när du integrerar texturen i en renderare. CGColorSpace.linearSRGB.

Beräkna radsteget före allokering

Den synliga radlängden är width × 4, men det allokerade radsteget kan vara större. Fråga minimumLinearTextureAlignment(for:) efter värdet för den valda enheten och det valda formatet och avrunda sedan den synliga radlängden uppåt till närmaste multipel av justeringen. Ge Core Graphics och Metal samma resulterande bytesPerRow. minimumLinearTextureAlignment(for:).

Med ett uttryckligen illustrativt enhetsjusteringsvärde på 256 byte behöver en bild på 257 × 128 synliga 1,028 byte per rad, ett justerat radsteg på 1,280 byte och totalt 163,840 allokerade byte. De extra 252 byten per rad är utfyllnad, inte ytterligare pixlar. Programmet frågar efter den verkliga justeringen i stället för att utgå från det här illustrativa värdet.

Allokera minst bytesPerRow × height byte. Byteförskjutningen för en pixel vid minneskolumn x och rad y är y × bytesPerRow + x × 4. Om du anpassar exemplet med fasta mått till variabla dimensioner ska du avvisa icke-positiva storlekar och kontrollera multiplikation och avrundning med avseende på heltalsöverskridning före allokering. Core Graphics dokumenterar det fullständiga lagringskravet som radsteg gånger höjd. Creating a Bitmap Graphics Context.

Håll allokeringen vid liv så länge någon använder den

I den här lösningen allokerar Metal minnet. Bitmapkontexten lånar dess adress; håll bufferten vid liv under hela kontextens livstid. Vid asynkront GPU-arbete ska du också behålla resurserna tills arbetet har slutförts. Frigör inte buffer.contents() själv och ersätt den inte med en pekare från en tillfällig Swift-arrayåtkomst.

Programmet slutför och släpper sin ritkontext innan GPU-arbetet skickas in och håller sedan buffertarna och texturen tillgängliga tills arbetet är klart. Detta följer modellen med bitmapminne som tillhandahålls av anroparen och Metals dokumenterade krav på ordningsföljd mellan CPU och GPU. Graphics Contexts, Synchronizing CPU and GPU work.

Skapa ett fullständigt exempel med syntetisk ritning

Skapa ett kommandoradsprojekt för macOS eller spara följande fullständiga program som main.swift. Använd ett aktuellt macOS SDK för Xcode: koden använder den aktuella CGContext-initieraren vars argument bitmapInfo är en CGBitmapInfo. Äldre SDK:er kan exponera den äldre initieraren som i stället förväntar sig bitmapInfo.rawValue. CGContext bitmap initializer.

Ritningen kombinerar direkta Core Graphics-anrop med en AppKit-oval. NSGraphicsContext(cgContext:flipped:) omsluter den befintliga kontexten, så båda ritar till samma mål. När AppKits grafikstatus sparas och återställs återställs den tidigare aktuella kontexten efter ritningen. 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")
}

Kompilera och kör programmet på mål-Mac-datorn:

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

Texturens egenskap buffer kontrollerar dess bakomliggande resurs. Diagnostiken kopierar sedan texturpixlar till en separat delad buffert och kontrollerar ett orört rött hörn efter att GPU-arbetet har slutförts. Den återläsningen är en avsiktlig testkopia och är separat från den delade ritallokeringen. Apple dokumenterar kopieringen från textur till buffert och anger destinationBytesPerImage: 0 för en 2D-kopia. MTLTexture.buffer, Texture-to-buffer copy.

Radstegsberäkningarna kontrollerades med deterministiska exempelvärden. Swift-programmet granskades mot de hämtade API:erna, men kunde inte kompileras eller köras i forskningsmiljön, som tillhandahåller Linux utan Swift eller Apple-ramverk.

Samordna CPU-ritning och GPU-användning

Även delad lagring kräver en åtkomstplan. Slutför CPU-ritningen innan du skickar in kommandon som läser texturen. Efter inskickningen ska allokeringen lämnas oförändrad tills GPU:n är klar med den. Apple kräver uttryckligen sådan samordning för delade resurser. MTLStorageMode.shared.

För exemplet med en enda körning gör waitUntilCompleted() överlämningen enkel att inspektera: anropet blockerar tills kommandobufferten och dess kompletteringshanterare är klara. I en interaktiv renderare bör du undvika att lägga in en sådan blockerande väntan i varje bildruta. Håll i stället flera par av buffertar och texturer och återför varje par till den tillgängliga poolen först efter en avisering om att GPU-arbetet har slutförts. Apple visar det här resurspoolsmönstret i sitt synkroniseringsexempel. waitUntilCompleted(), Synchronizing CPU and GPU work.

Om GPU:n senare skriver via ett alias och läser via ett annat bör du granska synkroniseringen på nytt. Apple påpekar att data i buffertstödda texturer kan vara koherenta mellan renderingspass utan att aliasändringar omedelbart blir synliga inom ett och samma pass. Exemplet undviker den komplikationen genom att slutföra CPU-ritningen före den enda GPU-läsningen. Buffer-backed texture coherence.

Felsök layoutproblem innan du ändrar renderaren

Använd följande ordning för att hitta vilket krav som inte uppfylldes. Kontrollerna bygger på de dokumenterade reglerna för pixellayout, bakomliggande resurs, radsteg och synkronisering ovan.

  1. Det går inte att skapa texturen: bekräfta att GPU:n stöder delade texturer, att båda resurserna använder .shared och att förskjutningen och radsteget uppfyller den efterfrågade justeringen. Texturen ska vara 2D, ha ett enda sampel, en mipmapnivå och ett arrayelement.
  2. Rött visas som blått: granska de enskilda byten. Exemplet kräver RGBA-byteordning, premultipliedLast, byteOrder32Big och .rgba8Unorm tillsammans.
  3. Raderna verkar sneda: jämför kontextens och texturens radsteg. Båda måste använda rowBytes, inklusive utfyllnad.
  4. Bilden verkar upp och ned: kontrollera rittransformen och renderarens mappning av texturkoordinater. Exemplet använder en oflippad ritkontext. AppKits argument flipped beskriver dess ursprungliga flippade läge; det ändrar inte ordningen på de lagrade raderna.
  5. Uppdateringarna verkar inkonsekventa: kontrollera om CPU-ritning sker samtidigt som GPU:n använder samma allokering. Spåra slutförandet innan du återanvänder den.

Begränsningarna för skapandet hämtas från Apples dokumentation om linjära texturer och skillnaden mellan flippat läge hämtas från AppKit-initieraren. Hantera orienteringen uttryckligen när du lägger till en egen visningsshader. Linear-texture requirements, AppKit flipped-state parameter.

När den bakomliggande bufferten, byte-layouten, det utfyllda radsteget och åtkomstplanen stämmer överens blir den syntetiska ritningen tillgänglig för Metal via den delade texturen. Behåll dessa krav när du ändrar bildens storlek eller lägger till fler ritoperationer; välj en kopia via mellanlagring när mål-GPU:n eller kraven på den nedströms texturen förutsätter separat lagring.

Källor