MetalUniformStreaming/AAPLRenderer.mm

/*
 Copyright (C) 2014 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 */
 
#import "AAPLView.h"
#import "AAPLViewController.h"
 
#import "AAPLCube.h"
#import "AAPLPlasmaUniforms.h"
 
#import "AAPLRenderer.h"
 
static const uint32_t kInFlightCommandBuffers = 3;
 
@implementation AAPLRenderer
{
@private
    id <MTLDevice> _device;
    MTLPixelFormat _depthPixelFormat;
    MTLPixelFormat _stencilPixelFormat;
    NSUInteger     _sampleCount;
    
    AAPLCube            *mpCube;
    AAPLPlasmaUniforms  *mpPlasmaUniforms;
 
    id <MTLCommandQueue>         m_CommandQueue;
    id <MTLLibrary>              m_ShaderLibrary;
    id <MTLDepthStencilState>    m_DepthState;
    id <MTLRenderPipelineState>  m_PipelineState;
    
    dispatch_semaphore_t  m_InflightSemaphore;
}
 
- (void) cleanup
{
    // Private
    m_PipelineState  = nil;
    m_DepthState     = nil;
    m_ShaderLibrary  = nil;
    m_CommandQueue   = nil;
    mpPlasmaUniforms = nil;
    mpCube           = nil;
}
 
- (instancetype) init
{
    self = [super init];
    
    if(self)
    {
        // Set the default pixel sample count
        _sampleCount = 4;
        
        // Set the default pixel formats
        _depthPixelFormat   = MTLPixelFormatDepth32Float;
        _stencilPixelFormat = MTLPixelFormatInvalid;
        
        // find a usable Device
        _device = MTLCreateSystemDefaultDevice();
        if(!_device)
        {
            NSLog(@">> ERROR: Failed creating a default system device!");
            
            exit(-1);
        } // if
        
        // load offline compiled shaders
        m_ShaderLibrary = [_device newDefaultLibrary];
        if(!m_ShaderLibrary)
        {
            NSLog(@">> ERROR: Failed creating a new library!");
            
            assert(0);
        } // if
        
        // setup a semaphore to synchronize between command buffer encoding and submission
        m_InflightSemaphore = dispatch_semaphore_create(kInFlightCommandBuffers);
    }
    
    return self;
} // init
 
#pragma mark - Configure
 
- (void) configure:(AAPLView *)view
{
    //configure the view with teh same pixel format and sample count used for the pipeline state object
    view.depthPixelFormat   = _depthPixelFormat;
    view.stencilPixelFormat = _stencilPixelFormat;
    view.sampleCount        = _sampleCount;
    
    // create a new command queue
    m_CommandQueue = [_device newCommandQueue];
    
    if(![self preparePipelineState])
    {
        NSLog(@">> ERROR: Failed creating a depth stencil state descriptor!");
        
        // if we dont have a valid compiled pieline state object, something went seriously wrong
        assert(0);
    }
    
    // setup content
    if(![self setupContent])
    {
        NSLog(@">> ERROR: Failed loading the assets!");
        
        exit(-1);
    }
} // configure
 
- (BOOL) preparePipelineState
{
    // load the vertex program into the library
    id <MTLFunction> vertexProgram = [m_ShaderLibrary newFunctionWithName:@"plasmaVertex"];
    
    if(!vertexProgram)
    {
        NSLog(@">> ERROR: Failed creating a new vertex program!");
        
        return NO;
    } // if
    
    // load the fragment program into the library
    id <MTLFunction> fragmentProgram = [m_ShaderLibrary newFunctionWithName:@"plasmaFragment"];
    
    if(!fragmentProgram)
    {
        NSLog(@">> ERROR: Failed creating a new fragment program!");
        
        return NO;
    } // if
    
    // Create a reusable pipeline state
    MTLRenderPipelineDescriptor *pPipelineStateDescriptor = [MTLRenderPipelineDescriptor new];
    
    if(!pPipelineStateDescriptor)
    {
        NSLog(@">> ERROR: Failed creating a new pipeline state descriptor!");
        
        return NO;
    } // if
    
    pPipelineStateDescriptor.depthAttachmentPixelFormat      = _depthPixelFormat;
    pPipelineStateDescriptor.stencilAttachmentPixelFormat    = _stencilPixelFormat;
    pPipelineStateDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
    
    pPipelineStateDescriptor.sampleCount      = _sampleCount;
    pPipelineStateDescriptor.vertexFunction   = vertexProgram;
    pPipelineStateDescriptor.fragmentFunction = fragmentProgram;
    
    NSError  *pError = nil;
    
    m_PipelineState = [_device newRenderPipelineStateWithDescriptor:pPipelineStateDescriptor
                                                              error:&pError];
    
    if(!m_PipelineState)
    {
        NSLog(@">> ERROR: Failed creating a new render pipeline state descriptor: %@", pError);
        
        return NO;
    } // if
 
    return YES;
} // preparePipelineState
 
