【问题标题】:Metal Shading language for Core Image color kernel, how to pass an array of float3Core Image颜色内核的Metal Shading语言,如何传递一个float3数组
【发布时间】:2020-01-05 04:17:54
【问题描述】:

我正在尝试通过对 Core Image 使用金属着色语言从 source 移植一些 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,它是一种 subset,仅用于处理片段。

【问题讨论】:

    标签: ios metal core-image cifilter metalkit


    【解决方案1】:

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

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

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

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

        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)
    

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

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

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

    在您的 (Metal) 内核中,您可以像使用普通输入图像一样通过 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,以便它们指向数据图像中像素的中间,以避免歧义和插值。

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

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

    【讨论】:

    • 谢谢你,弗兰克,我会调查一下,目前 +1
    • @frank-schlegel 你能开发出如何在金属着色器中接收和操作另一侧的 dataImage 吗?我们应该在着色器中为它使用哪种类型?
    • @yageek 我扩展了我的答案。 ?
    • 太棒了?它帮助了很多像我这样的菜鸟。
    • @FrankSchlegel 你能看看这个问题吗-stackoverflow.com/questions/70108851/…
    【解决方案2】:

    我做到了,如果答案很好并且还部署到较低的目标,结果并不完全符合我的预期。以字符串形式编写的原始内核与上述创建图像用作数据源的方法之间的差异很大。
    没有得到确切的原因,但我作为调色板源传递的图像在大小和颜色上与创建的图像有点不同(可能是由于色彩空间)。
    由于没有关于此声明的文档:

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

    我在业余时间尝试了很多,并想出了这个。
    首先是着色器:

       float4 eight_bit_buffer(sampler image, constant simd_float3 palette[], float paletteSize, destination dest) {
            float4 color = image.sample(image.transform(dest.coord()));
    
            float dist = distance(color.rgb, palette[0]);
            float3 returnColor = palette[0];
            for (int i = 1; i < floor(paletteSize); ++i) {
                float tempDist = distance(color.rgb, palette[i]);
                if (tempDist < dist) {
                    dist = tempDist;
                    returnColor = palette[i];
                }
            }
            return float4(returnColor, 1);
        }
    

    调色板二次转化为SIMD3&lt;Float&gt;

     static func toSIMD3Buffer(from palette: [RGB]) -> Data {
            var simd3Palette = SIMD3Palette(palette)
            let size = MemoryLayout<SIMD3<Float>>.size
            let count = palette.count * size
            let palettePointer = UnsafeMutableRawPointer.allocate(
                byteCount: simd3Palette.count * MemoryLayout<SIMD3<Float>>.stride,
                alignment: MemoryLayout<SIMD3<Float>>.alignment)
            let simd3Pointer = simd3Palette.withUnsafeMutableBufferPointer { (buffer) -> UnsafeMutablePointer<SIMD3<Float>> in
                let p = palettePointer.initializeMemory(as: SIMD3<Float>.self,
                from: buffer.baseAddress!,
                count: buffer.count)
                return p
            }
            let data = Data(bytesNoCopy: simd3Pointer, count: count * MemoryLayout<SIMD3<Float>>.stride, deallocator: .free)
    
            return data
        }
    

    我第一次尝试将 SIMD3 附加到 Data 对象但可能由于内存对齐而无法正常工作。 记得释放使用后创建的内存。
    希望能帮助到别人。

    【讨论】:

      猜你喜欢
      • 2020-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-03
      • 2020-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多