xcode 手动编译金属着色器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32298719/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 07:44:39  来源:igfitidea点击:

Manually Compile Metal Shaders

xcodecompilationshadermetal

提问by lcmylin

I'm interested in moving away from Xcode and manually compiling Metal shaders in a project for a mixed-language application.

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

I have no idea how to do this, though. Xcode hides the details of shader compilation and subsequent loading into the application at runtime (you just call device.newDefaultLibrary()). Is this even possible, or will I have to use runtime shader compilation for my purposes?

不过,我不知道该怎么做。Xcode 隐藏了着色器编译和随后在运行时加载到应用程序中的细节(您只需调用device.newDefaultLibrary())。这甚至是可能的,还是我必须使用运行时着色器编译来达到我的目的?

回答by Bill Hollings

Generally, you have three ways to load a shader library in Metal:

通常,您可以通过三种方式在 Metal 中加载着色器库:

  • Use runtime shader compilation from shader source code via the MTLDevice newLibraryWithSource:options:error:or newLibraryWithSource:options:completionHandler:methods. Although purists may shy away from runtime compilation, this option has minimal practical overhead, and so is completely viable. Your primary practical reason for avoiding this option might be to avoid making your shader source code available as part of your application, to protect your IP.

  • Load compiled binary libraries using the MTLLibrary newLibraryWithFile:error:or newLibraryWithData:error:methods. Follow the instructions in Using Command Line Utilities to Build a Libraryto create these individual binary libraries at build time.

  • Let Xcodecompile your various *.metalfiles at build time into the default library available through MTLDevice newDefaultLibrary.

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

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

  • Xcode*.metal在构建时将您的各种文件编译到通过MTLDevice newDefaultLibrary.

回答by James Bush

Here's actual code that creates vertex and fragment programs from a string; using it allows you to compile shaders at runtime (shown in the code following the shader string method).

这是从字符串创建顶点和片段程序的实际代码;使用它允许您在运行时编译着色器(显示在着色器字符串方法后面的代码中)。

To eliminate the need for the use of escape sequences (e.g., n...), I use the STRINGIFY macro. To workaround its limitation on the use of double quotes, I wrote a block that takes an array of header file names and creates import statements from them. It then inserts them into the shader at the appropriate place;?I did the same for include statements. It simplifies and expedites the insertion of what are sometimes rather lengthy lists.

为了消除使用转义序列(例如,n...)的需要,我使用了 STRINGIFY 宏。为了解决它对使用双引号的限制,我编写了一个块,该块采用头文件名数组并从中创建导入语句。然后将它们插入到着色器的适当位置;我对 include 语句也做了同样的处理。它简化并加快了插入有时相当长的列表的速度。

Incorporating this code will not only allow you to select a particular shader to use based on localization, but, if necessary, could also be used to update your app's shaders without having to update the app. You would simply create and ship a text file containing your shader code, which your app could be preprogrammed to reference as the shader source.

合并此代码不仅允许您根据本地化选择要使用的特定着色器,而且在必要时还可用于更新应用程序的着色器,而无需更新应用程序。您只需创建并发送一个包含着色器代码的文本文件,您的应用程序可以对其进行预编程以作为着色器源引用。

#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"];
.
.
.