imgui_impl_metal.mm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. // dear imgui: Renderer Backend for Metal
  2. // This needs to be used along with a Platform Backend (e.g. OSX)
  3. // Implemented features:
  4. // [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID!
  5. // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices.
  6. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
  7. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
  8. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
  9. // Read online: https://github.com/ocornut/imgui/tree/master/docs
  10. // CHANGELOG
  11. // (minor and older changes stripped away, please see git history for details)
  12. // 2021-08-24: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted. (#4464)
  13. // 2021-05-19: Metal: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
  14. // 2021-02-18: Metal: Change blending equation to preserve alpha in output buffer.
  15. // 2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst.
  16. // 2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
  17. // 2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
  18. // 2019-02-11: Metal: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
  19. // 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
  20. // 2018-07-05: Metal: Added new Metal backend implementation.
  21. #include "imgui.h"
  22. #include "imgui_impl_metal.h"
  23. #import <Metal/Metal.h>
  24. // #import <QuartzCore/CAMetalLayer.h> // Not supported in XCode 9.2. Maybe a macro to detect the SDK version can be used (something like #if MACOS_SDK >= 10.13 ...)
  25. #import <simd/simd.h>
  26. #pragma mark - Support classes
  27. // A wrapper around a MTLBuffer object that knows the last time it was reused
  28. @interface MetalBuffer : NSObject
  29. @property (nonatomic, strong) id<MTLBuffer> buffer;
  30. @property (nonatomic, assign) NSTimeInterval lastReuseTime;
  31. - (instancetype)initWithBuffer:(id<MTLBuffer>)buffer;
  32. @end
  33. // An object that encapsulates the data necessary to uniquely identify a
  34. // render pipeline state. These are used as cache keys.
  35. @interface FramebufferDescriptor : NSObject<NSCopying>
  36. @property (nonatomic, assign) unsigned long sampleCount;
  37. @property (nonatomic, assign) MTLPixelFormat colorPixelFormat;
  38. @property (nonatomic, assign) MTLPixelFormat depthPixelFormat;
  39. @property (nonatomic, assign) MTLPixelFormat stencilPixelFormat;
  40. - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor;
  41. @end
  42. // A singleton that stores long-lived objects that are needed by the Metal
  43. // renderer backend. Stores the render pipeline state cache and the default
  44. // font texture, and manages the reusable buffer cache.
  45. @interface MetalContext : NSObject
  46. @property (nonatomic, strong) id<MTLDepthStencilState> depthStencilState;
  47. @property (nonatomic, strong) FramebufferDescriptor *framebufferDescriptor; // framebuffer descriptor for current frame; transient
  48. @property (nonatomic, strong) NSMutableDictionary *renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors
  49. @property (nonatomic, strong, nullable) id<MTLTexture> fontTexture;
  50. @property (nonatomic, strong) NSMutableArray<MetalBuffer *> *bufferCache;
  51. @property (nonatomic, assign) NSTimeInterval lastBufferCachePurge;
  52. - (void)makeDeviceObjectsWithDevice:(id<MTLDevice>)device;
  53. - (void)makeFontTextureWithDevice:(id<MTLDevice>)device;
  54. - (MetalBuffer *)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device;
  55. - (void)enqueueReusableBuffer:(MetalBuffer *)buffer;
  56. - (id<MTLRenderPipelineState>)renderPipelineStateForFrameAndDevice:(id<MTLDevice>)device;
  57. - (void)emptyRenderPipelineStateCache;
  58. - (void)setupRenderState:(ImDrawData *)drawData
  59. commandBuffer:(id<MTLCommandBuffer>)commandBuffer
  60. commandEncoder:(id<MTLRenderCommandEncoder>)commandEncoder
  61. renderPipelineState:(id<MTLRenderPipelineState>)renderPipelineState
  62. vertexBuffer:(MetalBuffer *)vertexBuffer
  63. vertexBufferOffset:(size_t)vertexBufferOffset;
  64. - (void)renderDrawData:(ImDrawData *)drawData
  65. commandBuffer:(id<MTLCommandBuffer>)commandBuffer
  66. commandEncoder:(id<MTLRenderCommandEncoder>)commandEncoder;
  67. @end
  68. static MetalContext *g_sharedMetalContext = nil;
  69. #pragma mark - ImGui API implementation
  70. bool ImGui_ImplMetal_Init(id<MTLDevice> device)
  71. {
  72. ImGuiIO& io = ImGui::GetIO();
  73. io.BackendRendererName = "imgui_impl_metal";
  74. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
  75. static dispatch_once_t onceToken;
  76. dispatch_once(&onceToken, ^{
  77. g_sharedMetalContext = [[MetalContext alloc] init];
  78. });
  79. ImGui_ImplMetal_CreateDeviceObjects(device);
  80. return true;
  81. }
  82. void ImGui_ImplMetal_Shutdown()
  83. {
  84. ImGui_ImplMetal_DestroyDeviceObjects();
  85. }
  86. void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor *renderPassDescriptor)
  87. {
  88. IM_ASSERT(g_sharedMetalContext != nil && "No Metal context. Did you call ImGui_ImplMetal_Init() ?");
  89. g_sharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor];
  90. }
  91. // Metal Render function.
  92. void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer, id<MTLRenderCommandEncoder> commandEncoder)
  93. {
  94. [g_sharedMetalContext renderDrawData:draw_data commandBuffer:commandBuffer commandEncoder:commandEncoder];
  95. }
  96. bool ImGui_ImplMetal_CreateFontsTexture(id<MTLDevice> device)
  97. {
  98. [g_sharedMetalContext makeFontTextureWithDevice:device];
  99. ImGuiIO& io = ImGui::GetIO();
  100. io.Fonts->SetTexID((__bridge void *)g_sharedMetalContext.fontTexture); // ImTextureID == void*
  101. return (g_sharedMetalContext.fontTexture != nil);
  102. }
  103. void ImGui_ImplMetal_DestroyFontsTexture()
  104. {
  105. ImGuiIO& io = ImGui::GetIO();
  106. g_sharedMetalContext.fontTexture = nil;
  107. io.Fonts->SetTexID(nullptr);
  108. }
  109. bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device)
  110. {
  111. [g_sharedMetalContext makeDeviceObjectsWithDevice:device];
  112. ImGui_ImplMetal_CreateFontsTexture(device);
  113. return true;
  114. }
  115. void ImGui_ImplMetal_DestroyDeviceObjects()
  116. {
  117. ImGui_ImplMetal_DestroyFontsTexture();
  118. [g_sharedMetalContext emptyRenderPipelineStateCache];
  119. }
  120. #pragma mark - MetalBuffer implementation
  121. @implementation MetalBuffer
  122. - (instancetype)initWithBuffer:(id<MTLBuffer>)buffer
  123. {
  124. if ((self = [super init]))
  125. {
  126. _buffer = buffer;
  127. _lastReuseTime = [NSDate date].timeIntervalSince1970;
  128. }
  129. return self;
  130. }
  131. @end
  132. #pragma mark - FramebufferDescriptor implementation
  133. @implementation FramebufferDescriptor
  134. - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor
  135. {
  136. if ((self = [super init]))
  137. {
  138. _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount;
  139. _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat;
  140. _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat;
  141. _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat;
  142. }
  143. return self;
  144. }
  145. - (nonnull id)copyWithZone:(nullable NSZone *)zone
  146. {
  147. FramebufferDescriptor *copy = [[FramebufferDescriptor allocWithZone:zone] init];
  148. copy.sampleCount = self.sampleCount;
  149. copy.colorPixelFormat = self.colorPixelFormat;
  150. copy.depthPixelFormat = self.depthPixelFormat;
  151. copy.stencilPixelFormat = self.stencilPixelFormat;
  152. return copy;
  153. }
  154. - (NSUInteger)hash
  155. {
  156. NSUInteger sc = _sampleCount & 0x3;
  157. NSUInteger cf = _colorPixelFormat & 0x3FF;
  158. NSUInteger df = _depthPixelFormat & 0x3FF;
  159. NSUInteger sf = _stencilPixelFormat & 0x3FF;
  160. NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc;
  161. return hash;
  162. }
  163. - (BOOL)isEqual:(id)object
  164. {
  165. FramebufferDescriptor *other = object;
  166. if (![other isKindOfClass:[FramebufferDescriptor class]])
  167. return NO;
  168. return other.sampleCount == self.sampleCount &&
  169. other.colorPixelFormat == self.colorPixelFormat &&
  170. other.depthPixelFormat == self.depthPixelFormat &&
  171. other.stencilPixelFormat == self.stencilPixelFormat;
  172. }
  173. @end
  174. #pragma mark - MetalContext implementation
  175. @implementation MetalContext
  176. - (instancetype)init {
  177. if ((self = [super init]))
  178. {
  179. _renderPipelineStateCache = [NSMutableDictionary dictionary];
  180. _bufferCache = [NSMutableArray array];
  181. _lastBufferCachePurge = [NSDate date].timeIntervalSince1970;
  182. }
  183. return self;
  184. }
  185. - (void)makeDeviceObjectsWithDevice:(id<MTLDevice>)device
  186. {
  187. MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
  188. depthStencilDescriptor.depthWriteEnabled = NO;
  189. depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways;
  190. self.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
  191. }
  192. // We are retrieving and uploading the font atlas as a 4-channels RGBA texture here.
  193. // In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth.
  194. // However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures.
  195. // You can make that change in your implementation.
  196. - (void)makeFontTextureWithDevice:(id<MTLDevice>)device
  197. {
  198. ImGuiIO &io = ImGui::GetIO();
  199. unsigned char* pixels;
  200. int width, height;
  201. io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
  202. MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
  203. width:(NSUInteger)width
  204. height:(NSUInteger)height
  205. mipmapped:NO];
  206. textureDescriptor.usage = MTLTextureUsageShaderRead;
  207. #if TARGET_OS_OSX || TARGET_OS_MACCATALYST
  208. textureDescriptor.storageMode = MTLStorageModeManaged;
  209. #else
  210. textureDescriptor.storageMode = MTLStorageModeShared;
  211. #endif
  212. id <MTLTexture> texture = [device newTextureWithDescriptor:textureDescriptor];
  213. [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)width, (NSUInteger)height) mipmapLevel:0 withBytes:pixels bytesPerRow:(NSUInteger)width * 4];
  214. self.fontTexture = texture;
  215. }
  216. - (MetalBuffer *)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device
  217. {
  218. NSTimeInterval now = [NSDate date].timeIntervalSince1970;
  219. // Purge old buffers that haven't been useful for a while
  220. if (now - self.lastBufferCachePurge > 1.0)
  221. {
  222. NSMutableArray *survivors = [NSMutableArray array];
  223. for (MetalBuffer *candidate in self.bufferCache)
  224. {
  225. if (candidate.lastReuseTime > self.lastBufferCachePurge)
  226. {
  227. [survivors addObject:candidate];
  228. }
  229. }
  230. self.bufferCache = [survivors mutableCopy];
  231. self.lastBufferCachePurge = now;
  232. }
  233. // See if we have a buffer we can reuse
  234. MetalBuffer *bestCandidate = nil;
  235. for (MetalBuffer *candidate in self.bufferCache)
  236. if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime))
  237. bestCandidate = candidate;
  238. if (bestCandidate != nil)
  239. {
  240. [self.bufferCache removeObject:bestCandidate];
  241. bestCandidate.lastReuseTime = now;
  242. return bestCandidate;
  243. }
  244. // No luck; make a new buffer
  245. id<MTLBuffer> backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared];
  246. return [[MetalBuffer alloc] initWithBuffer:backing];
  247. }
  248. - (void)enqueueReusableBuffer:(MetalBuffer *)buffer
  249. {
  250. [self.bufferCache addObject:buffer];
  251. }
  252. - (_Nullable id<MTLRenderPipelineState>)renderPipelineStateForFrameAndDevice:(id<MTLDevice>)device
  253. {
  254. // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame
  255. // The hit rate for this cache should be very near 100%.
  256. id<MTLRenderPipelineState> renderPipelineState = self.renderPipelineStateCache[self.framebufferDescriptor];
  257. if (renderPipelineState == nil)
  258. {
  259. // No luck; make a new render pipeline state
  260. renderPipelineState = [self _renderPipelineStateForFramebufferDescriptor:self.framebufferDescriptor device:device];
  261. // Cache render pipeline state for later reuse
  262. self.renderPipelineStateCache[self.framebufferDescriptor] = renderPipelineState;
  263. }
  264. return renderPipelineState;
  265. }
  266. - (id<MTLRenderPipelineState>)_renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor *)descriptor device:(id<MTLDevice>)device
  267. {
  268. NSError *error = nil;
  269. NSString *shaderSource = @""
  270. "#include <metal_stdlib>\n"
  271. "using namespace metal;\n"
  272. "\n"
  273. "struct Uniforms {\n"
  274. " float4x4 projectionMatrix;\n"
  275. "};\n"
  276. "\n"
  277. "struct VertexIn {\n"
  278. " float2 position [[attribute(0)]];\n"
  279. " float2 texCoords [[attribute(1)]];\n"
  280. " uchar4 color [[attribute(2)]];\n"
  281. "};\n"
  282. "\n"
  283. "struct VertexOut {\n"
  284. " float4 position [[position]];\n"
  285. " float2 texCoords;\n"
  286. " float4 color;\n"
  287. "};\n"
  288. "\n"
  289. "vertex VertexOut vertex_main(VertexIn in [[stage_in]],\n"
  290. " constant Uniforms &uniforms [[buffer(1)]]) {\n"
  291. " VertexOut out;\n"
  292. " out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n"
  293. " out.texCoords = in.texCoords;\n"
  294. " out.color = float4(in.color) / float4(255.0);\n"
  295. " return out;\n"
  296. "}\n"
  297. "\n"
  298. "fragment half4 fragment_main(VertexOut in [[stage_in]],\n"
  299. " texture2d<half, access::sample> texture [[texture(0)]]) {\n"
  300. " constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear);\n"
  301. " half4 texColor = texture.sample(linearSampler, in.texCoords);\n"
  302. " return half4(in.color) * texColor;\n"
  303. "}\n";
  304. id<MTLLibrary> library = [device newLibraryWithSource:shaderSource options:nil error:&error];
  305. if (library == nil)
  306. {
  307. NSLog(@"Error: failed to create Metal library: %@", error);
  308. return nil;
  309. }
  310. id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertex_main"];
  311. id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragment_main"];
  312. if (vertexFunction == nil || fragmentFunction == nil)
  313. {
  314. NSLog(@"Error: failed to find Metal shader functions in library: %@", error);
  315. return nil;
  316. }
  317. MTLVertexDescriptor *vertexDescriptor = [MTLVertexDescriptor vertexDescriptor];
  318. vertexDescriptor.attributes[0].offset = IM_OFFSETOF(ImDrawVert, pos);
  319. vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position
  320. vertexDescriptor.attributes[0].bufferIndex = 0;
  321. vertexDescriptor.attributes[1].offset = IM_OFFSETOF(ImDrawVert, uv);
  322. vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoords
  323. vertexDescriptor.attributes[1].bufferIndex = 0;
  324. vertexDescriptor.attributes[2].offset = IM_OFFSETOF(ImDrawVert, col);
  325. vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; // color
  326. vertexDescriptor.attributes[2].bufferIndex = 0;
  327. vertexDescriptor.layouts[0].stepRate = 1;
  328. vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
  329. vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert);
  330. MTLRenderPipelineDescriptor *pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
  331. pipelineDescriptor.vertexFunction = vertexFunction;
  332. pipelineDescriptor.fragmentFunction = fragmentFunction;
  333. pipelineDescriptor.vertexDescriptor = vertexDescriptor;
  334. pipelineDescriptor.sampleCount = self.framebufferDescriptor.sampleCount;
  335. pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat;
  336. pipelineDescriptor.colorAttachments[0].blendingEnabled = YES;
  337. pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
  338. pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
  339. pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
  340. pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
  341. pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
  342. pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
  343. pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat;
  344. pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat;
  345. id<MTLRenderPipelineState> renderPipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
  346. if (error != nil)
  347. {
  348. NSLog(@"Error: failed to create Metal pipeline state: %@", error);
  349. }
  350. return renderPipelineState;
  351. }
  352. - (void)emptyRenderPipelineStateCache
  353. {
  354. [self.renderPipelineStateCache removeAllObjects];
  355. }
  356. - (void)setupRenderState:(ImDrawData *)drawData
  357. commandBuffer:(id<MTLCommandBuffer>)commandBuffer
  358. commandEncoder:(id<MTLRenderCommandEncoder>)commandEncoder
  359. renderPipelineState:(id<MTLRenderPipelineState>)renderPipelineState
  360. vertexBuffer:(MetalBuffer *)vertexBuffer
  361. vertexBufferOffset:(size_t)vertexBufferOffset
  362. {
  363. [commandEncoder setCullMode:MTLCullModeNone];
  364. [commandEncoder setDepthStencilState:g_sharedMetalContext.depthStencilState];
  365. // Setup viewport, orthographic projection matrix
  366. // Our visible imgui space lies from draw_data->DisplayPos (top left) to
  367. // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps.
  368. MTLViewport viewport =
  369. {
  370. .originX = 0.0,
  371. .originY = 0.0,
  372. .width = (double)(drawData->DisplaySize.x * drawData->FramebufferScale.x),
  373. .height = (double)(drawData->DisplaySize.y * drawData->FramebufferScale.y),
  374. .znear = 0.0,
  375. .zfar = 1.0
  376. };
  377. [commandEncoder setViewport:viewport];
  378. float L = drawData->DisplayPos.x;
  379. float R = drawData->DisplayPos.x + drawData->DisplaySize.x;
  380. float T = drawData->DisplayPos.y;
  381. float B = drawData->DisplayPos.y + drawData->DisplaySize.y;
  382. float N = (float)viewport.znear;
  383. float F = (float)viewport.zfar;
  384. const float ortho_projection[4][4] =
  385. {
  386. { 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
  387. { 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
  388. { 0.0f, 0.0f, 1/(F-N), 0.0f },
  389. { (R+L)/(L-R), (T+B)/(B-T), N/(F-N), 1.0f },
  390. };
  391. [commandEncoder setVertexBytes:&ortho_projection length:sizeof(ortho_projection) atIndex:1];
  392. [commandEncoder setRenderPipelineState:renderPipelineState];
  393. [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:0 atIndex:0];
  394. [commandEncoder setVertexBufferOffset:vertexBufferOffset atIndex:0];
  395. }
  396. - (void)renderDrawData:(ImDrawData *)drawData
  397. commandBuffer:(id<MTLCommandBuffer>)commandBuffer
  398. commandEncoder:(id<MTLRenderCommandEncoder>)commandEncoder
  399. {
  400. // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
  401. int fb_width = (int)(drawData->DisplaySize.x * drawData->FramebufferScale.x);
  402. int fb_height = (int)(drawData->DisplaySize.y * drawData->FramebufferScale.y);
  403. if (fb_width <= 0 || fb_height <= 0 || drawData->CmdListsCount == 0)
  404. return;
  405. id<MTLRenderPipelineState> renderPipelineState = [self renderPipelineStateForFrameAndDevice:commandBuffer.device];
  406. size_t vertexBufferLength = (size_t)drawData->TotalVtxCount * sizeof(ImDrawVert);
  407. size_t indexBufferLength = (size_t)drawData->TotalIdxCount * sizeof(ImDrawIdx);
  408. MetalBuffer* vertexBuffer = [self dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device];
  409. MetalBuffer* indexBuffer = [self dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device];
  410. [self setupRenderState:drawData commandBuffer:commandBuffer commandEncoder:commandEncoder renderPipelineState:renderPipelineState vertexBuffer:vertexBuffer vertexBufferOffset:0];
  411. // Will project scissor/clipping rectangles into framebuffer space
  412. ImVec2 clip_off = drawData->DisplayPos; // (0,0) unless using multi-viewports
  413. ImVec2 clip_scale = drawData->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
  414. // Render command lists
  415. size_t vertexBufferOffset = 0;
  416. size_t indexBufferOffset = 0;
  417. for (int n = 0; n < drawData->CmdListsCount; n++)
  418. {
  419. const ImDrawList* cmd_list = drawData->CmdLists[n];
  420. memcpy((char *)vertexBuffer.buffer.contents + vertexBufferOffset, cmd_list->VtxBuffer.Data, (size_t)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
  421. memcpy((char *)indexBuffer.buffer.contents + indexBufferOffset, cmd_list->IdxBuffer.Data, (size_t)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
  422. for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
  423. {
  424. const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
  425. if (pcmd->UserCallback)
  426. {
  427. // User callback, registered via ImDrawList::AddCallback()
  428. // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
  429. if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
  430. [self setupRenderState:drawData commandBuffer:commandBuffer commandEncoder:commandEncoder renderPipelineState:renderPipelineState vertexBuffer:vertexBuffer vertexBufferOffset:vertexBufferOffset];
  431. else
  432. pcmd->UserCallback(cmd_list, pcmd);
  433. }
  434. else
  435. {
  436. // Project scissor/clipping rectangles into framebuffer space
  437. ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
  438. ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
  439. // Clamp to viewport as setScissorRect() won't accept values that are off bounds
  440. if (clip_min.x < 0.0f) { clip_min.x = 0.0f; }
  441. if (clip_min.y < 0.0f) { clip_min.y = 0.0f; }
  442. if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; }
  443. if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; }
  444. if (clip_max.x < clip_min.x || clip_max.y < clip_min.y)
  445. continue;
  446. // Apply scissor/clipping rectangle
  447. MTLScissorRect scissorRect =
  448. {
  449. .x = NSUInteger(clip_min.x),
  450. .y = NSUInteger(clip_min.y),
  451. .width = NSUInteger(clip_max.x - clip_min.x),
  452. .height = NSUInteger(clip_max.y - clip_min.y)
  453. };
  454. [commandEncoder setScissorRect:scissorRect];
  455. // Bind texture, Draw
  456. if (ImTextureID tex_id = pcmd->GetTexID())
  457. [commandEncoder setFragmentTexture:(__bridge id<MTLTexture>)(tex_id) atIndex:0];
  458. [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0];
  459. [commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
  460. indexCount:pcmd->ElemCount
  461. indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
  462. indexBuffer:indexBuffer.buffer
  463. indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)];
  464. }
  465. }
  466. vertexBufferOffset += (size_t)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert);
  467. indexBufferOffset += (size_t)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx);
  468. }
  469. __weak id weakSelf = self;
  470. [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>)
  471. {
  472. dispatch_async(dispatch_get_main_queue(), ^{
  473. [weakSelf enqueueReusableBuffer:vertexBuffer];
  474. [weakSelf enqueueReusableBuffer:indexBuffer];
  475. });
  476. }];
  477. }
  478. @end