简体   繁体   中英

CoreImage: CIColorKernel.init with .metallib causes EXEC_BAD_ACCESS

I've made a Perlin Noise filter for CoreImage using Metal Shader Language. My first version of it works fine, and allows me to specify 2 colors to use as the low and high . My next step is to allow it to accept a color map such that it can produce multi-color output. The color map version compiles fine, but when my CIFilter subclass tries to create a CIColorKernel from it using the init(functionName:fromMetalLibraryData:) method, I get an EXEC_BAD_ACCESS with code=1 . Any idea what about my color map implementation might be causing this?

Here's the simple, 2-color version:

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

And here is the color map version:

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

Here's the method where I attempt to instantiate the CIColorKernel. The EXEC_BAD_ACCESS happens on the line where the CIColorKernel init function is called:

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

Edit: Added the Swift code where the CIColorKernel is being instantiated.

Based on Frank's comment, I reverted my Perlin Noise filter to its original, functional state and instead applied the color map as a following step using this improved version of the CIColorMap filter which can generate its own gradient image based on a [CGFloat: CIColor] dictionary.

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

That works well, except now I realize that traditional Perlin Noise is too smooth for realistic-looking terrain, so I'm moving on to creating a Simplex Noise metal kernel next.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM