Metal: Vertexfunction Defined in .Metal File Becomes Nil Once Setting Compiler and Linker Options for Msl Cikernel

Metal: vertexFunction defined in .metal file becomes nil once setting Compiler and Linker Options for MSL cikernel

I guess you have to compile your filter kernels separately instead of with your default Metal library.

To do so, you could for instance give them another file extension, like .kernel and add a custom Build Rule like so:

Sample Image

Then add a custom Build Phase that copies the compiled kernel metallibs into your app bundle:

Sample Image

To initialize the CIKernel with the correct metal source, you can do something like this:

let url = Bundle(for: type(of: self)).url(forResource: "<#name of your .kernel file#>", withExtension: "metallib")!
let data = try! Data(contentsOf: url)
let kernel = try! CIKernel(functionName: "<#kernel function name#>", fromMetalLibraryData: data)

(Note that you should remove the compiler and liker flags again from your project settings to have your other Metal sources compile properly again.)

One default MTLLibrary from multiple .metal files (compute kernel and CIKernel implementations)?

You can’t use the default Metal build pipeline for compiling multiple .metal files containing Core Image kernels into one library right now. The linker doesn’t allow merging multiple .air files into one .metallib when setting the -cikernel flag.

You either have to put all your kernels into one .metal file or use the solution I posted in the answer you linked to above.

Core Image shader language conversion to metal with simulator support

To silence the warning add to GCC_PREPROCESSOR_DEFINITIONS of your build configuration CI_SILENCE_GL_DEPRECATIONCI_SILENCE_GL_DEPRECATION

Convert Core Image Kernel Language function to Metal Shading Language

This should work:

float4 MyFunc(sampler src, float4 color, float dist, float slope, destination dest) {
const float d = dest.coord().y * slope + dist;
float4 t = unpremultiply(src.sample(src.coord()));
t = (t - d * color) / (1.0 - d);

return premultiply(t);
}

Note the destination parameter. This is an optional last parameter to a kernel, that gives you access to information about the render destination (like the coordinate in destination-space that you are rendering to). You don't need to pass anything for this when invoking the CIKernel, Core Image will fill it automatically.

Since you are only sampling the input src at the current location, you can also optimize the kernel to be a CIColorKernel. These are kernels that have a 1:1 mapping of input to output pixels. They can be concatenated by the Core Image runtime. The kernel code would look like this:

float4 MyFunc(sample_t src, float4 color, float dist, float slope, destination dest) {
const float d = dest.coord().y * slope + dist;
float4 t = unpremultiply(src);
t = (t - d * color) / (1.0 - d);

return premultiply(t);
}

Notice sample_t (which is basically a float4) vs. sampler.

Are there any differences between Metal kernels on iOS and Mac?

Yes, there shouldn't be a difference between the platforms on the language level.

One difference I can think of is that macOS supports images with 32 bits per channel ("full" float), whereas in iOS you can "only" use 16-bit half float images.

Another difference that just came to mind is the default coordinate space of input samplers. In iOS, the sampler space is in relative coordinates ([0...1]), whereas in macOS it's in absolute coordinates ([0...width]). You should be able to unify that behavior by explicitly setting the sampler matrix like that (in macOS):

// see documentation for `kCISamplerAffineMatrix`
let scaleMatrix = [1.0/inputImage.extent.width, 0, 0, 1.0/inputImage.extent.height, 0, 0]
let inputSampler = CISampler(image: inputImage, options: [kCISamplerAffineMatrix: scaleMatrix])
kernel.apply(extent: domainOfDefinition, roiCallback: roiCallback, arguments: [inputSampler, ...])

Connection between [[stage_in]], MTLVertexDescriptor and MTKMesh

  1. No. If you try to create a render pipeline state from a pipeline descriptor without a vertex descriptor, and the corresponding vertex function has a [[stage_in]] parameter, the pipeline state creation call will fail.

  2. Yes. After all, when you draw an MTKMesh, you're still obligated to call setVertexBuffer(...) with the buffers wrapped by the mesh's constituent MTKMeshBuffers. You could just as readily create an MTLBuffer yourself and copy your custom vertex structs into it.

  3. Yes. Instead of having a [[stage_in]] parameter, you'd have a parameter attributed with [[buffer(0)]] (assuming all of the vertex data is interleaved in a single vertex buffer) of type MyVertexType *, as well as a [[vertex_id]] parameter that tells you where to index into that buffer.

Here's an example of setting the vertex buffers from an MTKMesh on a render command encoder:

for (index, vertexBuffer) in mesh.vertexBuffers.enumerated() {
commandEncoder.setVertexBuffer(vertexBuffer.buffer, offset: vertexBuffer.offset, index: index)
}

vertexBuffer is of type MTKMeshBuffer, while its buffer property is of type MTLBuffer; I mention this because it can be confusing.

Here is one way in which you might create a vertex descriptor to tell Model I/O and MetalKit to lay out the mesh data you're loading:

let mdlVertexDescriptor = MDLVertexDescriptor()
mdlVertexDescriptor.attributes[0] = MDLVertexAttribute(name: MDLVertexAttributePosition, format: MDLVertexFormat.float3, offset: 0, bufferIndex: 0)
mdlVertexDescriptor.attributes[1] = MDLVertexAttribute(name: MDLVertexAttributeNormal, format: MDLVertexFormat.float3, offset: 12, bufferIndex: 0)
mdlVertexDescriptor.attributes[2] = MDLVertexAttribute(name: MDLVertexAttributeTextureCoordinate, format: MDLVertexFormat.float2, offset: 24, bufferIndex: 0)
mdlVertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: 32)

You can create a corresponding MTLVertexDescriptor in order to create a render pipeline state suitable for rendering such a mesh:

let vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mdlVertexDescriptor)!

Here's a vertex struct that matches that layout:

struct VertexIn {
float3 position [[attribute(0)]];
float3 normal [[attribute(1)]];
float2 texCoords [[attribute(2)]];
};

Here's a stub vertex function that consumes one of these vertices:

vertex VertexOut vertex_main(VertexIn in [[stage_in]])
{
}

And finally, here's a vertex struct and vertex function you could use to render the exact same mesh data without a vertex descriptor:

struct VertexIn {
packed_float3 position;
packed_float3 normal;
packed_float2 texCoords;
};

vertex VertexOut vertex_main(device VertexIn *vertices [[buffer(0)]],
uint vid [[vertex_id]])
{
VertexIn in = vertices[vid];
}

Note that in this last case, I need to mark the struct members as packed since by default, Metal's simd types are padded for alignment purposes (specifically, the stride of a float3 is 16 bytes, not 12 as we requested in our vertex descriptor).



Related Topics



Leave a reply



Submit