I am starting to play with Metal (currently my app is working with OpenGL). I am trying to check how to render with multiple pipeline states (multiple metal functions) in one rendering pass. The problem is that always only the last MTLRenderCommandEncoder is actually drawn. The following is my code:
@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;
}
My shaders:
#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);
}
You're trying to encode multiple render passes into a single command buffer (one encoder = one pass). This is valid, but you need to be mindful of your load and store actions in order to make it work. Since your render pass descriptor is configured to clear before both passes, your framebuffer attachments are basically being erased before the second pass. Instead, you should set the Load
load action on the pass descriptor before beginning the second pass.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With