Metal file as part of iOS structure

I am trying to create a framework that works with METAL Api (iOS). I am new to this platform and I would like to know how to create a framework for working with .metal files (I create a static lib, not a dynamic one). Should they be part of a .a file or as resource files in a frame package? Or is there another way to do this? Thanks.

Update: For those who do this — I finished the proposed Warrenm 1 option — converted the .metal file to a string and called newLibraryWithSource:options:error: Although it is not the best in performance, it allowed me to send only one framework file without additional resources for import. This can be useful for those who create a framework that uses Metal, ARKit files, etc. With shader files.

+7
source share
3 answers

There are many ways to provide a metal library with a static library with various tradeoffs. I will try to list them here.

1) Convert your .metal files to static lines, baked in your static library.

This is probably the worst option. The idea is that you pre-process your metal shader code into strings that are included as string literals in your static library. Then you would use the newLibraryWithSource:options:error: API (or its asynchronous sibling) API to turn the source into MTLLibrary and get the functions. This requires that you develop a process for converting .metal -to-string, and you lose the advantage of pre-compiling shaders, making the application slower.

2) Save the .metal files with your static library and ask the library users to add them to their target program.

All that is considered to be a decent option, although it puts more pressure on your users and provides your source for a metal shader (if this is a concern). The code in your static library may use the "default library" ( newDefaultLibrary ), since the code will be automatically compiled by Xcode in the default.metallib application, which is embedded in the application package as a resource.

3) Send the .metallib file along with your static library

This is a good intermediate point between ease of use, performance, and security (since it does not provide your shader source, but only its IR). Basically, you can create the Metal Library goal in your project into which you put your shader code. This will create a .metallib file that you can send along with your static library and include the user as a resource in your target program. Your static library can load .metallib at runtime using the newLibraryWithData:error: or newLibraryWithURL:error: . Since your shaders will be precompiled, creating libraries will be faster, and you will retain the advantage of compile-time diagnostics.

+12
source

Since someone wanted to incorporate the functions of metal shaders into an environment related to SceneKit / ARKit, the available answers led me in the wrong direction. There is a much simpler solution using makeDefaultLibrary (bundle: Bundle) (iOS 10+) to access the features included in the .metal dependency. Adding here for people in a similar position.

TL DR, access the MTLL library, for example:

  //Get the framework bundle by using 'Bundle(for: type(of: self))' from inside any framework class. //Then use the bundle to define an MTLLibrary. let frameworkBundle = Bundle(for: type(of: self)) let device = MTLCreateSystemDefaultDevice() do { let bundleLib = try device?.makeDefaultLibrary(bundle: frameworkBundle) print(bundleLib.functionNames) //we can access our framework metal functions! No build tricks/workarounds. } catch { print("Couldn't locate default library for bundle: \(frameworkBundle)") print( error ) } 

Xcode creates a library of default shader functions at build time by compiling .metal dependencies. This is true both for the purposes of the framework and for the purposes of the application, so the real question is how to access the default library for my frameworks?

You can access the default framework library using the makeDefaultLibrary(bundle: Bundle) MTLDevice on MTLDevice . The code example above shows more details.

For Scenekit / ARKit with SCNProgram

The bundle library can be set as a property of the SCNPrograms library, and then the fragment and shader functions can be defined in the same way as if the .metal file was included in the main project:

  //The SCNProgram that will use our framework metal functions var program = SCNProgram() //Use the framework bundle to define an MTLLibrary. let frameworkBundle = Bundle(for: type(of: self)) let device = MTLCreateSystemDefaultDevice() do { let bundleLib = try device?.makeDefaultLibrary(bundle: frameworkBundle) //set the SCNProgram library, and define functions as usual program.library = bundleLib program.fragmentFunctionName = "yourCustomFrameworkFragmentFunction" program.vertexFunctionName = "yourCustomFrameworkVertexFunction" } catch { print("Couldn't locate default library for bundle: \(frameworkBundle)") print( error ) } 
+1
source

The approach suggested by the questioner may not work (hence the lack of sample code). A metal shader (.metal) is just a set of functions; it does not create MTLLibrary (.metallib). Here is the working code that compiles the Metal shader from an array of characters ( const char * ) (does not match NSString ); it is followed by instructions to convert the .metal file to a .metallib file before execution.

Compiling a metal shader at run time

