CoreImage:CIColorKernel.init 与 .metallib 导致 EXEC_BAD_ACCESS

CoreImage: CIColorKernel.init with .metallib causes EXEC_BAD_ACCESS

我使用 Metal Shader Language 为 CoreImage 制作了一个 Perlin Noise 过滤器。我的第一个版本运行良好,允许我指定 2 种颜色用作 lowhigh。我的下一步是让它接受颜色图,以便它可以产生多色输出。颜色映射版本编译正常,但是当我的 CIFilter 子类尝试使用 init(functionName:fromMetalLibraryData:) 方法从它创建 CIColorKernel 时,我得到一个 EXEC_BAD_ACCESScode=1。知道我的颜色映射实现可能会导致这种情况吗?

这是简单的 2 色版本:

#include <CoreImage/CoreImage.h>
using namespace metal;

constant int p[512] = {151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180};

float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
float grad(int hash, float x, float y, float z) {
    int h = hash & 15;
    float u = h < 8 ? x : y;
    float v = h < 4 ? y : h == 12 || h == 14 ? x : z;
    return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
float lerp(float t, float x, float y) { return x + t * (y - x); }

float noise(float x, float y, float z) {
    int X = (int)floor(x) & 255;
    int Y = (int)floor(y) & 255;
    int Z = (int)floor(z) & 255;

    x -= floor(x);
    y -= floor(y);
    z -= floor(z);

    float u = fade(x);
    float v = fade(y);
    float w = fade(z);

    int A =  p[X    ] + Y;
    int AA = p[A    ] + Z;
    int AB = p[A + 1] + Z;
    int B =  p[X + 1] + Y;
    int BA = p[B    ] + Z;
    int BB = p[B + 1] + Z;

    float result = lerp(w, lerp(v, lerp(u, grad(p[AA  ], x  , y  , z   ),  // AND ADD
                                           grad(p[BA  ], x-1, y  , z   )), // BLENDED
                                   lerp(u, grad(p[AB  ], x  , y-1, z   ),  // RESULTS
                                           grad(p[BB  ], x-1, y-1, z   ))),// FROM  8
                           lerp(v, lerp(u, grad(p[AA+1], x  , y  , z-1 ),  // CORNERS
                                           grad(p[BA+1], x-1, y  , z-1 )), // OF CUBE
                                   lerp(u, grad(p[AB+1], x  , y-1, z-1 ),
                                           grad(p[BB+1], x-1, y-1, z-1 ))));
    return (result + 1.0) / 2.0;
}


extern "C" float4 PerlinNoise (float4 lowColor, float4 highColor, float offsetX, float offsetY, float offsetZ, float scale, float contrast, coreimage::destination dest)
{
    float val = noise(dest.coord().x * scale + offsetX, dest.coord().y * scale + offsetY, offsetZ);
    return mix(lowColor, highColor, pow(val, contrast));
}

这里是彩图版本:

#include <CoreImage/CoreImage.h>
using namespace metal;

constant uint8_t p[512] = {151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180};

float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
float grad(uint8_t hash, float x, float y, float z) {
    uint8_t h = hash & 15;
    float u = h < 8 ? x : y;
    float v = h < 4 ? y : h == 12 || h == 14 ? x : z;
    return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
float lerp(float t, float x, float y) { return x + t * (y - x); }

float noise(float x, float y, float z) {
    uint8_t X = (uint8_t)floor(x) & 255;
    uint8_t Y = (uint8_t)floor(y) & 255;
    uint8_t Z = (uint8_t)floor(z) & 255;

    x -= floor(x);
    y -= floor(y);
    z -= floor(z);

    float u = fade(x);
    float v = fade(y);
    float w = fade(z);

    uint8_t A =  p[X    ] + Y;
    uint8_t AA = p[A    ] + Z;
    uint8_t AB = p[A + 1] + Z;
    uint8_t B =  p[X + 1] + Y;
    uint8_t BA = p[B    ] + Z;
    uint8_t BB = p[B + 1] + Z;

    float result = lerp(w, lerp(v, lerp(u, grad(p[AA  ], x  , y  , z   ),  // AND ADD
                                           grad(p[BA  ], x-1, y  , z   )), // BLENDED
                                   lerp(u, grad(p[AB  ], x  , y-1, z   ),  // RESULTS
                                           grad(p[BB  ], x-1, y-1, z   ))),// FROM  8
                           lerp(v, lerp(u, grad(p[AA+1], x  , y  , z-1 ),  // CORNERS
                                           grad(p[BA+1], x-1, y  , z-1 )), // OF CUBE
                                   lerp(u, grad(p[AB+1], x  , y-1, z-1 ),
                                           grad(p[BB+1], x-1, y-1, z-1 ))));
    return (result + 1.0) / 2.0;
}

float4 colormapLookup(float value, size_t count, float indices[], float4 colormap[]) {
    // If the value is at the outside boundary, just return the boundary color.
    if (value < indices[0]) {
        return colormap[0];
    } else if (value >= indices[count - 1]) {
        return colormap[count - 1];
    }

    // Find which 2 indices the value falls between.
    size_t index = 0;
    while (value < indices[index] && index < count - 1) {
        index++;
    }
    float startIndex = indices[index];
    float endIndex = indices[index+1];

    // Calculate the normalized offset between those indies.
    float offset = (value - startIndex) / (endIndex - startIndex);

    // Return the blended color for that offest.
    return mix(colormap[index], colormap[index+1], offset);
}


extern "C" float4 PerlinNoise (size_t count, float indices[], float4 colormap[], float offsetX, float offsetY, float offsetZ, float scale, float contrast, coreimage::destination dest)
{
    float val = noise(dest.coord().x * scale + offsetX, dest.coord().y * scale + offsetY, offsetZ);
    return colormapLookup(pow(val, contrast), count, indices, colormap);
}

这是我尝试实例化 CIColorKernel 的方法。 EXEC_BAD_ACCESS 发生在调用 CIColorKernel init 函数的行上:

    static var kernel: CIColorKernel? = {
        guard let url = Bundle.main.url(forResource: "PerlinNoiseGenerator", withExtension: "ci.metallib") else { return nil }

        do {
            let data = try Data(contentsOf: url)
            return try CIColorKernel(functionName: "PerlinNoise", fromMetalLibraryData: data)
        } catch {
            print("[ERROR] Failed to create CIColorKernel: \(error)")
        }
        return nil
    }()

编辑:在实例化 CIColorKernel 的地方添加了 Swift 代码。

根据 Frank 的评论,我将我的 Perlin Noise 滤镜恢复到其原始功能状态,而是使用可以生成自己的渐变的 CIColorMap 滤镜的改进版本作为后续步骤应用颜色图基于 [CGFloat: CIColor] 字典的图像。

import CoreImage

class ImprovedColorMap: CIFilter {

    enum ColorMapError: Error {
        case unableToCreateCGContext
        case unableToCreateCGGradient
    }

    private var _colorMap: [CGFloat: CIColor] = [
        0.0: .black,
        1.0: .white,
    ]

    private var gradientImage: CIImage = CIImage()

    private var mapFilter: CIFilter? = CIFilter(name: "CIColorMap")

    var inputImage: CIImage?

    var inputColorMap: [CGFloat: CIColor] {
        get { _colorMap }
        set {
            guard newValue.keys.count >= 2 else {
                print("ERROR: Color map must have at least 2 entries. Change will be ignored.")
                return
            }
            _colorMap = newValue
            generateGradient()
        }
    }

    override var outputImage: CIImage? {
        guard let filter = mapFilter else {
            return nil
        }
        filter.setValue(gradientImage, forKey: kCIInputGradientImageKey)
        filter.setValue(inputImage, forKey: kCIInputImageKey)
        return filter.outputImage
    }

    init(colorMap: [CGFloat: CIColor]? = nil) {
        if let colorMap = colorMap {
            _colorMap = colorMap
        }
        super.init()
        generateGradient()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    private func generateGradient() {
        DispatchQueue.global(qos: .default).async {
            let colorSpace = CGColorSpaceCreateDeviceRGB()
            guard let context = CGContext(data: nil, width: 512, height: 16, bitsPerComponent: 8, bytesPerRow: 512 * 4, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
                print("ERROR: Could not create CGContext.")
                return
            }
            var locations: [CGFloat] = []
            var components: [CGFloat] = []
            for (location, color) in self._colorMap {
                locations.append(location)
                components.append(contentsOf: color.floatComponents)
            }
            guard let gradient = CGGradient(colorSpace: colorSpace, colorComponents: components, locations: locations, count: locations.count) else {
                print("ERROR: Could not create CGGradient.")
                return
            }
            context.drawLinearGradient(gradient, start: .zero, end: CGPoint(x: 512.0, y: 0.0), options: [])
            guard let image = context.makeImage() else {
                print("ERROR: Failed to create image from context.")
                return
            }
            DispatchQueue.main.async {
                self.gradientImage = CIImage(cgImage: image)
            }
        }
    }
}

这很好用,除了现在我意识到传统的 Perlin 噪声对于看起来逼真的地形来说太平滑了,所以接下来我将继续创建一个 Simplex 噪声金属内核。