Metal Shading语言用于Core Image颜色内核,如何传递float3数组

And*_*rea 5 core-image ios cifilter metal metalkit

我正在尝试通过使用 Core Image 的金属着色语言CIFilter从该移植一些内容。
我有一个由结构数组组成的调色板RGB,我想将它们作为参数传递给自定义 CI 彩色图像内核。
RGB 结构体被转换为SIMD3<Float>.

 static func SIMD3Palette(_ palette: [RGB]) -> [SIMD3<Float>] {
        return palette.map{$0.toFloat3()}
    }
Run Code Online (Sandbox Code Playgroud)

内核应该采用simd_float3值数组,问题是当我启动过滤器时,它告诉我索引 1 处的参数需要一个NSData.

override var outputImage: CIImage? {
        guard let inputImage = inputImage else
        {
            return nil
        }
         let palette = EightBitColorFilter.palettes[Int(inputPaletteIndex)]
        let extent = inputImage.extent
        let arguments = [inputImage, palette, Float(palette.count)] as [Any]

        let final = colorKernel.apply(extent: extent, arguments: arguments)

        return final
    }
Run Code Online (Sandbox Code Playgroud)

这是内核:

float4 eight_bit(sample_t image, simd_float3 palette[], float paletteSize, destination dest) {
        float dist = distance(image.rgb, palette[0]);
        float3 returnColor = palette[0];
        for (int i = 1; i < floor(paletteSize); ++i) {
            float tempDist = distance(image.rgb, palette[i]);
            if (tempDist < dist) {
                dist = tempDist;
                returnColor = palette[i];
            }
        }
        return float4(returnColor, 1);
    }
Run Code Online (Sandbox Code Playgroud)

我想知道如何将数据缓冲区传递到内核,因为将其转换为 NSData 似乎还不够。
我看到了一些例子,但他们使用的是“完整”着色语言,该语言不适用于核心图像,它是一种仅处理片段的子集。

Fra*_*gel 6

更新

\n

我们现在已经弄清楚如何将数据缓冲区直接传递到 Core Image 内核中。CIImage不需要使用下面描述的 a ,但仍然可以。

\n

假设您的原始数据为NSData,您可以在调用时将其传递给内核:

\n
kernel.apply(..., arguments: [data, ...])\n
Run Code Online (Sandbox Code Playgroud)\n

注意:Data也可能有效,但我知道这NSData是一种参数类型,允许 Core Image 根据输入参数缓存过滤器结果。因此,当有疑问时,最好转换为NSData.

\n

然后在内核函数中,您只需要声明适当的constant类型的参数:

\n
extern "C" float4 myKernel(constant float3 data[], ...) {\n    float3 data0 = data[0];\n    // ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

上一个答案

\n

Core Image 内核似乎不支持指针或数组参数类型。尽管 iOS 13 似乎会带来一些新的东西。从发行说明来看:

\n
\n

Metal CIKernel 实例支持具有任意结构化数据的参数。

\n
\n

但是,就像 Core Image 经常出现的那样,似乎没有进一步的文档\xe2\x80\xa6

\n

但是,您仍然可以使用传递缓冲区数据的“旧方法”,将其包装在 a 中CIImage并在内核中对其进行采样。例如:

\n
    let array: [Float] = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]\n    let data = array.withUnsafeBufferPointer { Data(buffer: $0) }\n    let dataImage = CIImage(bitmapData: data, bytesPerRow: data.count, size: CGSize(width: array.count/4, height: 1), format: .RGBAf, colorSpace: nil)\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,没有CIFormat3 通道图像,因为 GPU 不支持这些图像。因此,您要么必须使用单通道.Rf并再次重新打包内核中的值float3,要么向数据中添加一些步幅并分别使用.RGBAffloat4(我推荐这样做,因为它减少了纹理获取)。

\n

当您将该图像传递到内核时,您可能希望将采样模式设置为nearest,否则在两个像素之间采样时可能会得到插值:

\n
kernel.apply(..., arguments: [dataImage.samplingNearest(), ...])\n
Run Code Online (Sandbox Code Playgroud)\n

在您的(Metal)内核中,您可以像使用普通输入图像一样通过以下方式评估数据sampler

\n
extern "C" float4 myKernel(coreimage::sampler data, ...) {\n    float4 data0 = data.sample(data.transform(float2(0.5, 0.5))); // data[0]\n    float4 data1 = data.sample(data.transform(float2(1.5, 0.5))); // data[1]\n    // ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,我添加了0.5坐标,以便它们指向数据图像中像素的中间,以避免模糊和插值。

\n

另请注意,您从 a 获得的像素值sampler始终有 4 个通道。因此,即使您使用 formate 创建数据图像.Rf,采样时也会得到 a float4(其他值填充0.0为 G 和 B 以及1.0alpha)。在这种情况下,你可以这样做

\n
float data0 = data.sample(data.transform(float2(0.5, 0.5))).x;\n
Run Code Online (Sandbox Code Playgroud)\n

编辑

\n

我之前忘记将样本坐标从绝对像素空间(其中(0.5, 0.5)将是第一个像素的中间)转换为相对采样器空间(其中(0.5, 0.5)将是整个缓冲区的中间)。现在已经修好了。

\n