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

Я новичок в _1 _ / _ 2_ и пытаюсь понять некоторые фундаментальные концепции.
В нашем приложении мы используем 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 я использую оболочку 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
   }
}

Любая помощь будет принята с благодарностью!


person Roi Mulia    schedule 20.07.2019    source источник
comment
Ваша реализация MetalFilter пока кажется удовлетворительной. С чем вы боретесь?   -  person Frank Schlegel    schedule 20.07.2019
comment
@FrankSchlegel Привет, Франк, спасибо за ответ. Я пытаюсь понять, как преобразовать код фильтра OpenGL в Metal (файл filename.metal). Есть ли инструкция, как это сделать?   -  person Roi Mulia    schedule 20.07.2019


Ответы (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])
}
person Frank Schlegel    schedule 20.07.2019
comment
Привет, Фрэнк! Большое спасибо! Работает :) Теперь я могу использовать это как ориентир :) Superbe! - person Roi Mulia; 20.07.2019
comment
Отлично! Рад, что смог помочь. К сожалению, фильтры и API Core Image Metal очень скудно документированы ... Дайте мне знать, если у вас возникнут дополнительные вопросы. - person Frank Schlegel; 20.07.2019
comment
У вас есть рекомендация, что / где почитать по этим темам, чтобы я мог лучше понять, как самому преобразовать фильтр OpenGL в металл? Я пытаюсь понять, какие ключевые фразы мне следует искать. Например, я даже не был уверен, подходит ли здесь слово шейдер. - person Roi Mulia; 20.07.2019
comment
Я думаю, «ядро фильтра» было бы правильным словом здесь. Честно говоря, лучший (и почти единственный) источник документации от Apple - это сеанс WWDC, который вы уже смотрели. Возможно, также посмотрите те из 2018, которые подчеркивают некоторые недавние улучшения. И вот еще один очень полезный PDF-файл: developer.apple.com/metal/MetalCIKLReference6.pdf. И попробуйте нажать Cmd + Shift + O в Xcode и открыть CIKernelMetalLib.h. Это заголовок, содержащий все дополнения к Metal, связанные с CI, и некоторую документацию. - person Frank Schlegel; 20.07.2019
comment
Спасибо, Фрэнк. Я посмотрю, собираюсь провести выходные, обучая себя этому :) Также, позвольте мне спросить: сколько фактического прироста производительности мы получаем при использовании Metal с CIFilter по сравнению с использованием кода OpenGL в нашем приложении? Если мы разместим их рядом, будет ли заметно лучше у металлической обертки? - person Roi Mulia; 20.07.2019
comment
И последний небольшой вопрос, касающийся этого конкретного фильтра. Я видел, что inputTime был статическим в свойстве outputImage. Вместо этого я сделал его свойством класса MetalFilter, и каждому кадру я присваиваю его значение по времени композиции filter.inputTime = NSNumber(value: CMTimeGetSeconds(request.compositionTime)). Теперь я могу видеть, как линии фильтра идут вверх и вниз (: D). Это правильный способ сделать это? - person Roi Mulia; 20.07.2019
comment
Я думаю, это зависит от сложности вашего конвейера фильтров. Для этого простого фильтра вы, вероятно, не заметите разницы. Но если вы объедините в цепочку несколько фильтров, среда выполнения CI может провести отличную оптимизацию под капотом. Кроме того, Apple явно отказывается от OpenGL повсюду, так что Metal в любом случае подходит. - person Frank Schlegel; 20.07.2019
comment
Это! Если вы хотите максимально имитировать CI API, вы должны объявить свойство, подобное этому @objc dynamic var inputTime: NSNumber, а затем установить значение с помощью filter.setValue(CMTimeGetSeconds(request.compositionTime), forKey: "inputTime") - person Frank Schlegel; 20.07.2019
comment
Потрясающие. Еще раз спасибо! Желаю вам отличных и спокойных выходных :) - person Roi Mulia; 20.07.2019
comment
Ребята, это потрясающе и очень помогает мне, поскольку я совершенно не знаком с шейдерами! - person meaning-matters; 27.02.2021