Na Macu z układem Apple silicon rysuj do współdzielonego MTLBuffer za pomocą bitmapowego CGContext, a następnie utwórz teksturę Metal opartą na tym samym buforze. Kluczowe wymagania to zgodność bajtów RGBA, wyrównane kroki wierszy, zachowanie pamięci oraz uporządkowany dostęp CPU/GPU.
W tym przewodniku utworzymy syntetyczny czerwony prostokąt i niebieski owal, a następnie sprawdzimy, czy Metal może odczytać rysunek za pośrednictwem tekstury. Jest on przeznaczony dla programisty Maca, który przygotowuje wygenerowaną grafikę dla renderera Metal w aplikacji desktopowej. Mechanizm współdzielenia opisuje interfejs API Apple makeTexture(descriptor:offset:bytesPerRow:).
Którą metodę współdzielenia wybrać?
Najpierw przydziel pamięć za pomocą Metal. Pobierz jej adres CPU przez buffer.contents(), użyj go jako miejsca docelowego bitmapowego kontekstu, a następnie utwórz teksturę z bufora. Apple dokumentuje, że rysowanie bitmapowe aktualizuje dostarczoną pamięć, a tekstura oparta na buforze współdzieli jego alokację. Te interfejsy API razem zapewniają pomost bez pośredniego przesyłania obrazu. Konteksty graficzne, MTLBuffer.contents(), MTLTexture.
Przykład jawnie wymaga GPU z rodziny Apple. Aktualna dokumentacja Apple dotycząca trybów przechowywania informuje, że tekstury współdzielone nie są dostępne na GPU spoza rodziny Apple. Traktuj obsługę GPU jako warunek wstępny, zamiast zakładać, że każdy Mac obsługujący Metal pozwala na zastosowanie tej konkretnej strategii alokacji. MTLStorageMode.shared.
| Twoje wymaganie | Odpowiednie podejście |
|---|---|
| Core Graphics rysuje, a Metal odczytuje tę samą alokację na Apple silicon | Współdzielony bufor i oparta na nim tekstura, jak poniżej |
| Rysunek ma trafić do osobno alokowanej tekstury | Prześlij go za pomocą replace(region:mipmapLevel:withBytes:bytesPerRow:) do obsługiwanej tekstury dostępnej dla CPU |
| Przy wielokrotnym użyciu przez GPU przyda się osobna tekstura prywatna | Skopiuj dane z zasobu pośredniczącego za pomocą kodera blit |
| Wybrane GPU nie może tworzyć tekstur współdzielonych | Zamiast tego przykładu użyj obsługiwanej ścieżki przesyłania lub kopiowania z zasobu pośredniczącego |
Te alternatywy różnią się sposobem zarządzania zasobami i kopiowania. Apple opisuje replace jako natychmiastowe kopiowanie przez CPU, natomiast kodery blit przenoszą dane między zasobami, w tym między pamięcią dostępną dla CPU a pamięcią prywatną. Bezpośrednie współdzielenie pozwala uniknąć kopiowania podczas przesyłania, ale Apple ostrzega, że tekstury oparte na buforach mogą mieć mniej optymalizacji tekstur. Zastępowanie pikseli tekstury, MTLBlitCommandEncoder, Ograniczenia tekstur opartych na buforach.
Określ wymagania dotyczące pikseli, rozmiaru i własności
Zastosuj jawny układ bajtów: cztery bajty na piksel, w kolejności rosnących adresów R, G, B, A. W Core Graphics połącz premultipliedLast z byteOrder32Big; w Metal użyj rgba8Unorm. Ustawienie kolejności bajtów jawnie określa interpretację pamięci, niezależnie od reprezentacji natywnej liczby całkowitej. Apple osobno dokumentuje pozycję alfa, ustawienia kolejności bajtów i cztery znormalizowane składowe RGBA w Metal. CGImageAlphaInfo.premultipliedLast, CGBitmapInfo, MTLPixelFormat.rgba8Unorm.
W przypadku premultiplikowanej alfy zapisane kanały koloru już uwzględniają alfę. Przykładowo, w przybliżeniu półprzezroczysta czysta czerwień na przezroczystym obszarze ma wartość 128, 0, 0, 128, z uwzględnieniem kwantyzacji. Kolejny shader musi zachować tę interpretację; ponowne pomnożenie próbkowanego RGB przez alfę przyciemniłoby obraz. Przykład używa nieprzezroczystych kształtów, aby uprościć sprawdzanie bajtów. Obrazy bitmapowe i maski obrazów.
Przykład wybiera CGColorSpace.linearSRGB, który Apple definiuje jako kolorymetrię sRGB z liniową funkcją przenoszenia. Dzięki temu interpretacja kolorów rysunku jest jawna; samo współdzielenie pamięci nie wykonuje konwersji przestrzeni kolorów. Zachowaj tę samą interpretację przy integrowaniu tekstury z rendererem. CGColorSpace.linearSRGB.
Oblicz krok wiersza przed alokacją
Widoczna długość wiersza wynosi width × 4, ale przydzielony krok może być większy. Dla wybranego urządzenia i formatu pobierz wartość minimumLinearTextureAlignment(for:), a następnie zaokrąglij widoczną długość wiersza w górę do jej wielokrotności. Przekaż Core Graphics i Metal tę samą wartość bytesPerRow. minimumLinearTextureAlignment(for:).
Przy przykładowym, wyłącznie ilustracyjnym wyrównaniu urządzenia wynoszącym 256 bajtów obraz 257 × 128 wymaga 1,028 widocznych bajtów na wiersz, kroku wyrównanego do 1,280 bajtów i 163,840 przydzielonych bajtów. Dodatkowe 252 bajtów na wiersz to dopełnienie, a nie dodatkowe piksele. Program pobiera rzeczywistą wartość wyrównania zamiast zakładać tę przykładową.
Przydziel co najmniej bytesPerRow × height bajtów. Przesunięcie bajtowe piksela w kolumnie pamięci x i wierszu y wynosi y × bytesPerRow + x × 4. Przy dostosowywaniu przykładu ze stałymi wymiarami do zmiennych wymiarów odrzucaj wartości niedodatnie oraz sprawdzaj, czy mnożenie i zaokrąglanie nie spowodują przepełnienia liczby całkowitej przed alokacją. Core Graphics dokumentuje pełne wymaganie dotyczące pamięci: krok pomnożony przez wysokość. Tworzenie bitmapowego kontekstu graficznego.
Zachowaj alokację do czasu zakończenia pracy wszystkich odbiorców
W tym rozwiązaniu pamięć przydziela Metal. Bitmapowy kontekst pożycza jej adres; zachowaj bufor przez cały okres życia kontekstu. W przypadku asynchronicznej pracy GPU zachowaj też zasoby do jej zakończenia. Nie zwalniaj samodzielnie buffer.contents() ani nie zastępuj go wskaźnikiem uzyskanym podczas tymczasowego dostępu do tablicy Swift.
Program kończy i zwalnia kontekst rysowania przed wysłaniem pracy GPU, a następnie udostępnia bufory i teksturę aż do jej zakończenia. Jest to zgodne z modelem bitmapy wykorzystującym pamięć dostarczoną przez wywołującego oraz udokumentowanymi przez Metal wymaganiami dotyczącymi kolejności operacji CPU/GPU. Konteksty graficzne, Synchronizowanie pracy CPU i GPU.
Zbuduj kompletny przykład syntetycznego rysunku
Utwórz projekt wiersza poleceń dla macOS albo zapisz poniższy kompletny program w pliku main.swift. Użyj aktualnego SDK macOS z Xcode: kod korzysta z aktualnego inicjalizatora CGContext, którego argument bitmapInfo ma typ CGBitmapInfo. Starsze SDK mogą udostępniać starszy inicjalizator oczekujący bitmapInfo.rawValue. Inicjalizator bitmapowego CGContext.
Rysunek łączy bezpośrednie wywołania Core Graphics z owalem AppKit. NSGraphicsContext(cgContext:flipped:) opakowuje istniejący kontekst, więc oba mechanizmy rysują w tym samym miejscu docelowym. Zapisanie i odtworzenie stanu graficznego AppKit przywraca poprzedni bieżący kontekst po rysowaniu. Inicjalizator 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")
}
Skompiluj i uruchom program na docelowym Macu:
xcrun swiftc main.swift -o SharedRGBA -framework AppKit -framework CoreGraphics -framework Metal
./SharedRGBA
Właściwość buffer tekstury sprawdza jej zasób źródłowy. Diagnostyka kopiuje następnie piksele tekstury do osobnego współdzielonego bufora i po zakończeniu pracy GPU sprawdza nietknięty czerwony piksel w rogu. Ten odczyt jest celową kopią testową; nie korzysta ze współdzielonej alokacji rysunku. Apple dokumentuje operację kopiowania z tekstury do bufora i określa destinationBytesPerImage: 0 dla kopii 2D. MTLTexture.buffer, Kopiowanie z tekstury do bufora.
Obliczenia kroku wiersza sprawdzono dla deterministycznych danych wejściowych. Program Swift przeanalizowano w odniesieniu do pobranych interfejsów API, ale nie można go było skompilować ani uruchomić w środowisku badawczym, które działa pod Linuksem i nie zawiera Swifta ani frameworków Apple.
Koordynuj rysowanie CPU i użycie GPU
Współdzielona pamięć nadal wymaga harmonogramu dostępu. Zakończ rysowanie przez CPU przed zatwierdzeniem poleceń odczytujących teksturę. Po wysłaniu poleceń nie zmieniaj tej alokacji, dopóki GPU z niej korzysta. Apple wyraźnie wymaga takiej koordynacji dla zasobów współdzielonych. MTLStorageMode.shared.
W jednorazowym przykładzie waitUntilCompleted() ułatwia sprawdzenie przekazania zasobu: blokuje wykonanie do czasu zakończenia bufora poleceń i jego procedur obsługi zakończenia. W interaktywnym rendererze unikaj blokującego oczekiwania przy każdej klatce. Zamiast tego utrzymuj kilka par buforów i tekstur, a każdą parę zwracaj do dostępnej puli dopiero po otrzymaniu powiadomienia o zakończeniu pracy GPU. Apple pokazuje ten wzorzec puli zasobów w przykładzie dotyczącym synchronizacji. waitUntilCompleted(), Synchronizowanie pracy CPU i GPU.
Jeśli później GPU będzie zapisywać przez jeden alias, a odczytywać przez drugi, ponownie przeanalizuj synchronizację. Apple zauważa, że dane tekstury opartej na buforze mogą być spójne między przebiegami renderowania, ale zmiany w aliasie mogą nie być od razu widoczne w obrębie jednego przebiegu. Przykład unika tej komplikacji, kończąc rysowanie przez CPU przed jedyną operacją odczytu GPU. Spójność tekstur opartych na buforach.
Diagnozuj problemy z układem przed zmianą renderera
Aby ustalić, który warunek nie został spełniony, wykonaj poniższe kroki w podanej kolejności. Wynikają one z opisanych wyżej zasad dotyczących układu pikseli, zasobu źródłowego, kroku wiersza i synchronizacji.
- Nie udaje się utworzyć tekstury: potwierdź, że GPU obsługuje tekstury współdzielone, oba zasoby używają
.shared, a przesunięcie i krok spełniają pobrane wymagania wyrównania. Tekstura powinna być dwuwymiarowa, jednopróbkowa, z jednym poziomem mipmap i jednym elementem tablicy. - Zamiast czerwieni pojawia się niebieski: sprawdź poszczególne bajty. Przykład wymaga zgodnej kolejności bajtów RGBA,
premultipliedLast,byteOrder32Bigoraz.rgba8Unorm. - Wiersze są przekrzywione: porównaj kroki kontekstu i tekstury. Oba muszą używać
rowBytes, łącznie z dopełnieniem. - Obraz jest odwrócony: sprawdź transformację rysunku i mapowanie współrzędnych tekstury w rendererze. W przykładzie użyto nieodwróconego kontekstu rysowania. Argument
flippedAppKit opisuje początkowy stan odwrócenia; nie zmienia kolejności zapisanych wierszy. - Aktualizacje pojawiają się nieregularnie: sprawdź, czy rysowanie przez CPU nakłada się na dostęp GPU do tej samej alokacji. Przed ponownym użyciem zasobu śledź zakończenie pracy.
Ograniczenia tworzenia tekstur wynikają z dokumentacji Apple dotyczącej tekstur liniowych, a rozróżnienie stanu odwrócenia z inicjalizatora AppKit. Przy dodawaniu własnego shadera wyświetlającego jawnie obsługuj orientację. Wymagania dotyczące tekstur liniowych, Parametr stanu odwrócenia AppKit.
Gdy bufor źródłowy, układ bajtów, dopełniony krok i harmonogram dostępu są zgodne, Metal może korzystać z syntetycznego rysunku za pośrednictwem współdzielonej tekstury. Zachowaj te wymagania przy zmianie rozmiaru obrazu lub dodawaniu operacji rysowania; użyj kopiowania z zasobu pośredniczącego, jeśli wymaga tego docelowe GPU lub tekstura używana w dalszej części potoku.
Źródła
MTLTexture — Apple
MTLStorageMode.shared — Apple
contents() — Apple
Konteksty graficzne — Apple
Obrazy bitmapowe i maski obrazów — 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
Synchronizowanie pracy CPU i GPU — Apple
waitUntilCompleted() — Apple
MTLBlitCommandEncoder — Apple