Как освоить API-интерфейсы Metal с UIView и SwiftUI



Книга Как освоить API-интерфейсы Metal с UIView и SwiftUI



Введение


Metal  —  это мощный низкоуровневый фреймворк графического и вычислительного программирования на платформах Apple с максимальным задействованием производительности графического процессора для сложных вычислений и задач отрисовки.


Metal применяется в основном для обработки изображений и видео в реальном времени с высокой производительностью и низкими накладными расходами.


В этой статье, рисуя треугольник, мы погрузимся в мир Metal, рассмотрим основные понятия и терминологию, этапы настройки среды разработки, научимся делать с Metal простую 3D-графику для сложных проектов и в итоге создадим приложение, в котором этот треугольник отрисуется.





Содержание


· Настройка среды.
· Шейдеры Metal.
- создание и использование шейдеров в Metal;
- конвейеры и состояние.
· Применение Metal в ViewController.
1. Настройка исходного представления.
2. Настройка представления Metal.
3. Создание слоя Metal.
4. Создание буфера вершин.
5. Создание состояния конвейера отрисовки.
6. Отрисовка треугольника.
· Применение Metal в SwiftUI.
· Заключение.



Настройка Metal в проекте на iOS


API-интерфейсы Metal используются в проекте на iOS так.



  1. Создаем новый проект Xcode, в качестве платформы выбираем iOS.

  2. Во вкладке General («Общие») настроек проекта выставляем версию с поддержкой Metal  —  iOS 8 или новее.

  3. Во вкладке Build Settings («Параметры сборки») находим Metal и задаем параметру MetalCaptureEnabled значение Yes: включаем API-интерфейсы Metal для проекта.

  4. В коде импортируем фреймворк Metal, добавляя вверху файла эту строку: import Metal.

  5. Чтобы использовать Metal в коде, нужен объект MTLDevice  —  это устройство, обычно графический процессор, на котором запускается Metal.


Создаем этот объект:


let device = MTLCreateSystemDefaultDevice()

Затем создаются другие объекты Metal: MTLCommandQueue и MTLCommandBuffer  —  для выполнения операций в устройстве.


Рассмотрим основные понятия Metal.


Шейдеры Metal



Шейдер в Metal  —  это небольшая программа, запускаемая в графическом процессоре для выполнения конкретной задачи, например отрисовки 3D-модели и наложения фильтра на изображение.



В Metal имеется три типа шейдеров: вершинные, фрагментные и вычислительные.


1. Вершинные шейдеры


Этими шейдерами трехмерные координаты модели преобразуются в двухмерные координаты экранного пространства. На входе принимаются данные вершин  —  положение, обычные и текстурные координаты,  —  на выходе выдается набор преобразованных данных.


2. Фрагментные шейдеры


Ими определяется конечный цвет каждого пикселя на экране. Принимаются интерполированные данные вершин из вершинного шейдера, а возвращается конечный цвет каждого пикселя.


3. Вычислительные шейдеры


Эти шейдеры универсальнее: для любых вычислений в графическом процессоре. Часто применяются в задачах обработки и моделирования изображений.


Создание и использование шейдеров в Metal



В Metal шейдеры пишут на языке шейдеров Metal Shading Language (MSL).



Новый MSL-файл в Xcode создается так: переходим в File («Файл») > New («Новый») > File («Файл») и в разделе Metal выбираем Metal Shading Language File («Файл языка шейдеров Metal»).


Написав шейдеры, загружаем их в проект Metal с помощью MTLLibrary:


// Создаем устройство Metal
let device = MTLCreateSystemDefaultDevice()

// Создаем библиотеку Metal из MSL-файла
let metalLibrary = device.makeDefaultLibrary()

// Загружаем из библиотеки функцию вершины
let vertexFunction = metalLibrary.makeFunction(name: "vertexShader")

// Загружаем из библиотеки функцию фрагмента
let fragmentFunction = metalLibrary.makeFunction(name: "fragmentShader")

Конвейеры и состояние


Привяжем эти функции к состоянию конвейера:


// Создаем дескриптор конвейера отрисовки
let pipelineDescriptor = MTLRenderPipelineDescriptor()

// Задаем функции вершин и фрагментов
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction

// Создаем состояние конвейера
let pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)

