MetalVertexStreaming/AAPLRenderer.mm

/*
 Copyright (C) 2014 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 
  Metal Renderer for Metal Vertex Streaming sample. Acts as the update and render delegate for the view controller and performs rendering. Renders a simple basic triangle with and updates the vertices every frame using a shared CPU/GPU memory buffer.
  
 */
 
#import "AAPLRenderer.h"
#import "AAPLViewController.h"
#import "AAPLView.h"
 
#import <simd/simd.h>
 
static const long kMaxBufferBytesPerFrame = 1024*1024;
static const long kInFlightCommandBuffers = 3;
 
static const simd::float4 vertexData[] = {
    { -1.0f, -1.0f, 0.0f, 1.0f,  },
    { -1.0f,  1.0f, 0.0f, 1.0f,  },
    {  1.0f, -1.0f, 0.0f, 1.0f,  },
    
    {  1.0f, -1.0f, 0.0f, 1.0f,  },
    { -1.0f,  1.0f, 0.0f, 1.0f,  },
    {  1.0f,  1.0f, 0.0f, 1.0f,  },
    
    { -0.0f, 0.25f, 0.0f, 1.0f   },    
    { -0.25f, -0.25f, 0.0f, 1.0f },
    { 0.25f, -0.25f, 0.0f, 1.0f  },
};
 
static const simd::float4 vertexColorData[] = {
    { 0.0f, 0.0f, 1.0f, 1.0f },
    { 0.0f, 0.0f, 1.0f, 1.0f },
    { 0.0f, 0.0f, 1.0f, 1.0f },
    
    { 0.0f, 0.0f, 1.0f, 1.0f },
    { 0.0f, 0.0f, 1.0f, 1.0f },
    { 0.0f, 0.0f, 1.0f, 1.0f },
    
    { 0.0f, 0.0f, 1.0f, 1.0f },
    { 0.0f, 1.0f, 0.0f, 1.0f },
    { 1.0f, 0.0f, 0.0f, 1.0f },
};
 
// Current animated triangle offsets.
simd::float3 xOffset = { -1.0f, 1.0f, -1.0f };
simd::float3 yOffset = {  1.0f, 0.0f, -1.0f };
 
// Current vertex deltas
simd::float3 xDelta = { 0.002, -0.001, 0.003 };
simd::float3 yDelta = { 0.001,  0.002, -0.001 };
 
@implementation AAPLRenderer
{
    id <MTLDevice> _device;
    id <MTLCommandQueue> _commandQueue;
    id <MTLLibrary> _defaultLibrary;
    
    dispatch_semaphore_t _inflight_semaphore;
    
    // render stage
    id <MTLRenderPipelineState> _pipelineState;
    id <MTLBuffer> _vertexBuffer;
    id <MTLBuffer> _vertexColorBuffer;
}
 
- (instancetype)init
{
    self = [super init];
    if (self) {
        
        _sampleCount = 4;
        _depthPixelFormat = MTLPixelFormatInvalid;
        _stencilPixelFormat = MTLPixelFormatInvalid;
        
        // find a usable Device
        _device = MTLCreateSystemDefaultDevice();
        
        // create a new command queue
        _commandQueue = [_device newCommandQueue];
        
        _defaultLibrary = [_device newDefaultLibrary];
        
        if(!_defaultLibrary)
        {
            NSLog(@">> ERROR: Couldnt create a default shader library");
            
            // assert here becuase if the shader libary isnt loading, its good place to debug why shaders arent compiling
            assert(0);
        }
        
        _constantDataBufferIndex = 0;
        _inflight_semaphore = dispatch_semaphore_create(kInFlightCommandBuffers);
    }
    return self;
}
 
#pragma mark RENDER VIEW DELEGATE METHODS
 
