Em um Mac com Apple silicon, desenhe em um MTLBuffer compartilhado por meio de um CGContext bitmap e, em seguida, crie uma textura Metal respaldada por esse mesmo buffer. Os contratos essenciais são bytes RGBA compatíveis, strides de linha alinhados, armazenamento retido e acesso ordenado pela CPU/GPU.
Este guia cria um retângulo vermelho e uma elipse azul sintéticos e, em seguida, verifica se o Metal consegue ler o desenho pela textura. Ele se destina a quem desenvolve para Mac e está preparando gráficos gerados para o renderizador Metal de um app para desktop. O mecanismo de compartilhamento é documentado pela API da Apple makeTexture(descriptor:offset:bytesPerRow:).
Qual caminho de compartilhamento você deve escolher?
Alocar o armazenamento primeiro com o Metal. Obtenha seu endereço de CPU com buffer.contents(), use esse endereço como destino do contexto bitmap e crie a textura a partir do buffer. A Apple documenta que o desenho bitmap atualiza a memória fornecida e que uma textura respaldada por buffer compartilha a alocação desse buffer. Juntas, essas APIs fazem a ponte sem uma transferência intermediária da imagem. Graphics Contexts, MTLBuffer.contents(), MTLTexture.
O exemplo exige explicitamente uma GPU da família Apple. A documentação atual da Apple sobre modos de armazenamento informa que texturas compartilhadas não estão disponíveis em GPUs que não sejam da família Apple. Trate o suporte da GPU como pré-requisito, em vez de presumir que todo Mac compatível com Metal aceita exatamente essa estratégia de alocação. MTLStorageMode.shared.
| Seu requisito | Abordagem apropriada |
|---|---|
| Core Graphics desenha; Metal lê a mesma alocação em um Mac com Apple silicon | Buffer compartilhado com textura respaldada por buffer, como abaixo |
| Uma textura alocada separadamente deve receber o desenho | Transfira os dados com replace(region:mipmapLevel:withBytes:bytesPerRow:) para uma textura compatível com acesso pela CPU |
| O uso repetido pela GPU se beneficia de uma textura privada separada | Copie de um recurso de preparação com um codificador blit |
| A GPU selecionada não consegue criar texturas compartilhadas | Use um caminho compatível de transferência ou cópia de preparação, em vez deste exemplo de textura compartilhada |
Essas alternativas têm comportamentos diferentes de propriedade e cópia. A Apple documenta replace como uma cópia imediata feita pela CPU, enquanto codificadores blit transferem dados entre recursos, inclusive armazenamento acessível pela CPU e armazenamento privado. O caminho de compartilhamento direto evita essa cópia de transferência, mas a Apple alerta que texturas respaldadas por buffer podem receber menos otimizações de textura. Replacing texture pixels, MTLBlitCommandEncoder, Buffer-backed texture restrictions.
Defina os contratos de pixel, tamanho e propriedade
Use um layout explícito de bytes: quatro bytes por pixel, na ordem crescente dos endereços R, G, B, A. Para Core Graphics, combine premultipliedLast com byteOrder32Big; para Metal, use rgba8Unorm. A configuração de ordem dos bytes torna explícita a interpretação da memória, independentemente da representação de um inteiro nativo. A Apple documenta separadamente a posição do alfa, os controles de ordem dos bytes e os quatro componentes RGBA normalizados do Metal. CGImageAlphaInfo.premultipliedLast, CGBitmapInfo, MTLPixelFormat.rgba8Unorm.
Alfa pré-multiplicado significa que os canais de cor armazenados já incluem o alfa. Como cálculo ilustrativo, vermelho totalmente intenso com transparência pela metade sobre armazenamento transparente resulta aproximadamente em 128, 0, 0, 128, considerando a quantização. Um shader subsequente deve preservar essa interpretação; multiplicar novamente o RGB amostrado pelo alfa o escureceria. O exemplo usa formas opacas para simplificar a verificação dos bytes. Bitmap Images and Image Masks.
O exemplo escolhe CGColorSpace.linearSRGB, definido pela Apple como colorimetria sRGB com uma função de transferência linear. Isso estabelece explicitamente a interpretação de cor do desenho; o compartilhamento de memória, por si só, não realiza conversão de espaço de cor. Mantenha essa interpretação consistente ao integrar a textura ao renderizador. CGColorSpace.linearSRGB.
Calcule o stride da linha antes de alocar
O comprimento visível da linha é width × 4, mas o stride alocado pode ser maior. Consulte minimumLinearTextureAlignment(for:) para o dispositivo e formato selecionados e, em seguida, arredonde o comprimento visível da linha para cima até um múltiplo desse alinhamento. Passe o mesmo bytesPerRow para Core Graphics e Metal. minimumLinearTextureAlignment(for:).
Para um alinhamento de dispositivo de 256 bytes, usado aqui apenas para fins ilustrativos, uma imagem de 257 × 128 precisa de 1,028 bytes visíveis por linha, um stride alinhado de 1,280 bytes e 163,840 bytes alocados. Os 252 bytes extras por linha são preenchimento, não pixels adicionais. O programa consulta o alinhamento real em vez de presumir esse valor ilustrativo.
Aloque pelo menos bytesPerRow × height bytes. Para um pixel na coluna de memória x e na linha y, seu deslocamento em bytes é y × bytesPerRow + x × 4. Ao adaptar o exemplo fixo para dimensões variáveis, rejeite tamanhos não positivos e verifique se as multiplicações e o arredondamento não causam estouro de inteiro antes da alocação. Core Graphics documenta o requisito de armazenamento correspondente ao stride multiplicado pela altura. Creating a Bitmap Graphics Context.
Mantenha a alocação viva enquanto houver consumidores
Neste projeto, o Metal aloca a memória. O contexto bitmap toma emprestado seu endereço; mantenha o buffer vivo durante toda a vida útil do contexto. Para trabalho assíncrono da GPU, mantenha também os recursos retidos até a conclusão desse trabalho. Não libere buffer.contents() por conta própria nem o substitua por um ponteiro obtido durante o acesso temporário a um array Swift.
O programa termina e libera seu contexto de desenho antes de enviar o trabalho à GPU e, em seguida, mantém os buffers e a textura disponíveis até a conclusão. Isso segue o modelo de memória bitmap fornecida pelo chamador e os requisitos documentados pelo Metal para ordenação entre CPU e GPU. Graphics Contexts, Synchronizing CPU and GPU work.
Crie um exemplo completo de desenho sintético
Crie um projeto de linha de comando para macOS ou salve o programa completo a seguir como main.swift. Use um SDK atual do macOS no Xcode: o código usa o inicializador atual de CGContext, cujo argumento bitmapInfo é um CGBitmapInfo. SDKs mais antigos podem expor o inicializador legado, que espera bitmapInfo.rawValue. CGContext bitmap initializer.
O desenho combina chamadas diretas de Core Graphics com uma elipse do AppKit. NSGraphicsContext(cgContext:flipped:) encapsula o contexto existente, de modo que ambos desenham no mesmo destino. Salvar e restaurar o estado gráfico do AppKit restaura o contexto atual anterior após o desenho. 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")
}
Compile e execute no Mac de destino:
xcrun swiftc main.swift -o SharedRGBA -framework AppKit -framework CoreGraphics -framework Metal
./SharedRGBA
A propriedade buffer da textura verifica seu recurso de apoio. Em seguida, o diagnóstico copia os pixels da textura para um buffer compartilhado separado e verifica um canto vermelho que não foi alterado, após a conclusão da GPU. Essa leitura de retorno é uma cópia de teste intencional, separada da alocação compartilhada usada para o desenho. A Apple documenta a operação de cópia da textura para o buffer e especifica destinationBytesPerImage: 0 para uma cópia 2D. MTLTexture.buffer, Texture-to-buffer copy.
Os cálculos de stride foram verificados com entradas de exemplo determinísticas. O programa Swift foi analisado com base nas APIs consultadas, mas não pôde ser compilado nem executado no ambiente de pesquisa, que usa Linux e não disponibiliza Swift nem os frameworks da Apple.
Coordene o desenho da CPU e o uso da GPU
O armazenamento compartilhado ainda exige uma ordem de acesso. Termine o desenho na CPU antes de confirmar comandos que leem a textura. Após o envio, não altere essa alocação enquanto a GPU ainda a estiver usando. A Apple exige explicitamente essa coordenação para recursos compartilhados. MTLStorageMode.shared.
No exemplo de execução única, waitUntilCompleted() torna a transferência de controle fácil de inspecionar: ele bloqueia até que o buffer de comandos e seus manipuladores de conclusão terminem. Em um renderizador interativo, evite fazer essa espera bloqueante em todos os quadros. Em vez disso, mantenha vários pares de buffer e textura e devolva cada par ao pool de recursos disponíveis somente após a notificação de conclusão da GPU. A Apple demonstra esse padrão de pool de recursos em seu exemplo de sincronização. waitUntilCompleted(), Synchronizing CPU and GPU work.
Se mais tarde você fizer a GPU gravar por um alias e ler por outro, revise a sincronização novamente. A Apple observa que os dados de textura respaldada por buffer podem ser coerentes entre passes de renderização sem refletir imediatamente as alterações feitas por um alias dentro de um mesmo passe. O exemplo evita essa complicação ao concluir o desenho na CPU antes da única operação de leitura pela GPU. Buffer-backed texture coherence.
Diagnostique problemas de layout antes de alterar o renderizador
Use a sequência a seguir para isolar qual contrato falhou. Essas verificações decorrem das regras documentadas acima para layout de pixels, recurso de apoio, stride e sincronização.
- A criação da textura falha: confirme que a GPU oferece suporte a texturas compartilhadas, que ambos os recursos usam
.sharede que o deslocamento e o stride atendem ao alinhamento consultado. Mantenha a textura 2D, com uma única amostra, um nível de mipmap e um elemento de array. - O vermelho aparece azul: inspecione os bytes individualmente. O exemplo exige que a ordem dos bytes seja RGBA e que
premultipliedLast,byteOrder32Bige.rgba8Unormsejam usados em conjunto. - As linhas parecem inclinadas: compare os strides do contexto e da textura. Ambos devem usar
rowBytes, incluindo o preenchimento. - A imagem parece invertida: verifique a transformação do desenho e o mapeamento das coordenadas de textura no renderizador. O exemplo usa um contexto de desenho sem inversão. O argumento
flippeddo AppKit descreve seu estado inicial de inversão; ele não reorganiza as linhas armazenadas. - As atualizações parecem inconsistentes: verifique se o desenho na CPU ocorre ao mesmo tempo que o acesso da GPU à mesma alocação. Acompanhe as conclusões antes de reutilizá-la.
As restrições de criação vêm da documentação da Apple sobre texturas lineares, e a distinção sobre o estado de inversão vem do inicializador do AppKit. Mantenha explícito o tratamento da orientação ao adicionar seu próprio shader de exibição. Linear-texture requirements, AppKit flipped-state parameter.
Quando o buffer de apoio, o layout de bytes, o stride com preenchimento e a ordem de acesso estiverem de acordo, o desenho sintético ficará disponível para o Metal por meio da textura compartilhada. Preserve esses contratos ao redimensionar a imagem ou adicionar operações de desenho; escolha uma cópia de preparação quando a GPU de destino ou os requisitos da textura subsequente exigirem armazenamento separado.
Fontes
MTLTexture — Apple
MTLStorageMode.shared — Apple
contents() — Apple
Graphics Contexts — Apple
Bitmap Images and Image Masks — 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
Synchronizing CPU and GPU work — Apple
waitUntilCompleted() — Apple
MTLBlitCommandEncoder — Apple