Что такое MTLRenderPipelineDescriptor?


Это объект со свойствами  —  функция вершины, функция фрагмента и состояние трафарета/глубины,  —  которыми настраивается объект состояния конвейера отрисовки.


makeRenderPipelineState(descriptor:)  —  это метод класса MTLDevice, которым из объекта MTLRenderPipelineDescriptor создается новый объект MTLRenderPipelineState: на входе в метод принимается MTLRenderPipelineDescriptor, а на выходе возвращается MTLRenderPipelineState для отрисовки 3D-моделей или изображений.




Применение Metal в контроллере представления


Посмотрим, как с Metal отрисовывается треугольник в UIView или NSView в приложении iOS или macOS соответственно.



Нас интересуют основные этапы настройки представления, создания слоя и отрисовки окружности шейдерами Metal.



1. Настройка исходного представления


Добавим простой UIView в main.storyboard и зададим желаемые высоту, ширину и положение.


Перенесем вывод этого представления в файл Swift и назовем его, например mainView.


Созданное представление добавится к этому, поэтому понадобится его вывод.


2. Настройка представления Metal


Для применения Metal нужно создать пользовательское представление, поддерживаемое слоем Metal.


Это делается созданием подкласса UIView и переопределением свойства layerClass для возвращения класса CAMetalLayer:


class MetalView: UIView {
override class var layerClass: AnyClass {
return CAMetalLayer.self
}
}

Добавляем его объект в главный класс контроллера представления:


let metalView = MetalView()

3. Создание слоя Metal


Настроив представление Metal, вызываем его свойство layer для доступа к опорному слою Metal.


Слой Metal нужен для управления ресурсами графического процессора и воспроизведения отображаемого содержимого:


let metalLayer = self.metalView.layer as? CAMetalLayer
metalLayer?.frame = .init(x: 0, y: 0, width: mainView.frame.width, height: mainView.frame.height)

Здесь задан его фрейм с родительским представлением, ведь нужно отрисовать представление в добавленном статическом представлении.


Чтобы определить положение, нужно еще задать ограничения дочернего представления с родительским:


mainView.addSubview(metalView)
metalView.topAnchor.constraint(equalTo: mainView.topAnchor).isActive = true
metalView.bottomAnchor.constraint(equalTo: mainView.bottomAnchor).isActive = true
metalView.leftAnchor.constraint(equalTo: mainView.leftAnchor).isActive = true
metalView.rightAnchor.constraint(equalTo: mainView.rightAnchor).isActive = true


Добавим все это в метод viewDidLoad(): нужно показать представление Metal с отрисовкой представления.



4. Создание буфера вершин


Чтобы отрисовать треугольник с помощью Metal, создадим буфер вершин с данными вершин треугольника и передадим эти данные в вершинный шейдер:


let vertexData: [Float] = [0.0, 0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 1.0,
0.5, -0.5, 0.0, 1.0]
// Создаем буфер вершин для окружности
let vertexBuffer = device.makeBuffer(bytes: vertexData, length: vertexData.count * MemoryLayout<Float>.size, options: [])

Добавили объект device после объявления объекта metalView:


let device = MTLCreateSystemDefaultDevice()!

Чтобы раскрасить окружность, используем фрагментный шейдер.


Вершинный и фрагментный шейдеры  —  это файловые функции Metal для создания изображения или представления в соответствии с приведенным выше определением.


Вот реализация обеих функций:


vertex float4 vertexShader(constant float3 *vertexArray [[ buffer(0) ]],
uint vid [[ vertex_id ]]) {
float3 ver = vertexArray[vid];
return float4(ver, 1.0);
}

fragment float4 fragmentShader() {
return float4(1.0, 0.5, 0.5, 1.0);
}

Добавляем обе эти функции в .metal file проекта.


5. Создание состояния конвейера отрисовки


Чтобы отрисовать треугольник, создаем объект MTLRenderPipelineState, в котором содержатся шейдеры и другая информация о состоянии:


guard let drawable = metalLayer?.nextDrawable() else { return }

// Создаем дескриптор конвейера отрисовки
let pipelineDescriptor = MTLRenderPipelineDescriptor()

