На Mac с Apple silicon нарисуйте в общем MTLBuffer через битовый CGContext, затем создайте текстуру Metal, поддерживаемую этим же буфером. Основные контракты: соответствующие байты RGBA, выровненные шаги строк, удерживаемое хранилище и упорядоченный доступ CPU/GPU.

Это руководство создаёт синтетический красный прямоугольник и синий овал, а затем проверяет, что Metal может прочитать рисунок через текстуру. Оно предназначено для разработчика Mac, готовящего сгенерированную графику для Metal-рендерера настольного приложения. Механизм совместного использования задокументирован в API makeTexture(descriptor:offset:bytesPerRow:) от Apple.

Какой путь совместного использования следует выбрать?

Выделите хранилище через Metal первым. Получите его CPU-адрес с помощью buffer.contents(), используйте этот адрес в качестве назначения битового контекста и создайте текстуру из буфера. Apple документирует, что битовое рисование обновляет предоставленную память, а текстура, поддерживаемая буфером, делит выделение своего буфера. Вместе эти API обеспечивают мост без промежуточной загрузки изображения. Graphics Contexts, MTLBuffer.contents(), MTLTexture.

Пример явно требует GPU семейства Apple. Текущая документация Apple по хранилищу гласит, что общие текстуры недоступны на GPU не семейства Apple. Обратитесь с поддержкой GPU как с предварительным условием, а не как с гарантией, что каждый Mac с поддержкой Metal поддерживает именно эту стратегию выделения. MTLStorageMode.shared.

Ваше требование Подходящий подход
Core Graphics рисует; Metal читает то же выделение на Apple silicon Общий буфер плюс текстура, поддерживаемая буфером, как описано ниже
Отдельно выделенная текстура должна получить рисунок Загрузите с помощью replace(region:mipmapLevel:withBytes:bytesPerRow:) в поддерживаемую CPU-доступную текстуру
Повторное использование GPU выигрывает от отдельной приватной текстуры Скопируйте из промежуточного ресурса с помощью blit-кодировщика
Выбранный GPU не может создавать общие текстуры Используйте поддерживаемый путь загрузки или поэтапного копирования вместо этого примера с общей текстурой

У этих альтернатив разное поведение владения и копирования. Apple документирует replace как немедленное копирование CPU, в то время как blit-кодировщики перемещают данные между ресурсами, включая CPU-доступное и приватное хранилище. Путь прямого совместного использования избегает этой копии загрузки, но Apple предостерегает, что текстуры, поддерживаемые буфером, могут получать меньше оптимизаций текстур. Replacing texture pixels, MTLBlitCommandEncoder, Buffer-backed texture restrictions.

Определите контракты пикселя, размера и владения

Используйте явную байтовую компоновку: четыре байта на пиксель в порядке возрастания адреса R, G, B, A. Для Core Graphics объедините premultipliedLast с byteOrder32Big; для Metal используйте rgba8Unorm. Настройка порядка байтов делает интерпретацию памяти явной, независимо от того, как нативное целое число было бы представлено. Apple документирует позицию альфа, управление порядком байтов и четыре нормализованных RGBA-компонента Metal отдельно. CGImageAlphaInfo.premultipliedLast, CGBitmapInfo, MTLPixelFormat.rgba8Unorm.

Премноженная альфа означает, что сохранённые цветовые каналы уже включают альфа. Как иллюстративный расчёт, полупрозрачный полный красный поверх прозрачного хранилища составляет примерно 128, 0, 0, 128, с учётом квантования. Нижний шейдер должен сохранять эту интерпретацию; повторное умножение выборки RGB на альфу затемнит её. Пример использует непрозрачные фигуры, чтобы сохранить проверку байтов простой. Bitmap Images and Image Masks.

Пример выбирает CGColorSpace.linearSRGB, который Apple определяет как цветометрию sRGB с линейной функцией передачи. Это устанавливает интерпретацию цвета рисования явно; совместное использование памяти само по себе не выполняет преобразование цветового пространства. Сохраняйте эту интерпретацию согласованной при интеграции текстуры в рендерер. CGColorSpace.linearSRGB.

Вычислите шаг строки перед выделением

Видимая длина строки — width × 4, но выделенный шаг может быть больше. Запросите minimumLinearTextureAlignment(for:) для выбранного устройства и формата, затем округлите видимую длину строки вверх до кратного этого выравнивания. Передайте Core Graphics и Metal тот же результирующий bytesPerRow. minimumLinearTextureAlignment(for:).

Для явно иллюстративного выравнивания устройства в 256 байт, изображение 257 × 128 требует 1,028 видимых байт на строку, выровненный шаг в 1,280 байт и 163,840 выделенных байт. Дополнительные 252 байт на строку — это заполнение, а не дополнительные пиксели. Программа запрашивает реальное выравнивание вместо предположения этого иллюстративного значения.

Выделите не менее bytesPerRow × height байт. Для пикселя в столбце памяти x и строке y его байтовое смещение составляет y × bytesPerRow + x × 4. При адаптации фиксированного примера к переменным размерам отвергайте неположительные размеры и проверяйте умножение и округление на переполнение целых чисел перед выделением. Core Graphics документирует полное требование хранилища «шаг-размер-высота». Creating a Bitmap Graphics Context.

Держите выделение живым через каждого потребителя

