Преобразование шейдера OpenGL в Metal (Swift) для использования в CIFilter

Я новичок в OpenGL/Metal и пытаюсь понять некоторые фундаментальные понятия.
В нашем приложении мы используем CIFilter для фильтрации видео. Я видел WWDC видео от 2017 года, объясняющее, что вы можете обернуть CIFilter с Metal и использовать его как обычный фильтр.
Я пытаюсь понять, как преобразовать этот OpenGL видеоэффект в Metal, чтобы я мог использовать его в качестве ориентира для будущих эффектов.

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
float amount = sin(iTime) * 0.1;

// uv coords
vec2 uv = fragCoord / iResolution.xy;

amount *= 0.3;
float split = 1. - fract(iTime / 2.);
float scanOffset = 0.01;
vec2 uv1 = vec2(uv.x + amount, uv.y);
vec2 uv2 = vec2(uv.x, uv.y + amount);
if (uv.y > split) {
    uv.x += scanOffset;
    uv1.x += scanOffset;
    uv2.x += scanOffset;
}


float r = texture(iChannel0, uv1).r;
float g = texture(iChannel0, uv).g;
float b = texture(iChannel0, uv2).b;

fragColor = vec4(r, g, b, 1.);

}

Что производит:

Преобразование шейдера OpenGL в Metal (Swift) для использования в CIFilter

После преобразования кода OpenGL в Metal я использую оболочку CIFilter, чтобы использовать его с AVPlayerItem:

class MetalFilter: CIFilter {

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

private let kernel: CIKernel
var inputImage: CIImage?

override init() {
    let url = Bundle.main.url(forResource: "default", withExtension: "metallib")!
    let data = try! Data(contentsOf: url)
    kernel = try! CIKernel(functionName: "vhs", fromMetalLibraryData: data)
    super.init()
}


func outputImage() -> CIImage? {
    guard let inputImage = inputImage else {return nil}
    let sourceSize = inputImage.extent.size
    let outputImage = kernel.apply(extent: CGRect(x: 0, y: 0, width: sourceSize.width, height: sourceSize.height), roiCallback: { index, destRect in
        return destRect
    }, arguments: [inputImage, NSNumber(value: Float(1.0 / sourceSize.width)), NSNumber(value: Float(1.0 / sourceSize.height)), NSNumber(value: 60.0)])

    return outputImage
   }
}

Любая помощь будет высоко оценена!

Ваша реализация MetalFilter пока выглядит нормально. С чем вы боретесь?

Frank Schlegel 20.07.2019 11:35

@FrankSchlegel Эй, Фрэнк, спасибо за ответ. Я пытаюсь понять, как преобразовать код фильтра OpenGL в Metal (файл filename.metal). Есть ли руководство о том, как это сделать?

Roi Mulia 20.07.2019 13:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
2
1 779
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я попробовал. Вот код ядра:

#include <metal_stdlib>
using namespace metal;
#include <CoreImage/CoreImage.h>

extern "C" { namespace coreimage {

    float4 vhs(sampler_h src, float time, float amount) {
        const float magnitude = sin(time) * 0.1 * amount;

        float2 greenCoord = src.coord(); // this is alreay in relative coords; no need to devide by image size

        const float split = 1.0 - fract(time / 2.0);
        const float scanOffset = 0.01;
        float2 redCoord = float2(greenCoord.x + magnitude, greenCoord.y);
        float2 blueCoord = float2(greenCoord.x, greenCoord.y + magnitude);
        if (greenCoord.y > split) {
            greenCoord.x += scanOffset;
            redCoord.x += scanOffset;
            blueCoord.x += scanOffset;
        }

        float r = src.sample(redCoord).r;
        float g = src.sample(greenCoord).g;
        float b = src.sample(blueCoord).b;

        return float4(r, g, b, 1.0);
    }

}}

А вот небольшая корректировка outputImage в вашем фильтре:

