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