UTMRenderer.m 10 KB

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