首页 > 解决方案 > Core Image颜色内核的Metal Shading语言,如何传递一个float3数组

问题描述

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

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

内核应该采用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
    }

这是内核:

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);
    }

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

标签: iosmetalcore-imagecifiltermetalkit

解决方案


Core Image 内核似乎不支持指针或数组参数类型。虽然 iOS 13 似乎有一些东西。来自发行说明:

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

但是,与 Core Image 一样,似乎没有进一步的文档......</p>

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

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

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

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

kernel.apply(..., arguments: [dataImage.samplingNearest(), ...])

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

extern "C" float4 myKernel(coreimage::sampler data, ...) {
    float4 data0 = data.sample(float2(0.5, 0.5)); // data[0]
    float4 data1 = data.sample(float2(1.5, 0.5)); // data[1]
    // ...
}

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

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

float data0 = data.sample(float2(0.5, 0.5)).x;

推荐阅读