Metal:在一个渲染过程中使用多个 MTLRenderCommandEncoder

Posted

技术标签:

【中文标题】Metal:在一个渲染过程中使用多个 MTLRenderCommandEncoder【英文标题】:Metal: using multiple MTLRenderCommandEncoder in one rendering pass 【发布时间】:2016-03-09 18:39:30 【问题描述】:

我开始使用 Metal(目前我的应用程序正在使用 OpenGL)。我正在尝试检查如何在一个渲染过程中使用多个管道状态(多个金属功能)进行渲染。问题是总是只实际绘制最后一个 MTLRenderCommandEncoder。以下是我的代码:

@property (nonatomic, strong) UIView* metalView;

@property (nonatomic, strong) id <MTLDevice> mtlDevice;
@property (nonatomic, strong) id <MTLCommandQueue> mtlCommandQueue;
@property (nonatomic, strong) MTLRenderPassDescriptor *mtlRenderPassDescriptor;
@property (nonatomic, strong) CAMetalLayer *metalLayer;
@property (nonatomic, strong) id <CAMetalDrawable> frameDrawable;
@property (nonatomic, strong) CADisplayLink *displayLink;

@property (nonatomic, strong) MTLRenderPipelineDescriptor *renderPipelineDescriptor;
@property (nonatomic, strong) MTLRenderPipelineDescriptor *renderPipelineDescriptorb;

@property (nonatomic, strong) id <MTLRenderPipelineState> renderPipelineState;
@property (nonatomic, strong) id <MTLRenderPipelineState> renderPipelineStateb;
@property (nonatomic, strong) id <MTLBuffer> object;
@property (nonatomic, strong) id <MTLBuffer> objectb;

@end

@implementation MetalGPUAdapter

- (void)setupGraphics

    self.mtlDevice = MTLCreateSystemDefaultDevice();
    self.mtlCommandQueue = [self.mtlDevice newCommandQueue];

    self.metalLayer = [CAMetalLayer layer];
    [self.metalLayer setDevice:self.mtlDevice];
    [self.metalLayer setPixelFormat:MTLPixelFormatBGRA8Unorm];
    self.metalLayer.framebufferOnly = YES;
    [self.metalLayer setFrame:self.gpuViewController.view.layer.frame];

    self.metalView = [[UIView alloc] initWithFrame:self.gpuViewController.view.frame];
    [self.gpuViewController.view addSubview:self.metalView];
    [self.gpuViewController.view sendSubviewToBack:self.metalView];

    [self.metalView.layer addSublayer:self.metalLayer];
    [self.metalView setOpaque:YES];
    [self.metalView setBackgroundColor:nil];
    [self.metalView setContentScaleFactor:[UIScreen mainScreen].scale];

    // Create a reusable pipeline
    self.renderPipelineDescriptor = [MTLRenderPipelineDescriptor new];
    self.renderPipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;

    self.renderPipelineDescriptorb = [MTLRenderPipelineDescriptor new];
    self.renderPipelineDescriptorb.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;

    id <MTLLibrary> lib = [self.mtlDevice newDefaultLibrary];
    self.renderPipelineDescriptor.vertexFunction = [lib newFunctionWithName:@"VertexColor"];
    self.renderPipelineDescriptor.fragmentFunction = [lib newFunctionWithName:@"FragmentColor"];
    self.renderPipelineState = [self.mtlDevice newRenderPipelineStateWithDescriptor:self.renderPipelineDescriptor error: nil];

    self.renderPipelineDescriptorb.vertexFunction = [lib newFunctionWithName:@"VertexColorb"];
    self.renderPipelineDescriptorb.fragmentFunction = [lib newFunctionWithName:@"FragmentColorb"];
    self.renderPipelineStateb = [self.mtlDevice newRenderPipelineStateWithDescriptor:self.renderPipelineDescriptorb error: nil];

    Triangle triangle[3] =   -1.0f, -1.0f ,  1.0f, -1.0f ,  0.0f, 0.0f  ;
    Triangle square[4] =   -1.0f, 0.0f ,  -1.0f, 1.0f ,  1.0f, 0.0f ,  1.0f, 1.0f ;

    self.object = [self.mtlDevice newBufferWithBytes:&triangle length:sizeof(Triangle[3]) options:MTLResourceOptionCPUCacheModeDefault];
    self.objectb = [self.mtlDevice newBufferWithBytes:&square length:sizeof(Triangle[4]) options:MTLResourceOptionCPUCacheModeDefault];

    self.displayLink = [CADisplayLink displayLinkWithTarget: self selector: @selector(renderScene)];
    [self.displayLink addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];