The following example can also be used to provide users with a shader editor, and it allows you to update only part of the shader of your application, without requiring the user to update the entire application:

 NSError* error = NULL; const char* vshSource = "using namespace metal;\n" "typedef struct {\n" " packed_float2 position;\n" " packed_float2 texcoord;\n" "} Vertex;\n" "typedef struct {\n" " float3x3 matrix;\n" " float3 offset;\n" "} ColorConversion;\n" "typedef struct {\n" " float4 position [[position]];\n" " float2 texcoord;\n" "} Varyings;\n" "vertex Varyings vertexPassthrough(\n" "device Vertex* verticies [[ buffer(0) ]],\n" "unsigned int vid [[ vertex_id ]]\n" ") {\n" " Varyings out;\n" " device Vertex& v = verticies[vid];\n" " out.position = float4(float2(v.position), 0.0, 1.0);\n" " out.texcoord = v.texcoord;\n" " return out;\n" "}\n"; const char* fshSource = "using namespace metal;\n" "typedef struct {\n" "packed_float2 position;\n" "packed_float2 texcoord;\n" "} Vertex;\n" "typedef struct {\n" "float3x3 matrix;\n" "float3 offset;\n" "} ColorConversion;\n" "typedef struct {\n" "float4 position [[position]];\n" "float2 texcoord;\n" "} Varyings;\n" "fragment half4 fragmentColorConversion(\n" "Varyings in [[ stage_in ]],\n" "texture2d<float, access::sample> textureBGRA [[ texture(0) ]],\n" "constant ColorConversion &colorConversion [[ buffer(0) ]]\n" ") {\n" "constexpr sampler s(address::clamp_to_edge, filter::linear);\n" "return half4(half3(textureBGRA.sample(s, in.texcoord).rgb), 1.0);\n" "}\n"; id <MTLFunction> vertexProgram; id <MTLLibrary> vertexLibrary = [_device newLibraryWithSource:[NSString stringWithUTF8String:vshSource] options:NULL error:&error]; if (NULL != vertexLibrary) { vertexProgram = [vertexLibrary newFunctionWithName:@"vertexPassthrough"]; } else { NSLog(@"Error compiling vertex program: %@", error.description); } id <MTLFunction> fragmentProgram; id <MTLLibrary> fragmentLibrary = [_device newLibraryWithSource:[NSString stringWithUTF8String:fshSource] options:NULL error:&error]; if (NULL != fragmentLibrary) { fragmentProgram = [fragmentLibrary newFunctionWithName:@"fragmentColorConversion"]; } else { NSLog(@"Error compiling fragment program: %@", error.description); } 

The following are excerpts from the Apple Developer Documentation; although the information is relatively elementary, use it as the basis for a common structure that you and your audience will share when communicating about your subject.

Creating libraries during application build

The accepted answer is categorically incorrect for the same reasons; and it argues that performance trade-offs are dubious. Here is the only definite statement that can be made about compiling Metal shaders and creating Metal libraries, and then real code:

Functions and Libraries

This chapter describes how to create an MTLFunction object as a reference to a Shader or Metal computational function, and how to organize and access functions using an MTLLibrary object.

MTLFunction Represents a shader or computational function.

The MTLFunction object is the only function that is written in the Metal shading language and runs on the GPU as part of a graphics or computing pipeline. For more information about the language of metallic shading, see the Guide to the language of metallic shading.

To transfer data or state between the Metal runtime and a graphic or computational function written in the Metal shading language, you assign an argument index to textures, buffers, and samplers. The argument index determines which texture, buffer, or sampler is referenced by both the Metal execution code and the Metal shader code.

To render, you specify the MTLFunction object to use as a vertex or fragment shader in the MTLRenderPipelineDescriptor object, as described in detail in the section Creating the state of the rendering pipeline. To pass the calculations, you specify the MTLFunction object when creating the MTLComputePipelineState object for the target device, as described in the "Specify the calculation status and resources for the calculation command encoder" section.

A library is a repository of functions

An MTLLibrary object represents the repository of one or more MTLFunction objects. One MTLFunction object represents one Metal function, which was written in a shading language. In the source code of the Metal shading language, any function that uses the Metal function specifier (vertex, fragment, or kernel) can be represented by an MTLFunction object in the library. A Metal function without one of these function qualifiers cannot be directly represented by an MTLFunction object, although it can be called by another function in a shader.

MTLFunction objects in the library can be created from any of the following sources:

  • The metal shading language code that was compiled into the binary library during the build process of the application.
  • A text string containing the source code for the Metal shader language, which is compiled by the application at run time.

Compiling the source files in the shader language and creating the library (.metallib file) during the build process of the application provides better application performance than compiling the shader source code at run time. You can create the library in Xcode or using command line utilities.

Using Xcode to create a library

Any source shader files that are in your project are automatically used to create a default library, which can be accessed from the Metal platform code using the newDefaultLibrary MTLDevice method.

Using command line utilities to create a library

Figure 8-1 shows the command line utilities that form the compiler toolchain for the metal shader source code. When you include .metal files in your project, Xcode calls these tools to create a library file that you can access in your application at runtime.

To compile the shader source code into a library without using Xcode:

  1. Use the metal tool to compile each .metal file into a single .air file, which stores the intermediate representation (IR) of the shader language code.
  2. If desired, you can use the metal-ar tool to archive multiple .air files together into a single .metalar file. (metal-ar is similar to Unix ar.)
  3. Use the metallib tool to create the Metal.metallib library file from IR.air files or from .metalar archive files.

Example: creating a library file using command line utilities

 xcrun -sdk macosx metal MyLibrary.metal -o MyLibrary.air xcrun -sdk macosx metallib MyLibrary.air -o MyLibrary.metallib 

To access the resulting library in the platform code, call the newLibraryWithFile: error: method:

 NSError *libraryError = NULL; NSString *libraryFile = [[NSBundle mainBundle] pathForResource:@"MyLibrary" ofType:@"metallib"]; id <MTLLibrary> myLibrary = [_device newLibraryWithFile:libraryFile error:&libraryError]; if (!myLibrary) { NSLog(@"Library error: %@", libraryError); } 
0
source

Source: https://habr.com/ru/post/1272614/


All Articles