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:
Then add a custom Build Phase that copies the compiled kernel metallibs into your app bundle:
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_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
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.Yes. After all, when you draw an
MTKMesh
, you're still obligated to callsetVertexBuffer(...)
with the buffers wrapped by the mesh's constituentMTKMeshBuffer
s. You could just as readily create anMTLBuffer
yourself and copy your custom vertex structs into it.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 typeMyVertexType *
, 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
How to Blur the Background in a Swiftui MACos Application
Ios/Tvos Playground Fails with "Unable to Find Execution Service for Selected Run Destination"
Xcode Takes Long Time to Print Debug Results
App Crashes from IPA File But Runs Fine from Xcode
Ibdesignable and Uitableviewcell
iOS 12 Errors: Appears to Be from a Different Nsmanagedobjectmodel Than This Context'S
How to Check the Type of a Variable Against Another Variable in Swift
Synchronized Realm - Airplane Mode
Unsafemutablepointer<Void> to Concrete Object Type
How to Detect If a Observable Has Not Emitted Any Events for Specific Time in Rxswift
Swift Enumeration Order and Comparison
Alamofire 5 Upload Encodingcompletion
How to Subclass a Class Which Doesn't Have Any Designated Initializers
Swift: Move Uiimage Partly Along Path