手动编译Metal着色器

10

我有兴趣远离Xcode,在一个混合语言应用程序的项目中手动编译Metal着色器。

我不知道怎么做。Xcode隐藏了着色器编译的细节以及在运行时将其加载到应用程序中(只需调用device.newDefaultLibrary())。这是否可行,还是我必须使用运行时着色器编译来实现我的目的?


1
这个例子可能会对你有所帮助:https://github.com/n-yoda/metal-without-xcode - N. Yoda
2个回答

19

通常,您有三种方法在Metal中加载着色器库:

  • 使用来自着色器源代码的运行时着色器编译,通过MTLDevice newLibraryWithSource:options:error:newLibraryWithSource:options:completionHandler:方法。虽然纯粹主义者可能会回避运行时编译,但此选项具有最小的实际开销,因此完全可行。避免选择此选项的主要实际原因可能是为了避免将您的着色器源代码作为应用程序的一部分公开,以保护您的知识产权。

  • 使用MTLLibrary newLibraryWithFile:error:newLibraryWithData:error:方法加载已编译的二进制库。请按照使用命令行工具构建库中的说明,在构建时创建这些单个二进制库。

  • Xcode在构建时将各种*.metal文件编译为默认库可通过MTLDevice newDefaultLibrary获得。


是否有可能在运行时以编程方式编写着色器,然后再在运行时编译它呢? - J.Doe
是的。只需生成MSL源代码,并使用上面列出的第一种方法进行编译。 - Bill Hollings

6
这里是实际代码,可以从字符串中创建顶点和片段程序;使用它可以在运行时编译着色器(在着色器字符串方法之后的代码中显示)。
为了消除对转义序列(例如n...)的需求,我使用STRINGIFY宏。为了解决其对双引号使用的限制,我编写了一个块,它接受头文件名称数组并从中创建导入语句。然后将它们插入到适当的位置; 我也为包含语句做了同样的事情。它简化和加快了有时相当冗长列表的插入。
将这些代码整合进去不仅可以让您根据本地化选择特定的着色器,而且如果必要,还可以用于更新应用程序的着色器,而无需更新应用程序。您只需创建和发送一个包含您的着色器代码的文本文件,您的应用程序可以预先编程以引用作为着色器源。
#if !defined(_STRINGIFY)
#define __STRINGIFY( _x )   # _x
#define _STRINGIFY( _x )   __STRINGIFY( _x )
#endif

typedef NSString *(^StringifyArrayOfIncludes)(NSArray <NSString *> *includes);
static NSString *(^stringifyHeaderFileNamesArray)(NSArray <NSString *> *) = ^(NSArray <NSString *> *includes) {
    NSMutableString *importStatements = [NSMutableString new];
    [includes enumerateObjectsUsingBlock:^(NSString * _Nonnull include, NSUInteger idx, BOOL * _Nonnull stop) {
        [importStatements appendString:@"#include <"];
        [importStatements appendString:include];
        [importStatements appendString:@">\n"];
    }];

    return [NSString new];
};

typedef NSString *(^StringifyArrayOfHeaderFileNames)(NSArray <NSString *> *headerFileNames);
static NSString *(^stringifyIncludesArray)(NSArray *) = ^(NSArray *headerFileNames) {
    NSMutableString *importStatements = [NSMutableString new];
    [headerFileNames enumerateObjectsUsingBlock:^(NSString * _Nonnull headerFileName, NSUInteger idx, BOOL * _Nonnull stop) {
        [importStatements appendString:@"#import "];
        [importStatements appendString:@_STRINGIFY("")];
        [importStatements appendString:headerFileName];
        [importStatements appendString:@_STRINGIFY("")];
        [importStatements appendString:@"\n"];
    }];

    return [NSString new];
};

