// // Renderer.m // MetalRenderer // // Created by Stuart Carnie on 5/31/18. // Copyright © 2018 Stuart Carnie. All rights reserved. // #import #import "RendererCommon.h" #import "Renderer.h" #import "View.h" #import "PixelConverter+private.h" // Include header shared between C code here, which executes Metal API commands, and .metal files #import "ShaderTypes.h" @implementation Renderer { dispatch_semaphore_t _inflightSemaphore; id _device; id _library; id _commandQueue; Context *_context; PixelConverter *_conv; CAMetalLayer *_layer; // render target layer state id _t_pipelineState; id _t_pipelineStateNoAlpha; MTLRenderPassDescriptor *_t_rpd; id _samplerStateLinear; id _samplerStateNearest; // views NSMutableArray> *_views; // other state Uniforms _uniforms; BOOL _begin; } - (instancetype)initWithDevice:(id)device layer:(CAMetalLayer *)layer { self = [super init]; if (self) { _inflightSemaphore = dispatch_semaphore_create(MAX_INFLIGHT); _device = device; _layer = layer; _views = [NSMutableArray new]; [self _initMetal]; _conv = [[PixelConverter alloc] initWithContext:_context]; _begin = NO; } return self; } - (void)_initMetal { _commandQueue = [_device newCommandQueue]; _library = [_device newDefaultLibrary]; _context = [Context newContextWithDevice:_device layer:_layer library:_library commandQueue:_commandQueue]; { MTLVertexDescriptor *vd = [MTLVertexDescriptor new]; vd.attributes[0].offset = 0; vd.attributes[0].format = MTLVertexFormatFloat3; vd.attributes[1].offset = offsetof(Vertex, texCoord); vd.attributes[1].format = MTLVertexFormatFloat2; vd.layouts[0].stride = sizeof(Vertex); vd.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; MTLRenderPipelineDescriptor *psd = [MTLRenderPipelineDescriptor new]; psd.label = @"Pipeline+Alpha"; MTLRenderPipelineColorAttachmentDescriptor *ca = psd.colorAttachments[0]; ca.pixelFormat = _layer.pixelFormat; ca.blendingEnabled = YES; ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; ca.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; ca.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; psd.sampleCount = 1; psd.vertexDescriptor = vd; psd.vertexFunction = [_library newFunctionWithName:@"basic_vertex_proj_tex"]; psd.fragmentFunction = [_library newFunctionWithName:@"basic_fragment_proj_tex"]; NSError *err; _t_pipelineState = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; if (err != nil) { NSLog(@"error creating pipeline state: %@", err.localizedDescription); abort(); } ca.blendingEnabled = NO; _t_pipelineStateNoAlpha = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; if (err != nil) { NSLog(@"error creating pipeline state: %@", err.localizedDescription); abort(); } } { MTLRenderPassDescriptor *rpd = [MTLRenderPassDescriptor new]; rpd.colorAttachments[0].loadAction = MTLLoadActionDontCare; rpd.colorAttachments[0].storeAction = MTLStoreActionStore; _t_rpd = rpd; } { MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new]; _samplerStateNearest = [_device newSamplerStateWithDescriptor:sd]; sd.minFilter = MTLSamplerMinMagFilterLinear; sd.magFilter = MTLSamplerMinMagFilterLinear; _samplerStateLinear = [_device newSamplerStateWithDescriptor:sd]; } } - (void)_updateUniforms { //CGSize s = _layer.drawableSize; //_uniforms.projectionMatrix = matrix_proj_ortho(0, s.width, 0, s.height); _uniforms.projectionMatrix = matrix_proj_ortho(0, 1, 0, 1); } - (void)beginFrame { assert(!_begin); _begin = YES; dispatch_semaphore_wait(_inflightSemaphore, DISPATCH_TIME_FOREVER); [_context begin]; [self _updateUniforms]; } - (void)drawFrame { @autoreleasepool { [self _render]; } } - (void)_render { assert(_begin); _begin = NO; id cb = _context.commandBuffer; cb.label = @"renderer cb"; for (id v in _views) { if (!v.visible) continue; if ([v respondsToSelector:@selector(drawWithContext:)]) { [v drawWithContext:_context]; } } BOOL pendingDraws = NO; for (id v in _views) { if (v.visible && (v.drawState & ViewDrawStateEncoder) != 0) { pendingDraws = YES; break; } } if (pendingDraws) { id drawable = _context.nextDrawable; _t_rpd.colorAttachments[0].texture = drawable.texture; id rce = [cb renderCommandEncoderWithDescriptor:_t_rpd]; [rce setVertexBytes:&_uniforms length:sizeof(_uniforms) atIndex:BufferIndexUniforms]; for (id v in _views) { if (!v.visible || ![v respondsToSelector:@selector(drawWithEncoder:)] || (v.drawState & ViewDrawStateEncoder) == 0) { continue; } // set view state if (v.format == RPixelFormatBGRX8Unorm || v.format == RPixelFormatB5G6R5Unorm) { [rce setRenderPipelineState:_t_pipelineStateNoAlpha]; } else { [rce setRenderPipelineState:_t_pipelineState]; } if (v.filter == RTextureFilterNearest) { [rce setFragmentSamplerState:_samplerStateNearest atIndex:SamplerIndexDraw]; } else { [rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw]; } [v drawWithEncoder:rce]; } [rce endEncoding]; } __block dispatch_semaphore_t inflight = _inflightSemaphore; [cb addCompletedHandler:^(id _) { dispatch_semaphore_signal(inflight); }]; [cb presentDrawable:_context.nextDrawable]; [_context end]; } #pragma mark - view APIs - (void)bringViewToFront:(id)view { NSUInteger pos = [_views indexOfObject:view]; if (pos == NSNotFound || pos == _views.count - 1) return; [_views removeObjectAtIndex:pos]; [_views addObject:view]; } - (void)sendViewToBack:(id)view { NSUInteger pos = [_views indexOfObject:view]; if (pos == NSNotFound || pos == 0) return; [_views removeObjectAtIndex:pos]; [_views insertObject:view atIndex:0]; } - (void)addView:(id)view { [_views addObject:view]; } - (void)removeView:(id)view { NSUInteger pos = [_views indexOfObject:view]; if (pos == NSNotFound) return; [_views removeObjectAtIndex:pos]; } - (void)drawableSizeWillChange:(CGSize)size { _layer.drawableSize = size; } #pragma mark Matrix Math Utilities extern inline matrix_float4x4 matrix_proj_ortho(float left, float right, float top, float bottom) { float near = 0; float far = 1; float sx = 2 / (right - left); float sy = 2 / (top - bottom); float sz = 1 / (far - near); float tx = (right + left) / (left - right); float ty = (top + bottom) / (bottom - top); float tz = near / (far - near); vector_float4 P = {sx, 0, 0, 0}; vector_float4 Q = {0, sy, 0, 0}; vector_float4 R = {0, 0, sz, 0}; vector_float4 S = {tx, ty, tz, 1}; matrix_float4x4 mat = {P, Q, R, S}; return mat; } @end