CSRenderer.m 11 KB

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