ios 针对Core Image颜色内核的金属着色语言,如何传递float3数组

5ktev3wc  于 2022-11-19  发布在  iOS
关注(0)|答案(2)|浏览(94)

我 试图 通过 使用 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 似乎 还 不够 。
我 看到 了 一些 例子 , 但 他们 使用 的 是 " 完整 " 着色 语言 , 这 是 一 种 subset , 仅 用于 处理 碎片 的 Core Image 所 无法 使用 的 。

0pizxfdo

0pizxfdo1#

更新

  • 我们现在已经知道了如何将数据缓冲区直接传递到Core Image内核。虽然不需要使用下面描述的CIImage,但仍然可以使用。*

假设原始数据是NSData,那么只需在调用时将其传递给内核即可:

kernel.apply(..., arguments: [data, ...])
  • 注意:Data也可以工作,但我知道NSData是一个参数类型,它允许Core Image缓存基于输入参数的筛选结果。因此,如果不确定,最好强制转换为NSData。*

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

extern "C" float4 myKernel(constant float3 data[], ...) {
    float3 data0 = data[0];
    // ...
}

上一个答案

Core Image内核似乎不支持指针或数组参数类型。尽管iOS 13似乎有一些功能。
Metal CIKernel示例支持具有任意结构化数据的参数。
但是,正如经常与核心形象,似乎没有进一步的文档,这一点...
但是,您仍然可以使用传递缓冲区数据的“老方法”,即将其 Package 在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)

请注意,由于GPU不支持3通道图像,因此没有CIFormat。因此,您必须使用单通道.Rf,并将内核中的值重新打包为float3,或者向数据添加一些步长,并分别使用.RGBAffloat4(我建议这样做,因为它可以减少纹理提取)。
当您将该图像传递到内核中时,可能需要将采样模式设置为nearest,否则在两个像素之间采样时可能会得到插值:

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

在(Metal)内核中,您可以像处理普通输入图像一样通过sampler

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

请注意,我将0.5添加到坐标中,以便它们指向数据图像中像素的 * 中间 *,以避免模糊和插值。
还要注意,从sampler中获取的像素值总是有4个通道。因此,即使您使用formate .Rf创建数据图像,在采样时也会得到float4(其他值用0.0填充G和B,用1.0填充alpha)。在这种情况下,您只需

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

编辑

我之前忘了把采样坐标从绝对像素空间((0.5, 0.5)是第一个像素的中间)转换到相对采样空间((0.5, 0.5)是整个缓冲区的中间),现在已经修复了。

velaa5lx

velaa5lx2#

我做了它,事件,如果答案是好的,也部署到较低的目标,结果并不完全是我所期望的.原始内核写为字符串和上面的方法之间的差异创建一个图像作为数据源是一种大.
没有得到确切的原因,但我传递的图像作为调色板的来源是一种不同的大小和颜色(可能是由于颜色空间)创建的一个。
由于没有关于此语句的文档:
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<Float>

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对象,但可能是由于内存对齐的原因,没有工作。记住在使用它之后取消分配所创建的内存。
希望能帮到别人。

相关问题