blob: 002ec8024b837017a7b6b845f7510ed263a2fa97 [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};
51
52- (instancetype)initWithFrame:(NSRect)frame
53{
54 self = [super initWithFrame:frame];
55 if (self) {
56 id<MTLDevice> device = MTLCreateSystemDefaultDevice();
57 self.device = device;
58 commandQueue = [device newCommandQueue];
59 self.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
60 commandQueue = [device newCommandQueue];
61
62 CVReturn err = CVMetalTextureCacheCreate(kCFAllocatorDefault,
63 NULL,
64 self.device,
65 NULL,
66 &textureCache);
67
68 vertexBuffer = [device newBufferWithBytes:&kImagePlaneVertexData
69 length:sizeof(kImagePlaneVertexData)
70 options:MTLResourceCPUCacheModeDefaultCache];
71
72 NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
73 NSString *libraryPath = [resourcePath stringByAppendingPathComponent:@"Shader.metallib"];
74 id <MTLLibrary> library = [device newLibraryWithFile:libraryPath error:nil];
75 id<MTLFunction> vertexFunc = [library newFunctionWithName:@"imageVertex"];
76 id<MTLFunction> fragmentFunc = [library newFunctionWithName:@"imageFragment"];
77
78 // Create a vertex descriptor for our image plane vertex buffer
79 MTLVertexDescriptor *imagePlaneVertexDescriptor = [[MTLVertexDescriptor alloc] init];
80
81 // Positions.
82 imagePlaneVertexDescriptor.attributes[kVertexAttributePosition].format = MTLVertexFormatFloat2;
83 imagePlaneVertexDescriptor.attributes[kVertexAttributePosition].offset = 0;
84 imagePlaneVertexDescriptor.attributes[kVertexAttributePosition].bufferIndex = kBufferIndexMeshPositions;
85
86 // Texture coordinates.
87 imagePlaneVertexDescriptor.attributes[kVertexAttributeTexcoord].format = MTLVertexFormatFloat2;
88 imagePlaneVertexDescriptor.attributes[kVertexAttributeTexcoord].offset = 8;
89 imagePlaneVertexDescriptor.attributes[kVertexAttributeTexcoord].bufferIndex = kBufferIndexMeshPositions;
90
91 // Position Buffer Layout
92 imagePlaneVertexDescriptor.layouts[kBufferIndexMeshPositions].stride = 16;
93 imagePlaneVertexDescriptor.layouts[kBufferIndexMeshPositions].stepRate = 1;
94 imagePlaneVertexDescriptor.layouts[kBufferIndexMeshPositions].stepFunction = MTLVertexStepFunctionPerVertex;
95
96 MTLRenderPipelineDescriptor *pipelineDescriptor = [MTLRenderPipelineDescriptor new];
97 pipelineDescriptor.vertexFunction = vertexFunc;
98 pipelineDescriptor.fragmentFunction = fragmentFunc;
99 pipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
100 pipelineDescriptor.vertexDescriptor = imagePlaneVertexDescriptor;
101
102 pipeline = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:NULL];
103 MTLDepthStencilDescriptor *depthStateDescriptor = [[MTLDepthStencilDescriptor alloc] init];
104 depthStateDescriptor.depthCompareFunction = MTLCompareFunctionAlways;
105 depthStateDescriptor.depthWriteEnabled = NO;
106 depthState = [device newDepthStencilStateWithDescriptor:depthStateDescriptor];
107 self.preferredFramesPerSecond = 30;
108 }
109 return self;
110}
111
112- (void)fillWithBlack {
113 NSUInteger width = self.frame.size.width;
114 NSUInteger height = self.frame.size.height;
115 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
116 uint8_t *rawData = (uint8_t *)calloc(height * width * 4, sizeof(uint8_t));
117 NSUInteger bytesPerPixel = 4;
118 NSUInteger bytesPerRow = bytesPerPixel * width;
119 NSUInteger bitsPerComponent = 8;
120 MTLTextureDescriptor *textureDescriptor =
121 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
122 width:width
123 height:height
124 mipmapped:YES];
125 textureDescriptor.usage = MTLTextureUsageRenderTarget;
126 id<MTLTexture> texture = [self.device newTextureWithDescriptor:textureDescriptor];
127 MTLRegion region = MTLRegionMake2D(0, 0, width, height);
128 [texture replaceRegion:region mipmapLevel:0 withBytes:rawData bytesPerRow:bytesPerRow];
129 id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
130 MTLRenderPassDescriptor *renderPass = self.currentRenderPassDescriptor;
131 id<MTLRenderCommandEncoder> commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPass];
132 [commandEncoder setFragmentTexture:texture atIndex:0];
133 [commandEncoder endEncoding];
134 [commandBuffer presentDrawable:self.currentDrawable];
135 [commandBuffer commit];
136}
137
138bool frameDisplayed = false;
139
140- (void)renderWithPixelBuffer:(CVPixelBufferRef)buffer
141 size:(CGSize)size
142 rotation: (float)rotation
143 fillFrame: (bool)fill {
144 if(frameDisplayed) {
145 return;
146 }
147 if(_stopRendering) {
148 self.releaseDrawables;
149 return;
150 }
151 if (buffer == nil) return;
152 frameDisplayed = true;
153 CFRetain(buffer);
154 CVPixelBufferLockBaseAddress(buffer, 0);
155 id<MTLTexture> textureY = [self getTexture:buffer pixelFormat:MTLPixelFormatR8Unorm planeIndex:0];
156 id<MTLTexture> textureCbCr = [self getTexture:buffer pixelFormat:MTLPixelFormatRG8Unorm planeIndex:1];
157 CVPixelBufferUnlockBaseAddress(buffer, 0);
158 if(textureY == NULL || textureCbCr == NULL) {
159 frameDisplayed = false;
160 CVPixelBufferRelease(buffer);
161 return;
162 }
163 id<CAMetalDrawable> drawable = self.currentDrawable;
164 if (!drawable.texture) {
165 frameDisplayed = false;
166 CVPixelBufferRelease(buffer);
167 return;
168 }
169 NSSize frameSize = self.frame.size;
170
171 float viewRatio = (rotation == 90 || rotation == -90 || rotation == 180 || rotation == -180) ?
172 frameSize.height/frameSize.width : frameSize.width/frameSize.height;
173 float frameRatio = ((float)size.width)/((float)size.height);
174 simd::float4x4 projectionMatrix;
175 float ratio = viewRatio * (1/frameRatio);
176 if((viewRatio >= 1 && frameRatio >= 1) ||
177 (viewRatio < 1 && frameRatio < 1) ||
178 (ratio > 0.5 && ratio < 1.5) ) {
179 if (ratio <= 1.0 && ratio >= 0.5)
180 projectionMatrix = [self getScalingMatrix: 1/ratio axis: 'x'];
181 else if (ratio < 0.5)
182 projectionMatrix = [self getScalingMatrix: ratio axis: 'y'];
183 else if (ratio > 1 && ratio < 2)
184 projectionMatrix = [self getScalingMatrix: ratio axis: 'y'];
185 else
186 projectionMatrix = [self getScalingMatrix: 1/ratio axis: 'x'];
187 } else {
188 if (ratio < 1.0 && !fill || fill && ratio > 1.0)
189 projectionMatrix = [self getScalingMatrix: ratio axis: 'y'];
190 else
191 projectionMatrix = [self getScalingMatrix: 1/ratio axis: 'x'];
192 }
193 float radians = (-rotation * M_PI) / 180;
194 simd::float4x4 rotationMatrix = [self getRotationMatrix:radians];
195 Uniforms bytes = Uniforms{projectionMatrix: projectionMatrix, rotationMatrix: rotationMatrix};
196 id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
197 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> cbuffer) {
198 frameDisplayed = false;
199 CVPixelBufferRelease(buffer);
200 }];
201 MTLRenderPassDescriptor *renderPass = self.currentRenderPassDescriptor;
202 renderPass.colorAttachments[0].texture = drawable.texture;
203 id<MTLRenderCommandEncoder> commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPass];
204 [commandEncoder setRenderPipelineState: pipeline];
205 [commandEncoder setDepthStencilState:depthState];
206 [commandEncoder setVertexBytes: &bytes length:sizeof(bytes) atIndex:1];
207 [commandEncoder setVertexBuffer:vertexBuffer offset:0 atIndex:kBufferIndexMeshPositions];
208 [commandEncoder setFragmentTexture:textureY atIndex: 1];
209 [commandEncoder setFragmentTexture:textureCbCr atIndex:2];
210 [commandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
211 [commandEncoder endEncoding];
212 [commandBuffer presentDrawable:drawable];
213 [commandBuffer commit];
214}
215
216-(simd::float4x4) getScalingMatrix:(CGFloat) ratio axis:(char) axis {
217 simd::float4x4 N = 0.0;
218 simd::float4 v[4] = {0.0, 0.0, 0.0, 0.0};
219 float xMultyplier = axis == 'x' ? ratio: 1;
220 float yMultyplier = axis == 'y' ? ratio: 1;
221 v[0] = { xMultyplier, 0, 0, 0 };
222 v[1] = { 0, yMultyplier, 0, 0 };
223 v[2] = { 0, 0, 1, 0 };
224 v[3] = { 0, 0, 0, 1 };
225 N = matrix_from_rows(v[0], v[1], v[2], v[3]);
226 return N;
227}
228
229-(simd::float4x4) getRotationMatrix:(float) rotation {
230 simd::float4x4 N = 0.0;
231 simd::float4 v[4] = {0.0, 0.0, 0.0, 0.0};
232 v[0] = { cos(rotation), sin(rotation), 0, 0 };
233 v[1] = { -sin(rotation), cos(rotation), 0, 0 };
234 v[2] = { 0, 0, 1, 0 };
235 v[3] = { 0, 0, 0, 1 };
236 N = matrix_from_rows(v[0], v[1], v[2], v[3]);
237 return N;
238}
239
240- (id<MTLTexture>)getTexture:(CVPixelBufferRef)image pixelFormat:(MTLPixelFormat)pixelFormat planeIndex:(int)planeIndex {
241 id<MTLTexture> texture;
242 size_t width, height;
243 if (planeIndex == -1)
244 {
245 width = CVPixelBufferGetWidth(image);
246 height = CVPixelBufferGetHeight(image);
247 planeIndex = 0;
248 }
249 else
250 {
251 width = CVPixelBufferGetWidthOfPlane(image, planeIndex);
252 height = CVPixelBufferGetHeightOfPlane(image, planeIndex);
253 }
254 auto format = CVPixelBufferGetPixelFormatType(image);
255 CVMetalTextureRef textureRef = NULL;
256 CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, textureCache, image, NULL, pixelFormat, width, height, planeIndex, &textureRef);
257 if(status == kCVReturnSuccess)
258 {
259 texture = CVMetalTextureGetTexture(textureRef);
260 CFRelease(textureRef);
261 }
262 else
263 {
264 NSLog(@"CVMetalTextureCacheCreateTextureFromImage failed with return stats %d", status);
265 return NULL;
266 }
267 return texture;
268}
269
270@end