override var outputImage: CIImage? {
    guard let inputImage = self.inputImage else { return nil }

    // could be filter parameters
    let inputTime: NSNumber = 60
    let inputAmount: NSNumber = 0.3

    // You need to tell the kernel the region of interest of the input image,
    // i.e. what region of input pixels you need to read for a given output region.
    // Since you sample pixels to the right and below the center pixel, you need
    // to extend the ROI accordingly.
    let magnitude = CGFloat(sin(inputTime.floatValue) * 0.1 * inputAmount.floatValue)
    let inputExtent = inputImage.extent

    let roiCallback: CIKernelROICallback = { _, rect -> CGRect in
        return CGRect(x: rect.minX, y: rect.minY,
                      width: rect.width + (magnitude + 0.01) * inputExtent.width, // scanOffset
                      height: rect.height + magnitude * inputExtent.height)
    }

    return self.kernel.apply(extent: inputExtent,
                             roiCallback: roiCallback,
                             arguments: [inputImage, inputTime, inputAmount])
}

Привет, Фрэнк! Большое спасибо! Это работает :) Теперь я могу использовать его в качестве ориентира :) Супер!

Roi Mulia 20.07.2019 14:36

Хороший! Рад, что смог помочь. Фильтры Core Image Metal и API, к сожалению, очень плохо документированы... Дайте мне знать, если у вас возникнут дополнительные вопросы.

Frank Schlegel 20.07.2019 14:38

У вас есть рекомендации, что/где почитать на эту тему, чтобы я мог лучше понять, как преобразовать фильтр OpenGL в металл самостоятельно? Я пытаюсь понять, какие «ключевые фразы» мне следует искать. Например, я даже не был уверен, подходит ли сюда слово «Шейдер».

Roi Mulia 20.07.2019 14:38

Я думаю, что «ядро фильтра» было бы правильным словом. Честно говоря, лучший (и почти единственный) источник документации от Apple — это сессия WWDC, которую вы уже смотрели. Возможно, также ознакомьтесь с теми, что были в 2018 году, в которых отмечены некоторые недавние улучшения. И вот один очень полезный PDF-файл: developer.apple.com/metal/MetalCIKLReference6.pdf. И попробуйте нажать Cmd+Shift+O в Xcode и открыть CIKernelMetalLib.h. Это заголовок, содержащий все дополнения к Metal, связанные с CI, и некоторую документацию.

Frank Schlegel 20.07.2019 14:45

Спасибо, Фрэнк. Я посмотрю, собираюсь провести выходные, обучая себя этому :) Также, могу ли я спросить: сколько фактического прироста производительности мы получаем, используя Metal с CIFilter, по сравнению с использованием кода OpenGL в нашем приложении? Если мы разместим их рядом, производительность металлической обертки заметно улучшится?

Roi Mulia 20.07.2019 14:49

И последний небольшой вопрос, касающийся этого конкретного фильтра. Я видел, что inputTime было статическим в свойстве outputImage. Вместо этого я сделал его свойством класса MetalFilter и каждому кадру присваиваю его значение по времени композиции filter.inputTime = NSNumber(value: CMTimeGetSeconds(request.compositionTime)). Теперь я действительно вижу, как линии фильтра поднимаются и опускаются (:D). Это правильный способ сделать это?

Roi Mulia 20.07.2019 14:52

Я думаю, это зависит от сложности вашего конвейера фильтров. Для этого простого фильтра вы, вероятно, не заметите разницы. Но если вы соедините множество фильтров вместе, среда выполнения CI может выполнить большую внутреннюю оптимизацию. Кроме того, Apple явно осуждает OpenGL везде, так что Metal в любом случае — это путь.

Frank Schlegel 20.07.2019 14:53

Это! Если вы хотите максимально имитировать CI API, вы должны объявить свойство следующим образом @objc dynamic var inputTime: NSNumber, а затем установить значение с помощью filter.setValue(CMTimeGetSeconds(request.compositionTime), forKey: "inputTime")

Frank Schlegel 20.07.2019 14:55

Потрясающий. Еще раз спасибо! Желаем вам отличных и спокойных выходных :)

Roi Mulia 20.07.2019 14:57

Ребята, это потрясающе и очень помогает мне, новичку в шейдерах, очень!

meaning-matters 27.02.2021 20:34

Другие вопросы по теме