forked from utmapp/UTM
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathUTMRenderer.m
285 lines (234 loc) · 11.4 KB
/
UTMRenderer.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/*
See LICENSE folder for this sample’s licensing information.
Abstract:
Implementation of renderer class which performs Metal setup and per frame rendering
*/
@import simd;
@import MetalKit;
#import "UTMRenderer.h"
#import "UTMLogging.h"
// Header shared between C code here, which executes Metal API commands, and .metal files, which
// uses these types as inputs to the shaders
#import "UTMShaderTypes.h"
// Main class performing the rendering
@implementation UTMRenderer
{
// The device (aka GPU) we're using to render
id<MTLDevice> _device;
// Our render pipeline composed of our vertex and fragment shaders in the .metal shader file
id<MTLRenderPipelineState> _pipelineState;
// The command Queue from which we'll obtain command buffers
id<MTLCommandQueue> _commandQueue;
// The current size of our view so we can use this in our render pipeline
vector_uint2 _viewportSize;
// Sampler object
id<MTLSamplerState> _sampler;
}
- (void)setSourceScreen:(id<UTMRenderSource>)source {
source.device = _device;
_sourceScreen = source;
}
- (void)setSourceCursor:(id<UTMRenderSource>)source {
source.device = _device;
_sourceCursor = source;
}
/// Initialize with the MetalKit view from which we'll obtain our Metal device
- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView
{
self = [super init];
if(self)
{
_device = mtkView.device;
/// Create our render pipeline
// Load all the shader files with a .metal file extension in the project
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
// Load the vertex function from the library
id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
// Load the fragment function from the library
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"samplingShader"];
// Set up a descriptor for creating a pipeline state object
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"Texturing Pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
pipelineStateDescriptor.colorAttachments[0].blendingEnabled = YES;
pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOne;
pipelineStateDescriptor.depthAttachmentPixelFormat = mtkView.depthStencilPixelFormat;
NSError *error = NULL;
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
error:&error];
if (!_pipelineState)
{
// Pipeline State creation could fail if we haven't properly set up our pipeline descriptor.
// If the Metal API validation is enabled, we can find out more information about what
// went wrong. (Metal API validation is enabled by default when a debug build is run
// from Xcode)
UTMLog(@"Failed to created pipeline state, error %@", error);
}
// Create the command queue
_commandQueue = [_device newCommandQueue];
// Sampler
[self changeUpscaler:MTLSamplerMinMagFilterLinear downscaler:MTLSamplerMinMagFilterLinear];
}
return self;
}
/// Scalers from VM settings
- (void)changeUpscaler:(MTLSamplerMinMagFilter)upscaler downscaler:(MTLSamplerMinMagFilter)downscaler {
MTLSamplerDescriptor *samplerDescriptor = [MTLSamplerDescriptor new];
samplerDescriptor.minFilter = downscaler;
samplerDescriptor.magFilter = upscaler;
_sampler = [_device newSamplerStateWithDescriptor:samplerDescriptor];
}
/// Called whenever view changes orientation or is resized
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
{
// Save the size of the drawable as we'll pass these
// values to our vertex shader when we draw
_viewportSize.x = size.width;
_viewportSize.y = size.height;
}
/// Create a translation+scale matrix
static matrix_float4x4 matrix_scale_translate(CGFloat scale, CGPoint translate)
{
matrix_float4x4 m = {
.columns[0] = {
scale,
0,
0,
0
},
.columns[1] = {
0,
scale,
0,
0
},
.columns[2] = {
0,
0,
1,
0
},
.columns[3] = {
translate.x,
-translate.y, // y flipped
0,
1
}
};
return m;
}
/// Called whenever the view needs to render a frame
- (void)drawInMTKView:(nonnull MTKView *)view
{
if (view.hidden) {
return;
}
// Create a new command buffer for each render pass to the current drawable
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
commandBuffer.label = @"MyCommand";
// Obtain a renderPassDescriptor generated from the view's drawable textures
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
if(renderPassDescriptor != nil)
{
BOOL screenDrawn = NO;
__weak dispatch_semaphore_t screenLock = nil;
__weak dispatch_semaphore_t cursorLock = nil;
// Create a render command encoder so we can render into something
id<MTLRenderCommandEncoder> renderEncoder =
[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
renderEncoder.label = @"MyRenderEncoder";
if (self.sourceScreen) {
// Lock screen updates
bool hasAlpha = NO;
screenLock = self.sourceScreen.drawLock;
dispatch_semaphore_wait(screenLock, DISPATCH_TIME_FOREVER);
if (self.sourceScreen.visible) {
// Render the screen first
matrix_float4x4 transform = matrix_scale_translate(self.sourceScreen.viewportScale,
self.sourceScreen.viewportOrigin);
[renderEncoder setRenderPipelineState:_pipelineState];
[renderEncoder setVertexBuffer:self.sourceScreen.vertices
offset:0
atIndex:UTMVertexInputIndexVertices];
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:UTMVertexInputIndexViewportSize];
[renderEncoder setVertexBytes:&transform
length:sizeof(transform)
atIndex:UTMVertexInputIndexTransform];
[renderEncoder setVertexBytes:&hasAlpha
length:sizeof(hasAlpha)
atIndex:UTMVertexInputIndexHasAlpha];
// Set the texture object. The UTMTextureIndexBaseColor enum value corresponds
/// to the 'colorMap' argument in our 'samplingShader' function because its
// texture attribute qualifier also uses UTMTextureIndexBaseColor for its index
[renderEncoder setFragmentTexture:self.sourceScreen.texture
atIndex:UTMTextureIndexBaseColor];
[renderEncoder setFragmentSamplerState:_sampler
atIndex:UTMSamplerIndexTexture];
// Draw the vertices of our triangles
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:self.sourceScreen.numVertices];
screenDrawn = YES;
}
}
if (screenDrawn && self.sourceCursor) {
// Lock cursor updates
bool hasAlpha = YES;
cursorLock = self.sourceCursor.drawLock;
dispatch_semaphore_wait(cursorLock, DISPATCH_TIME_FOREVER);
if (self.sourceCursor.visible) {
// Next render the cursor
matrix_float4x4 transform = matrix_scale_translate(self.sourceScreen.viewportScale,
CGPointMake(self.sourceScreen.viewportOrigin.x +
self.sourceCursor.viewportOrigin.x,
self.sourceScreen.viewportOrigin.y +
self.sourceCursor.viewportOrigin.y));
[renderEncoder setVertexBuffer:self.sourceCursor.vertices
offset:0
atIndex:UTMVertexInputIndexVertices];
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:UTMVertexInputIndexViewportSize];
[renderEncoder setVertexBytes:&transform
length:sizeof(transform)
atIndex:UTMVertexInputIndexTransform];
[renderEncoder setVertexBytes:&hasAlpha
length:sizeof(hasAlpha)
atIndex:UTMVertexInputIndexHasAlpha];
[renderEncoder setFragmentTexture:self.sourceCursor.texture
atIndex:UTMTextureIndexBaseColor];
[renderEncoder setFragmentSamplerState:_sampler
atIndex:UTMSamplerIndexTexture];
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:self.sourceCursor.numVertices];
}
}
[renderEncoder endEncoding];
// Schedule a present once the framebuffer is complete using the current drawable
[commandBuffer presentDrawable:view.currentDrawable];
// Release lock after GPU is done
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> commandBuffer) {
// GPU work is complete
// Signal the semaphore to start the CPU work
if (screenLock) {
dispatch_semaphore_signal(screenLock);
}
if (cursorLock) {
dispatch_semaphore_signal(cursorLock);
}
}];
}
// Finalize rendering here & push the command buffer to the GPU
[commandBuffer commit];
}
@end