В этом дизайне Metal выделяет память. Битовый контекст заимствует её адрес; держите буфер живым на протяжении всего времени жизни контекста. Для асинхронной работы GPU также удерживайте ресурсы до завершения этой работы. Не освобождайте buffer.contents() самостоятельно и не подставляйте указатель, полученный из временного доступа к массиву Swift.

Программа завершает и освобождаёт свой контекст рисования перед отправкой работы GPU, затем держит буферы и текстуру доступными до завершения. Это следует модели вызова-поставляемой битовой памяти и документальным требованиям упорядочивания CPU/GPU Metal. Graphics Contexts, Synchronizing CPU and GPU work.

Постройте полный пример синтетического рисования

Создайте проект командной строки macOS или сохраните следующую полную программу как main.swift. Используйте актуальный Xcode macOS SDK: код использует текущий инициализатор CGContext, чей аргумент bitmapInfo — это CGBitmapInfo. Старые SDK могут предоставлять устаревший инициализатор, ожидающий bitmapInfo.rawValue вместо этого. CGContext bitmap initializer.

Рисование сочетает прямые вызовы Core Graphics с овалом AppKit. NSGraphicsContext(cgContext:flipped:) оборачивает существующий контекст, так что оба рисуют в одно и то же назначение. Сохранение и восстановление графического состояния AppKit восстанавливает предыдущий текущий контекст после рисования. 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")
}

Скомпилируйте и запустите на целевом Mac:

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

Свойство текстуры buffer проверяет её поддерживающий ресурс. Диагностика затем копирует пиксели текстуры в отдельный общий буфер и проверяет нетронутый красный угол после завершения GPU. Это чтение обратно — намеренная тестовая копия; оно отделено от выделения совместного рисования. Apple документирует операцию копирования текстуры-в-буфер и указывает destinationBytesPerImage: 0 для 2D-копии. MTLTexture.buffer, Texture-to-buffer copy.

Расчёты шагов проверялись с детерминированными образцами входных данных. Программа на Swift была проверена по извлечённым API, но не могла быть скомпилирована или запущена в исследовательской среде, которая предоставляет Linux без Swift или фреймворков Apple.

Координируйте CPU-рисование и использование GPU

Общее хранилище всё равно требует расписания доступа. Завершите CPU-рисование перед фиксацией команд, читающих текстуру. После отправки оставьте это выделение неизменным, пока GPU не закончит его использовать. Apple явно требует эту координацию для общих ресурсов. MTLStorageMode.shared.

Для примера однократного использования waitUntilCompleted() делает передачу легкой для инспекции: он блокирует до завершения командного буфера и его обработчиков завершения. В интерактивном рендерере избегайте размещения этого блокирующего ожидания в каждом кадре. Вместо этого поддерживайте несколько пар буфер-текстура и возвращайте каждую пару в доступный пул только после уведомления о завершении её GPU. Apple демонстрирует этот паттерн пула ресурсов в своём примере синхронизации. waitUntilCompleted(), Synchronizing CPU and GPU work.

Если позже вы заставите GPU писать через один псевдоним и читать через другой, пересмотрите синхронизацию снова. Apple отмечает, что данные текстуры, поддерживаемой буфером, могут быть когерентными между проходами рендеринга без немедленного отражения изменений псевдонимов внутри одного прохода. Пример избегает этой сложности, завершая CPU-рисование перед своей единственной операцией чтения GPU. Buffer-backed texture coherence.

Диагностируйте проблемы компоновки перед изменением рендерера

Используйте следующую последовательность для изоляции провалившегося контракта. Эти проверки следуют из задокументированных выше правил компоновки пикселей, поддерживающего ресурса, шага и синхронизации.

  1. Создание текстуры не удаётся: подтвердите, что GPU поддерживает общие текстуры, оба ресурса используют .shared, и смещение и шаг соответствуют запрошенному выравниванию. Держите текстуру 2D, с одним сэмплом, одним mip-уровнем и одним элементом массива.
  2. Красный выглядит синим: инспектируйте отдельные байты. Пример требует порядок байтов RGBA, premultipliedLast, byteOrder32Big и .rgba8Unorm вместе.
  3. Строки выглядят скошенными: сравните шаги контекста и текстуры. Оба должны использовать rowBytes, включая заполнение.
  4. Изображение выглядит инвертированным: проверьте трансформацию рисования и отображение текстурных координат рендерера. Пример использует не перевёрнутый контекст рисования. Аргумент flipped AppKit описывает его начальное перевёрнутое состояние; он не переупорядочивает сохранённые строки.
  5. Обновления появляются неконсистентно: проверьте, не перекрывается ли CPU-рисование с доступом GPU к тому же выделению. Используйте отслеживание завершения перед повторным использованием.

Ограничения создания исходят из документации Apple по линейным текстурам, а различие перевёрнутого состояния — из инициализатора AppKit. Держите обработку ориентации явной при добавлении собственного шейдера отображения. Linear-texture requirements, AppKit flipped-state parameter.

Как только поддерживающий буфер, байтовая компоновка, заполненный шаг и расписание доступа согласуются, синтетический рисунок становится доступен Metal через общую текстуру. Сохраняйте эти контракты при изменении размера изображения или добавлении операций рисования; выбирайте поэтапное копирование, когда целевой GPU или требования нижестоящей текстуры требуют отдельного хранилища.

Источники