- (NSString *)shader
{
    NSString *includes = stringifyIncludesArray(@[@"metal_stdlib", @"simd/simd.h"]);
    NSString *imports  = stringifyHeaderFileNamesArray(@[@"ShaderTypes.h"]);
    NSString *code     = [NSString stringWithFormat:@"%s",
                          _STRINGIFY(
                                     using namespace metal;

                                     typedef struct {
                                         float scale_factor;
                                         float display_configuration;
                                     } Uniforms;

                                     typedef struct {
                                         float4 renderedCoordinate [[position]];
                                         float2 textureCoordinate;
                                     } TextureMappingVertex;

                                     vertex TextureMappingVertex mapTexture(unsigned int vertex_id [[ vertex_id ]],
                                                                            constant Uniforms &uniform [[ buffer(1) ]])
                                     {
                                         float4x4 renderedCoordinates;
                                         float4x2 textureCoordinates;

                                         if (uniform.display_configuration == 0 ||
                                             uniform.display_configuration == 2 ||
                                             uniform.display_configuration == 4 ||
                                             uniform.display_configuration == 6)
                                         {
                                             renderedCoordinates = float4x4(float4( -1.0, -1.0, 0.0, 1.0 ),
                                                                                     float4(  1.0, -1.0, 0.0, 1.0 ),
                                                                                     float4( -1.0,  1.0, 0.0, 1.0 ),
                                                                                     float4(  1.0,  1.0, 0.0, 1.0 ));

                                             textureCoordinates = float4x2(float2( 0.0, 1.0 ),
                                                                                    float2( 2.0, 1.0 ),
                                                                                    float2( 0.0, 0.0 ),
                                                                                    float2( 2.0, 0.0 ));
                                         } else if (uniform.display_configuration == 1 ||
                                                    uniform.display_configuration == 3 ||
                                                    uniform.display_configuration == 5 ||
                                                    uniform.display_configuration == 7)
                                         {
                                             renderedCoordinates = float4x4(float4( -1.0, -1.0, 0.0, 1.0 ),
                                                                                     float4( -1.0,  1.0, 0.0, 1.0 ),
                                                                                     float4(  1.0, -1.0, 0.0, 1.0 ),
                                                                                     float4(  1.0,  1.0, 0.0, 1.0 ));
                                             if (uniform.display_configuration == 1 ||
                                                 uniform.display_configuration == 5)
                                             {
                                                 textureCoordinates = float4x2(float2( 0.0,  1.0 ),
                                                                                        float2( 1.0,  1.0 ),
                                                                                        float2( 0.0, -1.0 ),
                                                                                        float2( 1.0, -1.0 ));
                                             } else if (uniform.display_configuration == 3 ||
                                                        uniform.display_configuration == 7)
                                             {
                                                  textureCoordinates = float4x2(float2( 0.0,  2.0 ),
                                                                                        float2( 1.0,  2.0 ),
                                                                                        float2( 0.0,  0.0 ),
                                                                                        float2( 1.0,  0.0 ));
                                             }
                                         }

                                         TextureMappingVertex outVertex;
                                         outVertex.renderedCoordinate = float4(uniform.scale_factor, uniform.scale_factor , 1.0f, 1.0f ) * renderedCoordinates[vertex_id];
                                         outVertex.textureCoordinate = textureCoordinates[vertex_id];

                                         return outVertex;
                                     }

                                     fragment half4 displayTexture(TextureMappingVertex mappingVertex [[ stage_in ]],
                                                                   texture2d<float, access::sample> texture [[ texture(0) ]],
                                                                   sampler samplr [[sampler(0)]],
                                                                   constant Uniforms &uniform [[ buffer(1) ]]) {

                                         if (uniform.display_configuration == 1 ||
                                             uniform.display_configuration == 2 ||
                                             uniform.display_configuration == 4 ||
                                             uniform.display_configuration == 6 ||
                                             uniform.display_configuration == 7)
                                         {
                                             mappingVertex.textureCoordinate.x = 1 - mappingVertex.textureCoordinate.x;
                                         }
                                         if (uniform.display_configuration == 2 ||
                                             uniform.display_configuration == 6)
                                         {
                                             mappingVertex.textureCoordinate.y = 1 - mappingVertex.textureCoordinate.y;
                                         }

                                         if (uniform.scale_factor < 1.0)
                                         {
                                             mappingVertex.textureCoordinate.y += (texture.get_height(0) - (texture.get_height(0) * uniform.scale_factor));
                                         }

                                         half4 new_texture = half4(texture.sample(samplr, mappingVertex.textureCoordinate));

                                         return new_texture;
                                     }

                                     )];

    return [NSString stringWithFormat:@"%@\n%@", includes, imports, code];
}

        /*
        * Metal setup: Library
        */
        __autoreleasing NSError *error = nil;

        NSString* librarySrc = [self shader];
        if(!librarySrc) {
            [NSException raise:@"Failed to read shaders" format:@"%@", [error localizedDescription]];
        }

        _library = [_device newLibraryWithSource:librarySrc options:nil error:&error];
        if(!_library) {
            [NSException raise:@"Failed to compile shaders" format:@"%@", [error localizedDescription]];
        }

        id <MTLFunction> vertexProgram = [_library newFunctionWithName:@"mapTexture"];
        id <MTLFunction> fragmentProgram = [_library newFunctionWithName:@"displayTexture"];
.
.
.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接