- (BOOL) prepareDepthState
{
    MTLDepthStencilDescriptor *pDepthStateDesc = [MTLDepthStencilDescriptor new];
    
    if(!pDepthStateDesc)
    {
        NSLog(@">> ERROR: Failed creating a depth stencil descriptor!");
        
        return NO;
    } // if
    
    pDepthStateDesc.depthCompareFunction = MTLCompareFunctionLess;
    pDepthStateDesc.depthWriteEnabled    = YES;
    
    m_DepthState = [_device newDepthStencilStateWithDescriptor:pDepthStateDesc];
    
    if(!m_DepthState)
    {
        return NO;
    } // if
    
    return YES;
} // prepareDepthState
 
- (BOOL) setupContent
{
    // Create 3d cube with fixed dimensions
    AAPL::Vector3 size = {0.75f, 0.75f, 0.75f};
    
    mpCube = [[AAPLCube alloc] initWithDevice:_device
                                         size:size];
    
    if(!mpCube)
    {
        NSLog(@">> ERROR: Failed creating 3d cube!");
        
        return NO;
    } // if
    
    // allocate one region of memory for the constant buffer per max in
    // flight command buffers so that memory is properly syncronized.
    mpPlasmaUniforms = [[AAPLPlasmaUniforms alloc] initWithDevice:_device
                                                         capacity:kInFlightCommandBuffers];
    
    if(!mpPlasmaUniforms)
    {
        NSLog(@">> ERROR: Failed creating plasma constants!");
        
        return NO;
    } // if
    
    if(![self prepareDepthState])
    {
        NSLog(@">> ERROR: Failed creating a depth stencil!");
        
        return NO;
    } // if
    
    return YES;
} // configure
 
#pragma mark render
 
- (void) encode:(id <MTLRenderCommandEncoder>)renderEncoder
{
    // Set context state with the render encoder
    [renderEncoder pushDebugGroup:@"plasma cubes"];
    [renderEncoder setDepthStencilState:m_DepthState];
    [renderEncoder setRenderPipelineState:m_PipelineState];
    
    // Encode a 3d cube into renderer
    [mpCube encode:renderEncoder];
    
    // Encode into renderer the first set of vertex and fragment uniforms
    [mpPlasmaUniforms encode:renderEncoder];
    
    // Tell the render context we want to draw our first set of primitives
    [mpCube draw:renderEncoder];
    
    // Encode into renderer the second set of vertex and fragment uniforms
    [mpPlasmaUniforms encode:renderEncoder];
    
    // Tell the render context we want to draw our second set of primitives
    [mpCube draw:renderEncoder];
    
    // The encoding is now complete
    [renderEncoder endEncoding];
    [renderEncoder popDebugGroup];
} // encode
 
- (void) render:(AAPLView *)view
{
    // Wait on semaphore
    dispatch_semaphore_wait(m_InflightSemaphore, DISPATCH_TIME_FOREVER);
    
    // Upload the uniforms into the structure bound to shaders
    [mpPlasmaUniforms upload];
    
    // Acquire a command buffer
    id <MTLCommandBuffer> commandBuffer = [m_CommandQueue commandBuffer];
    
    MTLRenderPassDescriptor *renderPassDescriptor = view.renderPassDescriptor;
    if (renderPassDescriptor)
    {
        // Get a render encoder
        id <MTLRenderCommandEncoder>  renderEncoder
        = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        
        // Encode into a renderer
        [self encode:renderEncoder];
        
        // 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 dispatchSemaphore = m_InflightSemaphore;
        
        [commandBuffer addCompletedHandler:^(id <MTLCommandBuffer> cmdb){
            dispatch_semaphore_signal(dispatchSemaphore);
        }];
        
        // Schedule a present once the framebuffer is complete
        [commandBuffer presentDrawable:view.currentDrawable];
        
        // Finalize rendering here & push the command buffer to the GPU
        [commandBuffer commit];
    }
    else
    {
        dispatch_semaphore_signal(m_InflightSemaphore);
    }
} // render
 
- (void) reshape:(AAPLView *)view
{
    mpPlasmaUniforms.bounds = view.bounds;
} // reshape
 
- (void) update:(AAPLViewController *)controller
{
    [mpPlasmaUniforms update];
} // update
 
- (void) viewController:(AAPLViewController *)controller
              willPause:(BOOL)pause
{
    // timer is suspended/resumed
    // Can do any non-rendering related background work here when suspended
} // pause
 
@end