Идея:

Я задумал разработать приложение, в котором можно было бы применять живые фильтры к видео. Я проверил приложение для клипов, но в нем не было дополнительных возможностей для фильтров. Итак, я продолжил исследования, столкнувшись с проблемами с множеством решений, которые я объясню ниже.

CIFilter:

Первоначально я думал, что смогу сделать это в CIFilters, но у CIFilters было много проблем, упомянутых ниже,

  1. Несмотря на то, что это очень легко реализовать на изображениях, это не было настраиваемым для видеобуферов, поэтому я не мог глубоко изменять буферы.
  2. У процессора было много накладных расходов на память. Большинство фильтров будут работать непосредственно на ЦП, что снизит производительность.
  3. Нелегко добавить несколько фильтров в один буфер.

Учитывая вышеупомянутые проблемы с фильтрами, я изменил свое мнение и стал рассматривать другие платформы, и первое, что мне пришло в голову, это OpenGL ES.

OpenGL ES:

OpenGL ES - это низкоуровневый API, ориентированный на GPU. Хотя это очень гибкий и базовый инструмент для работы с графикой, он требует очень крутого обучения.

Изначально я изучал основы OpenGL с помощью этой Книги. ((Вы можете получить бесплатный pdf по этой ссылке).

Сложность OpenGL в том, что он очень универсален с целью C, и вам нужно написать множество вспомогательных функций, чтобы быстро справиться с API OpenGL.

Видеофильтр можно было бы легко сделать с помощью OpenGL, но когда я просмотрел Metal, я обнаружил надежность с swift, потому что API более быстрые. Более того, у него гораздо больше преимуществ перед OpenGL, поскольку он имеет как функции OpenGL, так и OpenCL, а также имеет низкую нагрузку на ЦП. Металлические шейдеры компилируются во время сборки. Таким образом, на компиляцию шейдеров во время выполнения экономится больше времени.

Почему металл?

С введением Metal в iOS 8 Apple обновила свою 3D-графику с ускорением на GPU. Metal похож на OpenGL, но он разработан специально для устройств Apple, чтобы снизить накладные расходы памяти на GPU.

На самом деле у нас может быть в 10 раз больше вызовов отрисовки, чем у OpenGL. Чтобы узнать больше о том, почему металл лучше исполняет, посмотрите это видео.

Вот изображение производительности в OpenGL. Между CPU и GPU должна быть синхронная связь. На изображении верхняя полоса - это время ЦП, которое включает время API-интерфейса приложения и графического процессора. Как показано на желтой полосе, время API графического процессора больше, чем время работы графического процессора с буфером. Из-за этого GPU должен какое-то время находиться в состоянии ожидания с помощью семафоров.

Но в случае с металлом наблюдается резкое сокращение времени API графического процессора, затрачиваемого на процессор.

Таким образом, мы можем вызывать в 10 раз больше вызовов отрисовки.

Это действительно побудило меня погрузиться в металл.

Металл:

По сути, это шаги, которые нужно выполнить в Metal перед рендерингом.

Для получения дополнительной информации о базовом рендеринге металла прочтите документацию Apple Developer и пройдите Hello Triangle.

Металлические ядра:

Язык ядра Metal основан на C ++ для выполнения операций с буферами или пикселями, которые должны быть отрисованы. Существует 3 типа шейдеров ядра Metal.

  1. Вычислить шейдеры
  2. Вершинные шейдеры
  3. Фрагментные шейдеры

Вычислить шейдеры:

Используя вычислительный шейдер, вы можете выполнять базовую фильтрацию с фильтрами по умолчанию, предоставленными Apple (см. Это).

Вот как вы можете добавлять собственные фильтры с помощью CIFilters. Сначала создайте образец ядра, подобный этому. Это файл .metal.

Затем создайте состояние конвейера, а затем используйте эту функцию для создания состояния конвейера.

Конечная цель - закодировать исходную текстуру (буфер) в целевую текстуру с необходимыми модификациями с использованием этого кода.

Вы можете применять фильтры с фильтрами по умолчанию, описанными в следующем коде.

Некоторые из фильтров по умолчанию

  1. MPSImageSobel,
  2. MPSImageThresholdBinary,
  3. MPSImageConvolution,
  4. MPSImageGaussianBlur,

(Для получения дополнительной информации просмотрите эту Документацию Apple, чтобы узнать о дополнительных фильтрах.)

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

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

Вершинные и фрагментные шейдеры:

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

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

Как вы знаете, примитивная геометрия в любой графике - это треугольник. Итак, чтобы создать прямоугольное отображение, нам нужно объединить два треугольника.

Здесь вы можете видеть, что прямоугольник создан из двух треугольников. один - ABC, другой - ACD. Та же концепция применяется в металле.

Используя эту концепцию и координаты текстуры (см. Базовое текстурирование и Hello Triangle) создайте 6 вершин для создания прямоугольника. Передайте все эти вершины, создав буферы в функцию Vertex.

Простая вершинная функция показана в следующем коде.

После процесса растеризации управление перейдет к функции фрагментации.

Основные манипуляции с пикселями происходят в функции фрагмента, и она создает выходной пиксель в соответствии с логикой написанного кода. Что касается нашего требования, нам пришлось разделить прямоугольник на 4 части и визуализировать разные фильтры в каждой части. Концепция поясняется ниже. (Это записано в файле .metal).

Допустим, нам нужно 4 сетки на нашем дисплее,

  1. Первый шаг - использовать лучший сэмплер для ваших требований (прочтите Сэмплеры в Metal Shading Language). Я использовал линейные фильтры, которые дали желаемый результат. После этого создайте образец исходной текстуры (colorSample).
  2. Затем найдите позицию в сетке текущего пикселя (P), который равен currentGridPosition, как показано на рисунке.
  3. Если вам нужно 4 сетки, тогда в массиве rgbColors будет 4 объекта, и после того, как вы узнаете, каковы текущие позиции пикселей (currentGridPosition), один из объектов из rgbColors и возьмем все три красного, зеленого и синего цветов. компоненты из текущего объекта.
  4. Следующий шаг - просто умножить компоненты RGB на образец исходной текстуры.
  5. Наконец, наша выходная текстура готова к возврату и рисованию на экране.

Это простая реализация с использованием функции фрагмента. Если вашему фильтру нужны дополнительные параметры, такие как яркость, контраст экспозиции и т. Д., Вам нужно выполнить много вычислений внутри функции фрагмента. Чтобы избежать такой двусмысленности, выполняйте все эти статические вычисления вне функции фрагмента (при кодировании буферов в функцию фрагмента на ЦП), чтобы снизить накладные расходы производительности на GPU.

Вот и все, на вашем дисплее появился простой набор живых фильтров.