Идея:
Я задумал разработать приложение, в котором можно было бы применять живые фильтры к видео. Я проверил приложение для клипов, но в нем не было дополнительных возможностей для фильтров. Итак, я продолжил исследования, столкнувшись с проблемами с множеством решений, которые я объясню ниже.
CIFilter:
Первоначально я думал, что смогу сделать это в CIFilters, но у CIFilters было много проблем, упомянутых ниже,
- Несмотря на то, что это очень легко реализовать на изображениях, это не было настраиваемым для видеобуферов, поэтому я не мог глубоко изменять буферы.
- У процессора было много накладных расходов на память. Большинство фильтров будут работать непосредственно на ЦП, что снизит производительность.
- Нелегко добавить несколько фильтров в один буфер.
Учитывая вышеупомянутые проблемы с фильтрами, я изменил свое мнение и стал рассматривать другие платформы, и первое, что мне пришло в голову, это 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.
- Вычислить шейдеры
- Вершинные шейдеры
- Фрагментные шейдеры
Вычислить шейдеры:
Используя вычислительный шейдер, вы можете выполнять базовую фильтрацию с фильтрами по умолчанию, предоставленными Apple (см. Это).
Вот как вы можете добавлять собственные фильтры с помощью CIFilters. Сначала создайте образец ядра, подобный этому. Это файл .metal.
Затем создайте состояние конвейера, а затем используйте эту функцию для создания состояния конвейера.
Конечная цель - закодировать исходную текстуру (буфер) в целевую текстуру с необходимыми модификациями с использованием этого кода.
Вы можете применять фильтры с фильтрами по умолчанию, описанными в следующем коде.
Некоторые из фильтров по умолчанию
- MPSImageSobel,
- MPSImageThresholdBinary,
- MPSImageConvolution,
- MPSImageGaussianBlur,
(Для получения дополнительной информации просмотрите эту Документацию Apple, чтобы узнать о дополнительных фильтрах.)
Здесь вы можете видеть, что вы также можете указать смещение целевой текстуры в отличие от исходной текстуры, что дает вам контроль над тем, как вы хотите переместить целевую текстуру при прокрутке представления коллекции.
Наконец, используя представление коллекции и отображая разные фильтры для каждой ячейки и обрезая прямоугольник, в сочетании с изменением ограничений по мере и при прокрутке, вы можете получить следующий результат сетки фильтров:
Вершинные и фрагментные шейдеры:
Как я уже упоминал выше, мы использовали текстуры в каждой ячейке, производительность была ниже номинальной, и было много сбоев из-за проблем с памятью. Итак, мы продолжили исследование того, как мы могли бы придумать лучший подход. Это вершинные и фрагментные шейдеры.
Наконец, этот вопрос о переполнении стека прояснил меня, что с помощью вершинных и фрагментных шейдеров можно добиться всего. Поэтому, прежде чем углубляться в вершинные и фрагментные шейдеры, позвольте мне объяснить концепцию реализации.
Как вы знаете, примитивная геометрия в любой графике - это треугольник. Итак, чтобы создать прямоугольное отображение, нам нужно объединить два треугольника.
Здесь вы можете видеть, что прямоугольник создан из двух треугольников. один - ABC, другой - ACD. Та же концепция применяется в металле.
Используя эту концепцию и координаты текстуры (см. Базовое текстурирование и Hello Triangle) создайте 6 вершин для создания прямоугольника. Передайте все эти вершины, создав буферы в функцию Vertex.
Простая вершинная функция показана в следующем коде.
После процесса растеризации управление перейдет к функции фрагментации.
Основные манипуляции с пикселями происходят в функции фрагмента, и она создает выходной пиксель в соответствии с логикой написанного кода. Что касается нашего требования, нам пришлось разделить прямоугольник на 4 части и визуализировать разные фильтры в каждой части. Концепция поясняется ниже. (Это записано в файле .metal).
Допустим, нам нужно 4 сетки на нашем дисплее,
- Первый шаг - использовать лучший сэмплер для ваших требований (прочтите Сэмплеры в Metal Shading Language). Я использовал линейные фильтры, которые дали желаемый результат. После этого создайте образец исходной текстуры (colorSample).
- Затем найдите позицию в сетке текущего пикселя (P), который равен currentGridPosition, как показано на рисунке.
- Если вам нужно 4 сетки, тогда в массиве rgbColors будет 4 объекта, и после того, как вы узнаете, каковы текущие позиции пикселей (currentGridPosition), один из объектов из rgbColors и возьмем все три красного, зеленого и синего цветов. компоненты из текущего объекта.
- Следующий шаг - просто умножить компоненты RGB на образец исходной текстуры.
- Наконец, наша выходная текстура готова к возврату и рисованию на экране.
Это простая реализация с использованием функции фрагмента. Если вашему фильтру нужны дополнительные параметры, такие как яркость, контраст экспозиции и т. Д., Вам нужно выполнить много вычислений внутри функции фрагмента. Чтобы избежать такой двусмысленности, выполняйте все эти статические вычисления вне функции фрагмента (при кодировании буферов в функцию фрагмента на ЦП), чтобы снизить накладные расходы производительности на GPU.
Вот и все, на вашем дисплее появился простой набор живых фильтров.