Metal Shader function with variadic arguments, called from SwiftUI?

170 views Asked by At

My app requires to render a field strength in a SwiftUI View, i.e. to assign a color with a coordinate dependent opacity to each point of the View.
This can be done using Canvas, as described here, but this is very slow. Much faster is to use a Metal shader, as described here.

To render a point field, I am using this Metal function:

[[ stitchable ]] half4 pointField(float2 position, half4 currentColor, float2 center, half4 newColor) {
    // Compute distance center - position
    float x = position.x;
    float y = position.y;
    float xDistance = center.x - x;
    float yDistance = center.y - y;
    float d = sqrt(xDistance * xDistance + yDistance * yDistance);
    float r = d + 1.0; // min r is now 1
    float strength = 1.0 / sqrt(r);
    return half4(newColor.rgb, strength);
}  

This function can be called from SwiftUI using e.g.

Rectangle()
    .frame(width: boardSize.width, height: boardSize.height)
    .colorEffect(ShaderLibrary.pointField(.float2(center.x, center.y), .color(Color(red: 0, green: 1, blue: 0))))

where center is the center of the point field.

According to Apple's docu, a Metal shader function has to have to following signature:

[[ stitchable ]] half4 name(float2 position, args...)

I want now to display a superposition of a number of point fields. Since the number can be changed at run time, my idea is to use a Metal shader comparable to the code shown above, but in a loop over the point sources given by the variadic arguments.
This blog shows how to call a Metal shader function with variadic arguments, using

let shader = Shader(function: function, arguments: [])  

My question is:
How can I determine, how many arguments have been passed from SwiftUI to Metal? This would be required to loop over the code above, and to sum up the field strengths of the various point fields.
I could imagine to pass as the first required argument the number of point field centers that follow. Is this the right way?

2

There are 2 answers

2
Reinhard Männer On BEST ANSWER

Jeshua Lacock's answer motivated me to understand how to pass arguments to Metal shader functions, using MTLBufer. Unfortunately, I failed, since I am completely new to Metal. But I found a (for me) simpler way:

A SwiftUI Shader has some arguments. Among them is the Type Method floatArray(_:).
The docu says:

Returns an argument value defined by the provided array of floating point numbers. When passed to an MSL function it will convert to a device const float *ptr, int count pair of parameters.

I thus can now call the Metal shader from SwiftUI with (test):

let center1 = CGPoint(x: 100, y: 50)
let center2 = CGPoint(x: 200, y: 150)
let pointCenters: [Float] = [Float(center1.x), Float(center1.y), Float(center2.x), Float(center2.y)]
Rectangle()
    .frame(width: boardSize.width, height: boardSize.height)
    .colorEffect(ShaderLibrary.pointFields(.floatArray(pointCenters)))  

and the corresponding Metal function is:

[[ stitchable ]] half4 pointFields(float2 position, half4 currentColor, device const float *centers, int count) {
    float totalStrength = 0.0;
    for (int i = 0; i < count; i += 2) {
        float nextCenterX = centers[i];
        float nextCenterY = centers[i+1];
        
        // Compute distance center - position
        float x = position.x;
        float y = position.y;
        float xDistance = nextCenterX - x;
        float yDistance = nextCenterY - y;
        float d = sqrt(xDistance * xDistance + yDistance * yDistance);
        float r = d + 1.0; // min r is now 1
        totalStrength += 1.0 / sqrt(r);
    }
    return half4(0.0, 1.0, 0.0, totalStrength);
}  

This yields the following View:
enter image description here

3
Jeshua Lacock On

You are responsible to know how many arguments have been passed to Metal, and in-fact they are fixed and not dynamic.

I would pass two arguments, one that is a 1D array of values and another that is the count of the current number values.

The array would be the length of the maximum supported number of values.