imgui_impl_metal.mm 21 KB

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