+ (Class)layerClass { return [CAMetalLayer class]; } - (void)initCommon { self.opaque = YES; self.backgroundColor = nil; _metalLayer = (CAMetalLayer *)self.layer; _device = MTLCreateSystemDefaultDevice(); _metalLayer.device = _device; _metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm; _metalLayer.framebufferOnly = YES; _sampleCount = 1; _depthPixelFormat = MTLPixelFormatDepth32Float; _stencilPixelFormat = MTLPixelFormatInvalid; }
- (void)setupRenderPassDescriptorForTexture:(id <MTLTexture>)texture { if (_renderPassDescriptor == nil) _renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; // init/update default render target MTLRenderPassColorAttachmentDescriptor* colorAttachment = _renderPassDescriptor.colorAttachments[0]; colorAttachment.texture = texture; colorAttachment.loadAction = MTLLoadActionClear; colorAttachment.clearColor = MTLClearColorMake(0.0f, 0.0f, 0.0f, 1.0f); if(_sampleCount > 1) { BOOL doUpdate = (_msaaTexture.width != texture.width) || ( _msaaTexture.height != texture.height) || ( _msaaTexture.sampleCount != _sampleCount); if(!_msaaTexture || (_msaaTexture && doUpdate)) { MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: MTLPixelFormatBGRA8Unorm width: texture.width height: texture.height mipmapped: NO]; desc.textureType = MTLTextureType2DMultisample; desc.sampleCount = _sampleCount; _msaaTexture = [_device newTextureWithDescriptor: desc]; _msaaTexture.label = @"Default MSAA render target"; } colorAttachment.texture = _msaaTexture; colorAttachment.resolveTexture = texture; colorAttachment.storeAction = MTLStoreActionMultisampleResolve; } else { colorAttachment.storeAction = MTLStoreActionStore; } // init/update default depth buffer if(_depthPixelFormat != MTLPixelFormatInvalid) { BOOL doUpdate = (_depthTexture.width != texture.width) || (_depthTexture.height != texture.height) || (_depthTexture.sampleCount != _sampleCount); if(!_depthTexture || doUpdate) { MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: _depthPixelFormat width: texture.width height: texture.height mipmapped: NO]; desc.textureType = (_sampleCount > 1) ? MTLTextureType2DMultisample : MTLTextureType2D; desc.sampleCount = _sampleCount; _depthTexture = [_device newTextureWithDescriptor: desc]; _depthTexture.label = @"Default depth buffer"; MTLRenderPassDepthAttachmentDescriptor* depthAttachment = _renderPassDescriptor.depthAttachment; depthAttachment.texture = _depthTexture; depthAttachment.loadAction = MTLLoadActionClear; depthAttachment.storeAction = MTLStoreActionDontCare; depthAttachment.clearDepth = 1.0; } } // init/update default stencil buffer if(_stencilPixelFormat != MTLPixelFormatInvalid) { BOOL doUpdate = (_stencilTexture.width != texture.width) || (_stencilTexture.height != texture.height) || (_stencilTexture.sampleCount != _sampleCount); if (!_stencilTexture || doUpdate) { MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: _stencilPixelFormat width: texture.width height: texture.height mipmapped: NO]; desc.textureType = (_sampleCount > 1) ? MTLTextureType2DMultisample : MTLTextureType2D; desc.sampleCount = _sampleCount; _stencilTexture = [_device newTextureWithDescriptor: desc]; _stencilTexture.label = @"Default stencil buffer"; MTLRenderPassStencilAttachmentDescriptor* stencilAttachment = _renderPassDescriptor.stencilAttachment; stencilAttachment.texture = _stencilTexture; stencilAttachment.loadAction = MTLLoadActionClear; stencilAttachment.storeAction = MTLStoreActionDontCare; stencilAttachment.clearStencil = 0; } } }
- (void)startTimer { _timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(_renderloop)]; [_timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; }
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSArray* touchesArray = [touches allObjects]; if (touches.count == 1) { if (!camera.isRotatingNow()) { CGPoint pos = [touchesArray[0] locationInView: self.view]; camera.startRotation(pos.x, pos.y); } else { // here we put second finger simd::float2 lastPos = camera.getLastFingerPosition(); camera.stopRotation(); CGPoint pos = [touchesArray[0] locationInView: self.view]; float d = vector_distance(simd::float2 { (float)pos.x, (float)pos.y }, lastPos); camera.startZooming(d); } } else if (touches.count == 2) { CGPoint pos1 = [touchesArray[0] locationInView: self.view]; CGPoint pos2 = [touchesArray[1] locationInView: self.view]; float d = vector_distance(simd::float2 { (float)pos1.x, (float)pos1.y }, simd::float2 { (float)pos2.x, (float)pos2.y }); camera.startZooming(d); } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { NSArray* touchesArray = [touches allObjects]; if (touches.count != 0 && camera.isRotatingNow()) { CGPoint pos = [touchesArray[0] locationInView: self.view]; camera.updateRotation(pos.x, pos.y); } else if (touches.count == 2 && camera.isZoomingNow()) { CGPoint pos1 = [touchesArray[0] locationInView: self.view]; CGPoint pos2 = [touchesArray[1] locationInView: self.view]; float d = vector_distance(simd::float2 { (float)pos1.x, (float)pos1.y }, simd::float2 { (float)pos2.x, (float)pos2.y }); camera.updateZooming(d); } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { camera.stopRotation(); camera.stopZooming(); }
- (void)configure:(RenderView*)renderView
- (void)setupMetal:(id<MTLDevice>)device
- (void)update
- (void)render:(RenderView*)renderView
- (void)resize:(RenderView*)renderView
// uint8_t* bufferPointer = (uint8_t*)[_dynamicUniformBuffer contents] + (sizeof(uniforms_t) * _currentUniformBufferIndex); memcpy(bufferPointer, &_uniform_buffer, sizeof(uniforms_t)); // [renderEncoder setVertexBuffer:_dynamicUniformBuffer offset:(sizeof(uniforms_t) * _currentUniformBufferIndex) atIndex:1 ];
- (void)setupMetal:(id<MTLDevice>)device { _commandQueue = [device newCommandQueue]; _defaultLibrary = [device newDefaultLibrary]; [self loadAssets: device]; } - (void)loadAssets:(id<MTLDevice>)device { _dynamicUniformBuffer = [device newBufferWithLength:MAX_UNIFORM_BUFFER_SIZE options:0]; _dynamicUniformBuffer.label = @"Uniform buffer"; id <MTLFunction> fragmentProgram = [_defaultLibrary newFunctionWithName:@"psLighting"]; id <MTLFunction> vertexProgram = [_defaultLibrary newFunctionWithName:@"vsLighting"]; _vertexBuffer = [device newBufferWithBytes:(Primitives::cube()) length:(Primitives::cubeSizeInBytes()) options:MTLResourceOptionCPUCacheModeDefault]; _vertexBuffer.label = @"Cube vertex buffer"; // pipeline state MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; pipelineStateDescriptor.label = @"Simple pipeline"; [pipelineStateDescriptor setSampleCount: ((RenderView*)self.view).sampleCount]; [pipelineStateDescriptor setVertexFunction:vertexProgram]; [pipelineStateDescriptor setFragmentFunction:fragmentProgram]; pipelineStateDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float; NSError* error = NULL; _pipelineState = [device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error]; if (!_pipelineState) { NSLog(@"Failed to created pipeline state, error %@", error); } MTLDepthStencilDescriptor *depthStateDesc = [[MTLDepthStencilDescriptor alloc] init]; depthStateDesc.depthCompareFunction = MTLCompareFunctionLess; depthStateDesc.depthWriteEnabled = YES; _depthState = [device newDepthStencilStateWithDescriptor:depthStateDesc]; }
- (void)render:(RenderView*)renderView { dispatch_semaphore_wait(_inflightSemaphore, DISPATCH_TIME_FOREVER); [self update]; MTLRenderPassDescriptor* renderPassDescriptor = renderView.renderPassDescriptor; id <CAMetalDrawable> drawable = renderView.currentDrawable; // new command buffer id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer]; commandBuffer.label = @"Simple command buffer"; // simple render encoder id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor: renderPassDescriptor]; renderEncoder.label = @"Simple render encoder"; [renderEncoder setDepthStencilState:_depthState]; [renderEncoder pushDebugGroup:@"Draw cube"]; [renderEncoder setRenderPipelineState:_pipelineState]; [renderEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:0 ]; [renderEncoder setVertexBuffer:_dynamicUniformBuffer offset:(sizeof(uniforms_t) * _currentUniformBufferIndex) atIndex:1 ]; [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:36 instanceCount:1]; [renderEncoder popDebugGroup]; [renderEncoder endEncoding]; __block dispatch_semaphore_t block_sema = _inflightSemaphore; [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) { dispatch_semaphore_signal(block_sema); }]; _currentUniformBufferIndex = (_currentUniformBufferIndex + 1) % MAX_INFLIGHT_BUFFERS; [commandBuffer presentDrawable:drawable]; [commandBuffer commit]; }
#include <metal_stdlib> #include <simd/simd.h> using namespace metal; constant float3 lightDirection = float3(0.5, -0.7, -1.0); constant float3 ambientColor = float3(0.18, 0.24, 0.8); constant float3 diffuseColor = float3(0.4, 0.4, 1.0); constant float3 specularColor = float3(0.3, 0.3, 0.3); constant float specularPower = 30.0; typedef struct { float4x4 modelViewProjection; float4x4 model; float3 viewPosition; } uniforms_t; typedef struct { packed_float3 position; packed_float3 normal; packed_float3 tangent; } vertex_t; typedef struct { float4 position [[position]]; float3 tangent; float3 normal; float3 viewDirection; } ColorInOut; // Vertex shader function vertex ColorInOut vsLighting(device vertex_t* vertex_array [[ buffer(0) ]], constant uniforms_t& uniforms [[ buffer(1) ]], unsigned int vid [[ vertex_id ]]) { ColorInOut out; float4 in_position = float4(float3(vertex_array[vid].position), 1.0); out.position = uniforms.modelViewProjection * in_position; float4x4 m = uniforms.model; m[3][0] = m[3][1] = m[3][2] = 0.0f; // suppress translation component out.normal = (m * float4(normalize(vertex_array[vid].normal), 1.0)).xyz; out.tangent = (m * float4(normalize(vertex_array[vid].tangent), 1.0)).xyz; float3 worldPos = (uniforms.model * in_position).xyz; out.viewDirection = normalize(worldPos - uniforms.viewPosition); return out; } // Fragment shader function fragment half4 psLighting(ColorInOut in [[stage_in]]) { float3 normalTS = float3(0, 0, 1); float3 lightDir = normalize(lightDirection); float3x3 ts = float3x3(in.tangent, cross(in.normal, in.tangent), in.normal); float3 normal = -normalize(ts * normalTS); float ndotl = fmax(0.0, dot(lightDir, normal)); float3 diffuse = diffuseColor * ndotl; float3 h = normalize(in.viewDirection + lightDir); float3 specular = specularColor * pow (fmax(dot(normal, h), 0.0), specularPower); float3 finalColor = saturate(ambientColor + diffuse + specular); return half4(float4(finalColor, 1.0)); }
id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor: renderPassDescriptor]; renderEncoder.label = @"Simple render encoder"; [renderEncoder setDepthStencilState:_depthState]; [renderEncoder pushDebugGroup:@"Draw cubes"]; [renderEncoder setRenderPipelineState:_pipelineState]; [renderEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:0 ]; for (int i = 0; i < CUBE_COUNTS; i++) { [renderEncoder setVertexBuffer:_dynamicUniformBuffer offset:(sizeof(_uniform_buffer) * _currentUniformBufferIndex + i * sizeof(uniforms_t)) atIndex:1 ]; [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:36 instanceCount:1]; } [renderEncoder popDebugGroup]; [renderEncoder endEncoding];
- (void)render:(RenderView*)renderView { dispatch_semaphore_wait(_inflightSemaphore, DISPATCH_TIME_FOREVER); [self update]; MTLRenderPassDescriptor* renderPassDescriptor = renderView.renderPassDescriptor; id <CAMetalDrawable> drawable = renderView.currentDrawable; // new command buffer id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer]; commandBuffer.label = @"Simple command buffer"; // parallel render encoder id <MTLParallelRenderCommandEncoder> parallelRCE = [commandBuffer parallelRenderCommandEncoderWithDescriptor:renderPassDescriptor]; parallelRCE.label = @"Parallel render encoder"; id <MTLRenderCommandEncoder> rCE1 = [parallelRCE renderCommandEncoder]; id <MTLRenderCommandEncoder> rCE2 = [parallelRCE renderCommandEncoder]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ { @autoreleasepool { [self encodeRenderCommands: rCE2 Comment: @"Draw cubes in additional thread" StartIndex: CUBE_COUNTS / 2 EndIndex: CUBE_COUNTS]; } dispatch_semaphore_signal(_renderThreadSemaphore); }); [self encodeRenderCommands: rCE1 Comment: @"Draw cubes" StartIndex: 0 EndIndex: CUBE_COUNTS / 2]; // wait additional thread and finish encoding dispatch_semaphore_wait(_renderThreadSemaphore, DISPATCH_TIME_FOREVER); [parallelRCE endEncoding]; __block dispatch_semaphore_t block_sema = _inflightSemaphore; [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) { dispatch_semaphore_signal(block_sema); }]; _currentUniformBufferIndex = (_currentUniformBufferIndex + 1) % MAX_INFLIGHT_BUFFERS; [commandBuffer presentDrawable:drawable]; [commandBuffer commit]; } - (void)encodeRenderCommands:(id <MTLRenderCommandEncoder>)renderEncoder Comment:(NSString*)comment StartIndex:(int)startIndex EndIndex:(int)endIndex { [renderEncoder setDepthStencilState:_depthState]; [renderEncoder pushDebugGroup:comment]; [renderEncoder setRenderPipelineState:_pipelineState]; [renderEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:0 ]; for (int i = startIndex; i < endIndex; i++) { [renderEncoder setVertexBuffer:_dynamicUniformBuffer offset:(sizeof(_uniform_buffer) * _currentUniformBufferIndex + i * sizeof(uniforms_t)) atIndex:1 ]; [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:36 instanceCount:1]; } [renderEncoder popDebugGroup]; [renderEncoder endEncoding]; }
Source: https://habr.com/ru/post/248785/
All Articles