blob: 25014087e77c4ef1c88f2478ac98a710ee160ee6 [file] [log] [blame]
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -04001/*
2 * Copyright (C) 2019 Savoir-faire Linux Inc.
3 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#import "CallMTKView.h"
21
22@implementation CallMTKView {
23 id <MTLBuffer> vertexBuffer;
24 id <MTLDepthStencilState> depthState;
25 id<MTLCommandQueue> commandQueue;
26 id<MTLRenderPipelineState> pipeline;
27 CVMetalTextureCacheRef textureCache;
28}
29
30// Vertex data for an image plane
31static const float kImagePlaneVertexData[16] = {
32 -1.0, -1.0, 0.0, 1.0,
33 1.0, -1.0, 1.0, 1.0,
34 -1.0, 1.0, 0.0, 0.0,
35 1.0, 1.0, 1.0, 0.0,
36};
37
38typedef enum BufferIndices {
39 kBufferIndexMeshPositions = 0,
40} BufferIndices;
41
42typedef enum VertexAttributes {
43 kVertexAttributePosition = 0,
44 kVertexAttributeTexcoord = 1,
45} VertexAttributes;
46
47struct Uniforms {
48 simd::float4x4 projectionMatrix;
49 simd::float4x4 rotationMatrix;
50};
Kateryna Kostiuk06681682020-05-07 20:50:56 -040051@synthesize videoRunning;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -040052
53- (instancetype)initWithFrame:(NSRect)frame
54{
55 self = [super initWithFrame:frame];
56 if (self) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -040057 [self setupView];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -040058 }
59 return self;
60}
61
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -040062-(void)setupView {
63 id<MTLDevice> device = MTLCreateSystemDefaultDevice();
64 self.device = device;
65 commandQueue = [device newCommandQueue];
66 self.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
67 commandQueue = [device newCommandQueue];
68
69 CVReturn err = CVMetalTextureCacheCreate(kCFAllocatorDefault,
70 NULL,
71 self.device,
72 NULL,
73 &textureCache);
74
75 vertexBuffer = [device newBufferWithBytes:&kImagePlaneVertexData
76 length:sizeof(kImagePlaneVertexData)
77 options:MTLResourceCPUCacheModeDefaultCache];
78
79 NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
80 NSString *libraryPath = [resourcePath stringByAppendingPathComponent:@"Shader.metallib"];
81 id <MTLLibrary> library = [device newLibraryWithFile:libraryPath error:nil];
82 id<MTLFunction> vertexFunc = [library newFunctionWithName:@"imageVertex"];
83 id<MTLFunction> fragmentFunc = [library newFunctionWithName:@"imageFragment"];
84
85 // Create a vertex descriptor for our image plane vertex buffer
86 MTLVertexDescriptor *imagePlaneVertexDescriptor = [[MTLVertexDescriptor alloc] init];
87
88 // Positions.
89 imagePlaneVertexDescriptor.attributes[kVertexAttributePosition].format = MTLVertexFormatFloat2;
90 imagePlaneVertexDescriptor.attributes[kVertexAttributePosition].offset = 0;
91 imagePlaneVertexDescriptor.attributes[kVertexAttributePosition].bufferIndex = kBufferIndexMeshPositions;
92
93 // Texture coordinates.
94 imagePlaneVertexDescriptor.attributes[kVertexAttributeTexcoord].format = MTLVertexFormatFloat2;
95 imagePlaneVertexDescriptor.attributes[kVertexAttributeTexcoord].offset = 8;
96 imagePlaneVertexDescriptor.attributes[kVertexAttributeTexcoord].bufferIndex = kBufferIndexMeshPositions;
97
98 // Position Buffer Layout
99 imagePlaneVertexDescriptor.layouts[kBufferIndexMeshPositions].stride = 16;
100 imagePlaneVertexDescriptor.layouts[kBufferIndexMeshPositions].stepRate = 1;
101 imagePlaneVertexDescriptor.layouts[kBufferIndexMeshPositions].stepFunction = MTLVertexStepFunctionPerVertex;
102
103 MTLRenderPipelineDescriptor *pipelineDescriptor = [MTLRenderPipelineDescriptor new];
104 pipelineDescriptor.vertexFunction = vertexFunc;
105 pipelineDescriptor.fragmentFunction = fragmentFunc;
106 pipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
107 pipelineDescriptor.vertexDescriptor = imagePlaneVertexDescriptor;
108
109 pipeline = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:NULL];
110 MTLDepthStencilDescriptor *depthStateDescriptor = [[MTLDepthStencilDescriptor alloc] init];
111 depthStateDescriptor.depthCompareFunction = MTLCompareFunctionAlways;
112 depthStateDescriptor.depthWriteEnabled = NO;
113 depthState = [device newDepthStencilStateWithDescriptor:depthStateDescriptor];
114 self.preferredFramesPerSecond = 30;
Kateryna Kostiuk8f77b792019-10-07 17:08:26 -0400115 self.paused = YES;
116 self.enableSetNeedsDisplay = NO;
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400117}
118
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400119- (void)fillWithBlack {
Kateryna Kostiukdc720842020-09-10 16:44:02 -0400120 self.clearColor = MTLClearColorMake(0, 0, 0, 0);
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400121 id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
Kateryna Kostiukdc720842020-09-10 16:44:02 -0400122 MTLRenderPassDescriptor *renderPassDescriptor = self.currentRenderPassDescriptor;
123 id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
124 [renderEncoder endEncoding];
125 id<CAMetalDrawable> drawable = self.currentDrawable;
126 [commandBuffer presentDrawable:drawable];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400127 [commandBuffer commit];
Kateryna Kostiukdc720842020-09-10 16:44:02 -0400128 [commandBuffer waitUntilScheduled];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400129}
130
131bool frameDisplayed = false;
132
133- (void)renderWithPixelBuffer:(CVPixelBufferRef)buffer
134 size:(CGSize)size
135 rotation: (float)rotation
136 fillFrame: (bool)fill {
137 if(frameDisplayed) {
138 return;
139 }
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400140 if(!self.videoRunning) {
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400141 self.releaseDrawables;
142 return;
143 }
144 if (buffer == nil) return;
145 frameDisplayed = true;
146 CFRetain(buffer);
147 CVPixelBufferLockBaseAddress(buffer, 0);
148 id<MTLTexture> textureY = [self getTexture:buffer pixelFormat:MTLPixelFormatR8Unorm planeIndex:0];
149 id<MTLTexture> textureCbCr = [self getTexture:buffer pixelFormat:MTLPixelFormatRG8Unorm planeIndex:1];
150 CVPixelBufferUnlockBaseAddress(buffer, 0);
151 if(textureY == NULL || textureCbCr == NULL) {
152 frameDisplayed = false;
153 CVPixelBufferRelease(buffer);
154 return;
155 }
156 id<CAMetalDrawable> drawable = self.currentDrawable;
157 if (!drawable.texture) {
158 frameDisplayed = false;
159 CVPixelBufferRelease(buffer);
160 return;
161 }
162 NSSize frameSize = self.frame.size;
163
Kateryna Kostiukb61b0fa2019-09-24 17:56:30 -0400164 float viewRatio = (rotation == 90 || rotation == -90) ?
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400165 frameSize.height/frameSize.width : frameSize.width/frameSize.height;
166 float frameRatio = ((float)size.width)/((float)size.height);
167 simd::float4x4 projectionMatrix;
168 float ratio = viewRatio * (1/frameRatio);
Kateryna Kostiukb9b9e562019-11-09 17:44:48 -0500169 if (ratio < 1.0 && !fill || fill && ratio > 1.0)
170 projectionMatrix = [self getScalingMatrix: ratio axis: 'y'];
171 else
172 projectionMatrix = [self getScalingMatrix: 1/ratio axis: 'x'];
Kateryna Kostiuk1fd950b2019-11-09 18:52:08 -0500173 float radians = (rotation * M_PI) / 180;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400174 simd::float4x4 rotationMatrix = [self getRotationMatrix:radians];
175 Uniforms bytes = Uniforms{projectionMatrix: projectionMatrix, rotationMatrix: rotationMatrix};
176 id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
177 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> cbuffer) {
178 frameDisplayed = false;
179 CVPixelBufferRelease(buffer);
180 }];
181 MTLRenderPassDescriptor *renderPass = self.currentRenderPassDescriptor;
182 renderPass.colorAttachments[0].texture = drawable.texture;
183 id<MTLRenderCommandEncoder> commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPass];
184 [commandEncoder setRenderPipelineState: pipeline];
185 [commandEncoder setDepthStencilState:depthState];
186 [commandEncoder setVertexBytes: &bytes length:sizeof(bytes) atIndex:1];
187 [commandEncoder setVertexBuffer:vertexBuffer offset:0 atIndex:kBufferIndexMeshPositions];
188 [commandEncoder setFragmentTexture:textureY atIndex: 1];
189 [commandEncoder setFragmentTexture:textureCbCr atIndex:2];
190 [commandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
191 [commandEncoder endEncoding];
192 [commandBuffer presentDrawable:drawable];
193 [commandBuffer commit];
Kateryna Kostiukbbac5ca2019-10-02 13:40:49 -0400194 [self draw];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400195}
196
197-(simd::float4x4) getScalingMatrix:(CGFloat) ratio axis:(char) axis {
198 simd::float4x4 N = 0.0;
199 simd::float4 v[4] = {0.0, 0.0, 0.0, 0.0};
200 float xMultyplier = axis == 'x' ? ratio: 1;
201 float yMultyplier = axis == 'y' ? ratio: 1;
202 v[0] = { xMultyplier, 0, 0, 0 };
203 v[1] = { 0, yMultyplier, 0, 0 };
204 v[2] = { 0, 0, 1, 0 };
205 v[3] = { 0, 0, 0, 1 };
206 N = matrix_from_rows(v[0], v[1], v[2], v[3]);
207 return N;
208}
209
210-(simd::float4x4) getRotationMatrix:(float) rotation {
211 simd::float4x4 N = 0.0;
212 simd::float4 v[4] = {0.0, 0.0, 0.0, 0.0};
213 v[0] = { cos(rotation), sin(rotation), 0, 0 };
214 v[1] = { -sin(rotation), cos(rotation), 0, 0 };
215 v[2] = { 0, 0, 1, 0 };
216 v[3] = { 0, 0, 0, 1 };
217 N = matrix_from_rows(v[0], v[1], v[2], v[3]);
218 return N;
219}
220
221- (id<MTLTexture>)getTexture:(CVPixelBufferRef)image pixelFormat:(MTLPixelFormat)pixelFormat planeIndex:(int)planeIndex {
222 id<MTLTexture> texture;
223 size_t width, height;
224 if (planeIndex == -1)
225 {
226 width = CVPixelBufferGetWidth(image);
227 height = CVPixelBufferGetHeight(image);
228 planeIndex = 0;
229 }
230 else
231 {
232 width = CVPixelBufferGetWidthOfPlane(image, planeIndex);
233 height = CVPixelBufferGetHeightOfPlane(image, planeIndex);
234 }
235 auto format = CVPixelBufferGetPixelFormatType(image);
236 CVMetalTextureRef textureRef = NULL;
237 CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, textureCache, image, NULL, pixelFormat, width, height, planeIndex, &textureRef);
238 if(status == kCVReturnSuccess)
239 {
240 texture = CVMetalTextureGetTexture(textureRef);
241 CFRelease(textureRef);
242 }
243 else
244 {
245 NSLog(@"CVMetalTextureCacheCreateTextureFromImage failed with return stats %d", status);
246 return NULL;
247 }
248 return texture;
249}
250
251@end