// Получаем определtнные функции Metal из файла Metal
let metalLibrary = device.makeDefaultLibrary()
let vertexFunction = metalLibrary?.makeFunction(name: "vertexShader")
let fragmentFunction = metalLibrary?.makeFunction(name: "fragmentShader")

// Задаем функцию вершин
pipelineDescriptor.vertexFunction = vertexFunction

// Задаем функцию фрагментов
pipelineDescriptor.fragmentFunction = fragmentFunction

// Задаем формат пикселей для конвейера
pipelineDescriptor.colorAttachments[0].pixelFormat = drawable.texture.pixelFormat

do {
// Создаем состояние конвейера отрисовки
let pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch let error {
print("Error: \(error.localizedDescription)")
}

Но этого недостаточно.


6. Отрисовка треугольника


Настроив представление и слой Metal, состояние конвейера и буфер вершин, отрисуем треугольник, создав объекты MTLCommandBuffer и MTLRenderCommandEncoder, закодировав состояние конвейера, буфер вершин и другую информацию о состоянии, а затем отправив буфер команд в графический процессор.


Добавим в блок do этот код:


// Создаем энкодер команд отрисовки
let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
renderPassDescriptor.colorAttachments[0].loadAction = .clear

// Создаем буфер команд
let commandQueue = device.makeCommandQueue()!
let commandBuffer = commandQueue.makeCommandBuffer()!

// Создаем энкодер команд отрисовки
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!

// Задаем состояние конвейера
renderEncoder.setRenderPipelineState(pipelineState)

// Задаем буфер вершин
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)

// Отрисовываем треугольник
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertexData.count)

// Завершаем кодировку
renderEncoder.endEncoding()

// Представляем отрисовываемое
commandBuffer.present(drawable)

// Фиксируем буфер команд
commandBuffer.commit()

Запускаем приложение.


Очень просто, только нужно помнить, в каком порядке все добавляется.


Посмотрим, как это сделать в SwiftUI.




Применение Metal в SwiftUI


Теперь с помощью Metal отрисуем треугольник в SwiftUI.


Единственное отличие от UIView  —  как получается drawable, используемое в Metal для renderEncoder.present.


Чтобы использовать Metal в SwiftUI, создаем MTKView с помощью фреймворка MetalKit, обертываем MTKView в структуру UIViewRepresentable и применяем в представлении SwiftUI:


struct TriangleView: UIViewRepresentable {
func makeUIView(context: Context) -> MTKView {
let view = MTKView()
view.device = MTLCreateSystemDefaultDevice()
view.delegate = context.coordinator
return view
}

func updateUIView(_ uiView: MTKView, context: Context) {}

func makeCoordinator() -> Coordinator {
Coordinator()
}

class Coordinator: NSObject, MTKViewDelegate {

func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}

func draw(in view: MTKView) {
.
.
let descriptor = view.currentRenderPassDescriptor
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)
guard let drawable = view.currentDrawable else { return }
.
.
}
}

В реализации SwiftUI все остальное то же, что у ViewController.


Здесь мы создаем drawable из MTKView, а не из CAMetalLayer.


Применим это представление в ContentView для отображения треугольника:


struct ContentView: View {

var body: some View {
VStack {
TriangleView()
.frame(width: 230, height: 200, alignment: .center)
Text("Hello, world!")
}
.padding()
}
}

Вот и все.


Заключение


Мы рассмотрели основы использования API-интерфейсов Metal для отрисовки треугольника в UIView и в SwiftUI, настроили представление, создали слой Metal и отрисовали окружность с помощью шейдеров Metal. Кроме того, теперь у вас есть фрагменты кода и рекомендации для применения Metal в собственном приложении iOS или macOS.


Хотя этот пример относительно простой, мощь и гибкость API-интерфейсов Metal им демонстрируется. С Metal можно создавать сложную и высокооптимизированную 3D-графику, обрабатывать изображения, выполнять другие задачи с ускорением графического процессора.


Для дальнейшего изучения Metal рекомендуем ознакомиться с официальным руководством по программированию Metal и справочником по фреймворку Metal от Apple.


Кроме того, в интернете имеется много руководств, примеров кода и других ресурсов для освоения Metal и его использования в приложениях.



44   0  
    Ничего не найдено.

Добавить ответ:
Отменить.