- (void)configure:(AAPLView *)view
{
    view.depthPixelFormat   = _depthPixelFormat;
    view.stencilPixelFormat = _stencilPixelFormat;
    view.sampleCount        = _sampleCount;
    
    // load the vertex program into the library
    id <MTLFunction> vertexProgram = [_defaultLibrary newFunctionWithName:@"passThroughVertex"];
    
    if(!vertexProgram)
    {
        NSLog(@">> ERROR: Couldnt load vertex function from default library");
    }
    
    // load the fragment program into the library
    id <MTLFunction> fragmentProgram = [_defaultLibrary newFunctionWithName:@"passThroughFragment"];
    
    if(!fragmentProgram)
    {
        NSLog(@">> ERROR: Couldnt load fragment function from default library");
    }
    
    // set the vertex shader and buffers defined in the shader source, in this case we have 2 inputs. A position buffer and a color buffer
    // Allocate a buffer to store vertex position data (we'll quad buffer this one)
    _vertexBuffer = [_device newBufferWithLength:kMaxBufferBytesPerFrame
                                         options:0];
    
    _vertexBuffer.label = @"Vertices";
    
    // Single static buffer for color information
    _vertexColorBuffer = [_device newBufferWithBytes: vertexColorData
                                              length: sizeof(vertexColorData)
                                             options:0];
    
    _vertexBuffer.label = @"colors";
    
    //  create a reusable pipeline state
    MTLRenderPipelineDescriptor *pipelineStateDescriptor = [MTLRenderPipelineDescriptor new];
    
    pipelineStateDescriptor.label = @"MyPipeline";
    
    pipelineStateDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
    
    pipelineStateDescriptor.sampleCount      = _sampleCount;
    pipelineStateDescriptor.vertexFunction   = vertexProgram;
    pipelineStateDescriptor.fragmentFunction = fragmentProgram;
    
    _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
                                                             error:nil];
}
 
- (void)_renderTriangle:(id <MTLRenderCommandEncoder>)renderEncoder
                   view:(AAPLView *)view
                   name:(NSString *)name
{
    [renderEncoder pushDebugGroup:name];
    
    //  set context state
    [renderEncoder setRenderPipelineState:_pipelineState];
    
    [renderEncoder setVertexBuffer:_vertexBuffer
                            offset:256*_constantDataBufferIndex
                           atIndex:0 ];
    
    [renderEncoder setVertexBuffer:_vertexColorBuffer
                            offset:0
                           atIndex:1 ];
    
    // tell the render context we want to draw our primitives
    [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:9 instanceCount:1];
    
    [renderEncoder popDebugGroup];
}
 
- (void)render:(AAPLView *)view
{
    dispatch_semaphore_wait(_inflight_semaphore, DISPATCH_TIME_FOREVER);
    
    // create a new command buffer for each renderpass to the current drawable
    id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    
    // create a render command encoder so we can render into something
    MTLRenderPassDescriptor *renderPassDescriptor = view.renderPassDescriptor;
    if (renderPassDescriptor)
    {
        id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        
        [self _renderTriangle:renderEncoder view:view name:@"Triangle"];
        
        [renderEncoder endEncoding];
        
        // call the view's completion handler which is required by the view since it will signal its semaphore and set up the next buffer
        __block dispatch_semaphore_t block_sema = _inflight_semaphore;
        
        [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
            dispatch_semaphore_signal(block_sema);
        }];
        
        // schedule a present once the framebuffer is complete
        [commandBuffer presentDrawable:view.currentDrawable];
        
        // finalize rendering here. this will push the command buffer to the GPU
        [commandBuffer commit];
    }
    else
    {
        // release the semaphore to keep things synchronized even if we couldnt render
        dispatch_semaphore_signal(_inflight_semaphore);
    }
        
    // the renderview assumes it can now increment the buffer index and that the previous index wont be touched
    // until we cycle back around to the same index
    _constantDataBufferIndex = (_constantDataBufferIndex + 1) % kInFlightCommandBuffers;
}
 
- (void)reshape:(AAPLView *)view
{
    // unused in this sample
}
 
#pragma mark VIEW CONTROLLER DELEGATE METHODS
 
- (void)update:(AAPLViewController *)controller
{
    simd::float4 *vData = (simd::float4 *)((uintptr_t)[_vertexBuffer contents] + 256*_constantDataBufferIndex);
    
    // reset the vertex data in the shared cpu/gpu buffer each frame and just accumulate offsets below
    memcpy(vData,vertexData,sizeof(vertexData));
    
    // Animate triangle offsets
    int j;
    
    for(j = 0; j < 3; j++)
    {
        xOffset[j] += xDelta[j];
        
        if(xOffset[j] >= 1.0 || xOffset[j] <= -1.0)
        {
            xDelta[j] = -xDelta[j];
            xOffset[j] += xDelta[j];
        }
        
        yOffset[j] += yDelta[j];
        
        if(yOffset[j] >= 1.0 || yOffset[j] <= -1.0)
        {
            yDelta[j] = -yDelta[j];
            yOffset[j] += yDelta[j];
        }
        
        // Update last triangle position directly in the shared cpu/gpu buffer
        vData[6+j].x = xOffset[j];
        vData[6+j].y = yOffset[j];
    }
}
 
- (void)viewController:(AAPLViewController *)controller willPause:(BOOL)pause
{
    // timer is suspended/resumed
    // Can do any non-rendering related background work here when suspended
}
 
 
@end