- (void)renderScene

    id <MTLCommandBuffer>mtlCommandBuffer = [self.mtlCommandQueue commandBuffer];

    while (!self.frameDrawable)
        self.frameDrawable = [self.metalLayer nextDrawable];
    

    if (!self.mtlRenderPassDescriptor)
        self.mtlRenderPassDescriptor = [MTLRenderPassDescriptor new];

    self.mtlRenderPassDescriptor.colorAttachments[0].texture = self.frameDrawable.texture;
    self.mtlRenderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
    self.mtlRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.75, 0.5, 1.0, 1.0);
    self.mtlRenderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;

    id <MTLRenderCommandEncoder> renderCommand = [mtlCommandBuffer renderCommandEncoderWithDescriptor: self.mtlRenderPassDescriptor];

    [renderCommand setRenderPipelineState:self.renderPipelineStateb];
    [renderCommand setVertexBuffer:self.object offset:0 atIndex:0];
    [renderCommand drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];

    [renderCommand endEncoding];

    id <MTLRenderCommandEncoder> renderCommandb = [mtlCommandBuffer renderCommandEncoderWithDescriptor: self.mtlRenderPassDescriptor];

    [renderCommandb setRenderPipelineState:self.renderPipelineStateb];
    [renderCommandb setVertexBuffer:self.objectb offset:0 atIndex:0];
    [renderCommandb drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];

    [renderCommandb endEncoding];


    [mtlCommandBuffer presentDrawable: self.frameDrawable];
    [mtlCommandBuffer commit];
    self.mtlRenderPassDescriptor = nil;
    self.frameDrawable = nil;

我的着色器:

#include <metal_stdlib>
using namespace metal;

typedef struct 
    float2 position;
 Triangle;

typedef struct 
    float4 position [[position]];
 TriangleOutput;

vertex TriangleOutput VertexColor(const device Triangle *Vertices [[buffer(0)]], const uint index [[vertex_id]])

    TriangleOutput out;
    out.position = float4(Vertices[index].position, 0.0, 1.0);
    return out;


vertex TriangleOutput VertexColorb(const device Triangle *Vertices [[buffer(0)]], const uint index [[vertex_id]])

    TriangleOutput out;
    out.position = float4(Vertices[index].position, 0.0, 1.0);
    return out;


fragment half4 FragmentColor(void)

    return half4(1.0, 0.0, 0.0, 1.0);


fragment half4 FragmentColorb(void)

    return half4(1.0, 0.0, 1.0, 1.0);

【问题讨论】:

【参考方案1】:

您正在尝试将多个渲染通道编码到一个命令缓冲区中(一个编码器 = 一个通道)。这是有效的,但您需要注意加载和存储操作以使其正常工作。由于您的渲染通道描述符配置为在两次通道之前清除,因此您的帧缓冲区附件基本上在第二通道之前被擦除。相反,您应该在开始第二次传递之前在传递描述符上设置Load 加载操作。

【讨论】:

那么我应该只使用一个编码器吗?在这种情况下,如何更改缓冲区之间的着色器? 要在单遍中更改着色器程序,只需在相应的绘制调用之前在编码器上设置适当的渲染管线状态。当您发出绘图调用时,编码器上设置的状态基本上是“快照”,因此您可以在一次传递中设置管道状态任意多次。

以上是关于Metal:在一个渲染过程中使用多个 MTLRenderCommandEncoder的主要内容,如果未能解决你的问题,请参考以下文章

使用 SceneKit 通过 Metal 计算管道渲染几何图形

Metal渲染:实现旋转/翻转功能

iOS12 导致 Metal Command Buffer 执行出错,渲染出现故障或未发生

使用 Metal 对 SceneKit 渲染进行抗锯齿处理

Metal渲染:实现画面比例功能

高质量渲染——RealityKit vs SceneKit vs Metal