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