| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | |
| 12 | |
| 13 | |
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | |
| 25 | |
| 26 | |
| 27 | |
| 28 | |
| 29 | |
| 30 | |
| 31 | |
| 32 | |
| 33 | |
| 34 | |
| 35 | |
| 36 | |
| 37 | |
| 38 | |
| 39 | |
| 40 | |
| 41 | |
| 42 | |
| 43 | |
| 44 | |
| 45 | |
| 46 | |
| 47 | #include "imgui.h" |
| 48 | #ifndef IMGUI_DISABLE |
| 49 | #include "imgui_impl_metal.h" |
| 50 | #import <time.h> |
| 51 | #import <Metal/Metal.h> |
| 52 | |
| 53 | |
| 54 | static void ImGui_ImplMetal_InitMultiViewportSupport(); |
| 55 | static void ImGui_ImplMetal_ShutdownMultiViewportSupport(); |
| 56 | static void ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows(); |
| 57 | static void ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows(); |
| 58 | |
| 59 | #pragma mark - Support classes |
| 60 | |
| 61 | |
| 62 | @interface MetalBuffer : NSObject |
| 63 | @property (nonatomic, strong) id<MTLBuffer> buffer; |
| 64 | @property (nonatomic, assign) double lastReuseTime; |
| 65 | - (instancetype)initWithBuffer:(id<MTLBuffer>)buffer; |
| 66 | @end |
| 67 | |
| 68 | |
| 69 | |
| 70 | @interface FramebufferDescriptor : NSObject<NSCopying> |
| 71 | @property (nonatomic, assign) unsigned long sampleCount; |
| 72 | @property (nonatomic, assign) MTLPixelFormat colorPixelFormat; |
| 73 | @property (nonatomic, assign) MTLPixelFormat depthPixelFormat; |
| 74 | @property (nonatomic, assign) MTLPixelFormat stencilPixelFormat; |
| 75 | - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor; |
| 76 | @end |
| 77 | |
| 78 | @interface MetalTexture : NSObject |
| 79 | @property (nonatomic, strong) id<MTLTexture> metalTexture; |
| 80 | - (instancetype)initWithTexture:(id<MTLTexture>)metalTexture; |
| 81 | @end |
| 82 | |
| 83 | |
| 84 | |
| 85 | |
| 86 | @interface MetalContext : NSObject |
| 87 | @property (nonatomic, strong) id<MTLDevice> device; |
| 88 | @property (nonatomic, strong) id<MTLDepthStencilState> depthStencilState; |
| 89 | @property (nonatomic, strong) id<MTLSamplerState> samplerStateLinear; |
| 90 | @property (nonatomic, strong) id<MTLSamplerState> samplerStateNearest; |
| 91 | @property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; |
| 92 | @property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; |
| 93 | @property (nonatomic, strong) NSMutableArray<MetalBuffer*>* bufferCache; |
| 94 | @property (nonatomic, strong) NSObject* bufferCacheLock; |
| 95 | @property (nonatomic, assign) double lastBufferCachePurge; |
| 96 | - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device; |
| 97 | - (id<MTLRenderPipelineState>)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id<MTLDevice>)device; |
| 98 | @end |
| 99 | |
| 100 | struct ImGui_ImplMetal_Data |
| 101 | { |
| 102 | MetalContext* SharedMetalContext; |
| 103 | id<MTLRenderCommandEncoder> RenderCommandEncoder; |
| 104 | |
| 105 | ImGui_ImplMetal_Data() { memset((void*)this, 0, sizeof(*this)); } |
| 106 | }; |
| 107 | |
| 108 | static ImGui_ImplMetal_Data* ImGui_ImplMetal_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } |
| 109 | static void ImGui_ImplMetal_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal_GetBackendData()); } |
| 110 | |
| 111 | static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); } |
| 112 | |
| 113 | #ifdef IMGUI_IMPL_METAL_CPP |
| 114 | |
| 115 | #pragma mark - Dear ImGui Metal C++ Backend API |
| 116 | |
| 117 | bool ImGui_ImplMetal_Init(MTL::Device* device) |
| 118 | { |
| 119 | return ImGui_ImplMetal_Init((__bridge id<MTLDevice>)(device)); |
| 120 | } |
| 121 | |
| 122 | void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor) |
| 123 | { |
| 124 | ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor)); |
| 125 | } |
| 126 | |
| 127 | void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, |
| 128 | MTL::CommandBuffer* commandBuffer, |
| 129 | MTL::RenderCommandEncoder* commandEncoder) |
| 130 | { |
| 131 | ImGui_ImplMetal_RenderDrawData(draw_data, |
| 132 | (__bridge id<MTLCommandBuffer>)(commandBuffer), |
| 133 | (__bridge id<MTLRenderCommandEncoder>)(commandEncoder)); |
| 134 | |
| 135 | } |
| 136 | |
| 137 | bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) |
| 138 | { |
| 139 | return ImGui_ImplMetal_CreateDeviceObjects((__bridge id<MTLDevice>)(device)); |
| 140 | } |
| 141 | |
| 142 | #endif |
| 143 | |
| 144 | #pragma mark - Dear ImGui Metal Backend API |
| 145 | |
| 146 | void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) |
| 147 | { |
| 148 | ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); |
| 149 | IM_ASSERT(bd != nil && "Context or backend not initialized! Did you call ImGui_ImplMetal_Init()?"); |
| 150 | #ifdef IMGUI_IMPL_METAL_CPP |
| 151 | bd->SharedMetalContext.framebufferDescriptor = [[[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]autorelease]; |
| 152 | #else |
| 153 | bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]; |
| 154 | #endif |
| 155 | if (bd->SharedMetalContext.depthStencilState == nil) |
| 156 | ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device); |
| 157 | } |
| 158 | |
| 159 | static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer, |
| 160 | id<MTLRenderCommandEncoder> commandEncoder, id<MTLRenderPipelineState> renderPipelineState, |
| 161 | MetalBuffer* vertexBuffer, size_t vertexBufferOffset) |
| 162 | { |
| 163 | IM_UNUSED(commandBuffer); |
| 164 | ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); |
| 165 | [commandEncoder setCullMode:MTLCullModeNone]; |
| 166 | [commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState]; |
| 167 | |
| 168 | |
| 169 | |
| 170 | |
| 171 | MTLViewport viewport = |
| 172 | { |
| 173 | .originX = 0.0, |
| 174 | .originY = 0.0, |
| 175 | .width = (double)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x), |
| 176 | .height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y), |
| 177 | .znear = 0.0, |
| 178 | .zfar = 1.0 |
| 179 | }; |
| 180 | [commandEncoder setViewport:viewport]; |
| 181 | |
| 182 | float L = draw_data->DisplayPos.x; |
| 183 | float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; |
| 184 | float T = draw_data->DisplayPos.y; |
| 185 | float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; |
| 186 | float N = (float)viewport.znear; |
| 187 | float F = (float)viewport.zfar; |
| 188 | const float ortho_projection[4][4] = |
| 189 | { |
| 190 | { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, |
| 191 | { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, |
| 192 | { 0.0f, 0.0f, 1/(F-N), 0.0f }, |
| 193 | { (R+L)/(L-R), (T+B)/(B-T), N/(F-N), 1.0f }, |
| 194 | }; |
| 195 | [commandEncoder setVertexBytes:&ortho_projection length:sizeof(ortho_projection) atIndex:1]; |
| 196 | |
| 197 | [commandEncoder setRenderPipelineState:renderPipelineState]; |
| 198 | [commandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateLinear atIndex:0]; |
| 199 | |
| 200 | [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:vertexBufferOffset atIndex:0]; |
| 201 | } |
| 202 | |
| 203 | |
| 204 | static void ImGui_ImplMetal_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} |
| 205 | static void ImGui_ImplMetal_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); [bd->RenderCommandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateLinear atIndex:0]; } |
| 206 | static void ImGui_ImplMetal_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); [bd->RenderCommandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateNearest atIndex:0]; } |
| 207 | |
| 208 | |
| 209 | void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer, id<MTLRenderCommandEncoder> commandEncoder) |
| 210 | { |
| 211 | ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); |
| 212 | MetalContext* ctx = bd->SharedMetalContext; |
| 213 | |
| 214 | |
| 215 | int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); |
| 216 | int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); |
| 217 | if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) |
| 218 | return; |
| 219 | |
| 220 | |
| 221 | |
| 222 | if (draw_data->Textures != nullptr) |
| 223 | for (ImTextureData* tex : *draw_data->Textures) |
| 224 | if (tex->Status != ImTextureStatus_OK) |
| 225 | ImGui_ImplMetal_UpdateTexture(tex); |
| 226 | |
| 227 | |
| 228 | |
| 229 | id<MTLRenderPipelineState> renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor]; |
| 230 | if (renderPipelineState == nil) |
| 231 | { |
| 232 | |
| 233 | renderPipelineState = [ctx renderPipelineStateForFramebufferDescriptor:ctx.framebufferDescriptor device:commandBuffer.device]; |
| 234 | |
| 235 | |
| 236 | ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState; |
| 237 | } |
| 238 | |
| 239 | size_t vertexBufferLength = (size_t)draw_data->TotalVtxCount * sizeof(ImDrawVert); |
| 240 | size_t indexBufferLength = (size_t)draw_data->TotalIdxCount * sizeof(ImDrawIdx); |
| 241 | MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device]; |
| 242 | MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device]; |
| 243 | |
| 244 | bd->RenderCommandEncoder = commandEncoder; |
| 245 | ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); |
| 246 | |
| 247 | |
| 248 | ImVec2 clip_off = draw_data->DisplayPos; |
| 249 | ImVec2 clip_scale = draw_data->FramebufferScale; |
| 250 | |
| 251 | |
| 252 | size_t vertexBufferOffset = 0; |
| 253 | size_t indexBufferOffset = 0; |
| 254 | for (const ImDrawList* draw_list : draw_data->CmdLists) |
| 255 | { |
| 256 | memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, draw_list->VtxBuffer.Data, (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); |
| 257 | memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, draw_list->IdxBuffer.Data, (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); |
| 258 | |
| 259 | for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++) |
| 260 | { |
| 261 | const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i]; |
| 262 | if (pcmd->UserCallback) |
| 263 | { |
| 264 | |
| 265 | if (pcmd->UserCallback == ImGui_ImplMetal_DrawCallback_ResetRenderState) |
| 266 | ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); |
| 267 | else |
| 268 | pcmd->UserCallback(draw_list, pcmd); |
| 269 | } |
| 270 | else |
| 271 | { |
| 272 | |
| 273 | ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); |
| 274 | ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); |
| 275 | |
| 276 | |
| 277 | if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } |
| 278 | if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } |
| 279 | if (clip_max.x > (float)fb_width) { clip_max.x = (float)fb_width; } |
| 280 | if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; } |
| 281 | if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) |
| 282 | continue; |
| 283 | if (pcmd->ElemCount == 0) |
| 284 | continue; |
| 285 | |
| 286 | |
| 287 | MTLScissorRect scissorRect = |
| 288 | { |
| 289 | .x = NSUInteger(clip_min.x), |
| 290 | .y = NSUInteger(clip_min.y), |
| 291 | .width = NSUInteger(clip_max.x - clip_min.x), |
| 292 | .height = NSUInteger(clip_max.y - clip_min.y) |
| 293 | }; |
| 294 | [commandEncoder setScissorRect:scissorRect]; |
| 295 | |
| 296 | |
| 297 | ImTextureID tex_id = pcmd->GetTexID(); |
| 298 | if (tex_id != ImTextureID_Invalid) |
| 299 | [commandEncoder setFragmentTexture:(__bridge id<MTLTexture>)(void*)(intptr_t)(tex_id) atIndex:0]; |
| 300 | |
| 301 | [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0]; |
| 302 | [commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle |
| 303 | indexCount:pcmd->ElemCount |
| 304 | indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32 |
| 305 | indexBuffer:indexBuffer.buffer |
| 306 | indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)]; |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | vertexBufferOffset += (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert); |
| 311 | indexBufferOffset += (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx); |
| 312 | } |
| 313 | |
| 314 | MetalContext* sharedMetalContext = bd->SharedMetalContext; |
| 315 | [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>) |
| 316 | { |
| 317 | @synchronized(sharedMetalContext.bufferCacheLock) |
| 318 | { |
| 319 | [sharedMetalContext.bufferCache addObject:vertexBuffer]; |
| 320 | [sharedMetalContext.bufferCache addObject:indexBuffer]; |
| 321 | } |
| 322 | }]; |
| 323 | bd->RenderCommandEncoder = nil; |
| 324 | } |
| 325 | |
| 326 | static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex) |
| 327 | { |
| 328 | if (MetalTexture* backend_tex = (__bridge_transfer MetalTexture*)(tex->BackendUserData)) |
| 329 | { |
| 330 | IM_ASSERT(backend_tex.metalTexture == (__bridge id<MTLTexture>)(void*)(intptr_t)tex->TexID); |
| 331 | backend_tex.metalTexture = nil; |
| 332 | |
| 333 | |
| 334 | tex->SetTexID(ImTextureID_Invalid); |
| 335 | tex->BackendUserData = nullptr; |
| 336 | } |
| 337 | tex->SetStatus(ImTextureStatus_Destroyed); |
| 338 | } |
| 339 | |
| 340 | void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex) |
| 341 | { |
| 342 | ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); |
| 343 | if (tex->Status == ImTextureStatus_WantCreate) |
| 344 | { |
| 345 | |
| 346 | |
| 347 | IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); |
| 348 | IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); |
| 349 | |
| 350 | |
| 351 | |
| 352 | |
| 353 | |
| 354 | MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm |
| 355 | width:(NSUInteger)tex->Width |
| 356 | height:(NSUInteger)tex->Height |
| 357 | mipmapped:NO]; |
| 358 | textureDescriptor.usage = MTLTextureUsageShaderRead; |
| 359 | #if TARGET_OS_OSX || TARGET_OS_MACCATALYST |
| 360 | textureDescriptor.storageMode = MTLStorageModeManaged; |
| 361 | #else |
| 362 | textureDescriptor.storageMode = MTLStorageModeShared; |
| 363 | #endif |
| 364 | id <MTLTexture> texture = [bd->SharedMetalContext.device newTextureWithDescriptor:textureDescriptor]; |
| 365 | [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)tex->Width, (NSUInteger)tex->Height) mipmapLevel:0 withBytes:tex->Pixels bytesPerRow:(NSUInteger)tex->Width * 4]; |
| 366 | MetalTexture* backend_tex = [[MetalTexture alloc] initWithTexture:texture]; |
| 367 | |
| 368 | |
| 369 | tex->SetTexID((ImTextureID)(intptr_t)texture); |
| 370 | tex->SetStatus(ImTextureStatus_OK); |
| 371 | tex->BackendUserData = (__bridge_retained void*)(backend_tex); |
| 372 | } |
| 373 | else if (tex->Status == ImTextureStatus_WantUpdates) |
| 374 | { |
| 375 | |
| 376 | |
| 377 | MetalTexture* backend_tex = (__bridge MetalTexture*)(tex->BackendUserData); |
| 378 | for (ImTextureRect& r : tex->Updates) |
| 379 | { |
| 380 | [backend_tex.metalTexture replaceRegion:MTLRegionMake2D((NSUInteger)r.x, (NSUInteger)r.y, (NSUInteger)r.w, (NSUInteger)r.h) |
| 381 | mipmapLevel:0 |
| 382 | withBytes:tex->GetPixelsAt(r.x, r.y) |
| 383 | bytesPerRow:(NSUInteger)tex->Width * 4]; |
| 384 | } |
| 385 | tex->SetStatus(ImTextureStatus_OK); |
| 386 | } |
| 387 | else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) |
| 388 | { |
| 389 | ImGui_ImplMetal_DestroyTexture(tex); |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device) |
| 394 | { |
| 395 | ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); |
| 396 | MTLDepthStencilDescriptor* depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; |
| 397 | depthStencilDescriptor.depthWriteEnabled = NO; |
| 398 | depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; |
| 399 | bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; |
| 400 | MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init]; |
| 401 | samplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear; |
| 402 | samplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear; |
| 403 | samplerDescriptor.mipFilter = MTLSamplerMipFilterLinear; |
| 404 | bd->SharedMetalContext.samplerStateLinear = [device newSamplerStateWithDescriptor:samplerDescriptor]; |
| 405 | samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest; |
| 406 | samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest; |
| 407 | samplerDescriptor.mipFilter = MTLSamplerMipFilterNearest; |
| 408 | bd->SharedMetalContext.samplerStateNearest = [device newSamplerStateWithDescriptor:samplerDescriptor]; |
| 409 | |
| 410 | ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows(); |
| 411 | #ifdef IMGUI_IMPL_METAL_CPP |
| 412 | [samplerDescriptor release]; |
| 413 | [depthStencilDescriptor release]; |
| 414 | #endif |
| 415 | |
| 416 | return true; |
| 417 | } |
| 418 | |
| 419 | void ImGui_ImplMetal_DestroyDeviceObjects() |
| 420 | { |
| 421 | ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); |
| 422 | |
| 423 | |
| 424 | for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) |
| 425 | if (tex->RefCount == 1) |
| 426 | ImGui_ImplMetal_DestroyTexture(tex); |
| 427 | |
| 428 | ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows(); |
| 429 | [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects]; |
| 430 | bd->SharedMetalContext.samplerStateLinear = nil; |
| 431 | bd->SharedMetalContext.samplerStateNearest = nil; |
| 432 | } |
| 433 | |
| 434 | bool ImGui_ImplMetal_Init(id<MTLDevice> device) |
| 435 | { |
| 436 | ImGuiIO& io = ImGui::GetIO(); |
| 437 | IMGUI_CHECKVERSION(); |
| 438 | IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); |
| 439 | |
| 440 | ImGui_ImplMetal_Data* bd = IM_NEW(ImGui_ImplMetal_Data)(); |
| 441 | io.BackendRendererUserData = (void*)bd; |
| 442 | io.BackendRendererName = "imgui_impl_metal"; |
| 443 | io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; |
| 444 | io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; |
| 445 | io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; |
| 446 | |
| 447 | ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); |
| 448 | platform_io.DrawCallback_ResetRenderState = ImGui_ImplMetal_DrawCallback_ResetRenderState; |
| 449 | platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplMetal_DrawCallback_SetSamplerLinear; |
| 450 | platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplMetal_DrawCallback_SetSamplerNearest; |
| 451 | |
| 452 | bd->SharedMetalContext = [[MetalContext alloc] init]; |
| 453 | bd->SharedMetalContext.device = device; |
| 454 | |
| 455 | ImGui_ImplMetal_InitMultiViewportSupport(); |
| 456 | |
| 457 | return true; |
| 458 | } |
| 459 | |
| 460 | void ImGui_ImplMetal_Shutdown() |
| 461 | { |
| 462 | ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); |
| 463 | IM_UNUSED(bd); |
| 464 | IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); |
| 465 | ImGuiIO& io = ImGui::GetIO(); |
| 466 | ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); |
| 467 | |
| 468 | ImGui_ImplMetal_ShutdownMultiViewportSupport(); |
| 469 | ImGui_ImplMetal_DestroyDeviceObjects(); |
| 470 | ImGui_ImplMetal_DestroyBackendData(); |
| 471 | |
| 472 | io.BackendRendererName = nullptr; |
| 473 | io.BackendRendererUserData = nullptr; |
| 474 | io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports); |
| 475 | platform_io.ClearRendererHandlers(); |
| 476 | } |
| 477 | |
| 478 | #pragma mark - Multi-viewport support |
| 479 | |
| 480 | #import <QuartzCore/CAMetalLayer.h> |
| 481 | |
| 482 | #if TARGET_OS_OSX |
| 483 | #import <Cocoa/Cocoa.h> |
| 484 | #endif |
| 485 | |
| 486 | |
| 487 | |
| 488 | |
| 489 | |
| 490 | |
| 491 | |
| 492 | struct ImGuiViewportDataMetal |
| 493 | { |
| 494 | CAMetalLayer* MetalLayer; |
| 495 | id<MTLCommandQueue> CommandQueue; |
| 496 | MTLRenderPassDescriptor* RenderPassDescriptor; |
| 497 | void* Handle = nullptr; |
| 498 | bool FirstFrame = true; |
| 499 | }; |
| 500 | |
| 501 | static void ImGui_ImplMetal_CreateWindow(ImGuiViewport* viewport) |
| 502 | { |
| 503 | ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); |
| 504 | ImGuiViewportDataMetal* data = IM_NEW(ImGuiViewportDataMetal)(); |
| 505 | viewport->RendererUserData = data; |
| 506 | |
| 507 | |
| 508 | |
| 509 | void* handle = viewport->PlatformHandleRaw ? viewport->PlatformHandleRaw : viewport->PlatformHandle; |
| 510 | IM_ASSERT(handle != nullptr); |
| 511 | |
| 512 | id<MTLDevice> device = bd->SharedMetalContext.device; |
| 513 | CAMetalLayer* layer = [CAMetalLayer layer]; |
| 514 | layer.device = device; |
| 515 | layer.framebufferOnly = YES; |
| 516 | layer.pixelFormat = bd->SharedMetalContext.framebufferDescriptor.colorPixelFormat; |
| 517 | #if TARGET_OS_OSX |
| 518 | NSWindow* window = (__bridge NSWindow*)handle; |
| 519 | NSView* view = window.contentView; |
| 520 | view.layer = layer; |
| 521 | view.wantsLayer = YES; |
| 522 | #endif |
| 523 | data->MetalLayer = layer; |
| 524 | data->CommandQueue = [device newCommandQueue]; |
| 525 | data->RenderPassDescriptor = [[MTLRenderPassDescriptor alloc] init]; |
| 526 | data->Handle = handle; |
| 527 | } |
| 528 | |
| 529 | static void ImGui_ImplMetal_DestroyWindow(ImGuiViewport* viewport) |
| 530 | { |
| 531 | |
| 532 | if (ImGuiViewportDataMetal* data = (ImGuiViewportDataMetal*)viewport->RendererUserData) |
| 533 | IM_DELETE(data); |
| 534 | viewport->RendererUserData = nullptr; |
| 535 | } |
| 536 | |
| 537 | inline static CGSize MakeScaledSize(CGSize size, CGFloat scale) |
| 538 | { |
| 539 | return CGSizeMake(size.width * scale, size.height * scale); |
| 540 | } |
| 541 | |
| 542 | static void ImGui_ImplMetal_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) |
| 543 | { |
| 544 | ImGuiViewportDataMetal* data = (ImGuiViewportDataMetal*)viewport->RendererUserData; |
| 545 | data->MetalLayer.drawableSize = MakeScaledSize(CGSizeMake(size.x, size.y), viewport->DpiScale); |
| 546 | } |
| 547 | |
| 548 | static void ImGui_ImplMetal_RenderWindow(ImGuiViewport* viewport, void*) |
| 549 | { |
| 550 | ImGuiViewportDataMetal* data = (ImGuiViewportDataMetal*)viewport->RendererUserData; |
| 551 | |
| 552 | #if TARGET_OS_OSX |
| 553 | void* handle = viewport->PlatformHandleRaw ? viewport->PlatformHandleRaw : viewport->PlatformHandle; |
| 554 | NSWindow* window = (__bridge NSWindow*)handle; |
| 555 | |
| 556 | |
| 557 | if ((window.occlusionState & NSWindowOcclusionStateVisible) == 0 && !data->FirstFrame) |
| 558 | { |
| 559 | |
| 560 | |
| 561 | return; |
| 562 | } |
| 563 | data->FirstFrame = false; |
| 564 | |
| 565 | float fb_scale = (float)window.backingScaleFactor; |
| 566 | if (data->MetalLayer.contentsScale != fb_scale) |
| 567 | { |
| 568 | data->MetalLayer.contentsScale = fb_scale; |
| 569 | data->MetalLayer.drawableSize = MakeScaledSize(window.frame.size, fb_scale); |
| 570 | } |
| 571 | #endif |
| 572 | |
| 573 | id <CAMetalDrawable> drawable = [data->MetalLayer nextDrawable]; |
| 574 | if (drawable == nil) |
| 575 | return; |
| 576 | |
| 577 | MTLRenderPassDescriptor* renderPassDescriptor = data->RenderPassDescriptor; |
| 578 | renderPassDescriptor.colorAttachments[0].texture = drawable.texture; |
| 579 | renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0); |
| 580 | if ((viewport->Flags & ImGuiViewportFlags_NoRendererClear) == 0) |
| 581 | renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; |
| 582 | |
| 583 | id <MTLCommandBuffer> commandBuffer = [data->CommandQueue commandBuffer]; |
| 584 | id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; |
| 585 | ImGui_ImplMetal_RenderDrawData(viewport->DrawData, commandBuffer, renderEncoder); |
| 586 | [renderEncoder endEncoding]; |
| 587 | |
| 588 | [commandBuffer presentDrawable:drawable]; |
| 589 | [commandBuffer commit]; |
| 590 | } |
| 591 | |
| 592 | static void ImGui_ImplMetal_InitMultiViewportSupport() |
| 593 | { |
| 594 | ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); |
| 595 | platform_io.Renderer_CreateWindow = ImGui_ImplMetal_CreateWindow; |
| 596 | platform_io.Renderer_DestroyWindow = ImGui_ImplMetal_DestroyWindow; |
| 597 | platform_io.Renderer_SetWindowSize = ImGui_ImplMetal_SetWindowSize; |
| 598 | platform_io.Renderer_RenderWindow = ImGui_ImplMetal_RenderWindow; |
| 599 | } |
| 600 | |
| 601 | static void ImGui_ImplMetal_ShutdownMultiViewportSupport() |
| 602 | { |
| 603 | ImGui::DestroyPlatformWindows(); |
| 604 | } |
| 605 | |
| 606 | static void ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows() |
| 607 | { |
| 608 | ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); |
| 609 | for (int i = 1; i < platform_io.Viewports.Size; i++) |
| 610 | if (!platform_io.Viewports[i]->RendererUserData) |
| 611 | ImGui_ImplMetal_CreateWindow(platform_io.Viewports[i]); |
| 612 | } |
| 613 | |
| 614 | static void ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows() |
| 615 | { |
| 616 | ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); |
| 617 | for (int i = 1; i < platform_io.Viewports.Size; i++) |
| 618 | if (platform_io.Viewports[i]->RendererUserData) |
| 619 | ImGui_ImplMetal_DestroyWindow(platform_io.Viewports[i]); |
| 620 | } |
| 621 | |
| 622 | #pragma mark - MetalBuffer implementation |
| 623 | |
| 624 | @implementation MetalBuffer |
| 625 | - (instancetype)initWithBuffer:(id<MTLBuffer>)buffer |
| 626 | { |
| 627 | if ((self = [super init])) |
| 628 | { |
| 629 | _buffer = buffer; |
| 630 | _lastReuseTime = GetMachAbsoluteTimeInSeconds(); |
| 631 | } |
| 632 | return self; |
| 633 | } |
| 634 | @end |
| 635 | |
| 636 | #pragma mark - FramebufferDescriptor implementation |
| 637 | |
| 638 | @implementation FramebufferDescriptor |
| 639 | - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor |
| 640 | { |
| 641 | if ((self = [super init])) |
| 642 | { |
| 643 | _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount; |
| 644 | _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat; |
| 645 | _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat; |
| 646 | _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat; |
| 647 | } |
| 648 | return self; |
| 649 | } |
| 650 | |
| 651 | - (nonnull id)copyWithZone:(nullable NSZone*)zone |
| 652 | { |
| 653 | FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init]; |
| 654 | copy.sampleCount = self.sampleCount; |
| 655 | copy.colorPixelFormat = self.colorPixelFormat; |
| 656 | copy.depthPixelFormat = self.depthPixelFormat; |
| 657 | copy.stencilPixelFormat = self.stencilPixelFormat; |
| 658 | return copy; |
| 659 | } |
| 660 | |
| 661 | - (NSUInteger)hash |
| 662 | { |
| 663 | NSUInteger sc = _sampleCount & 0x3; |
| 664 | NSUInteger cf = _colorPixelFormat & 0x3FF; |
| 665 | NSUInteger df = _depthPixelFormat & 0x3FF; |
| 666 | NSUInteger sf = _stencilPixelFormat & 0x3FF; |
| 667 | NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc; |
| 668 | return hash; |
| 669 | } |
| 670 | |
| 671 | - (BOOL)isEqual:(id)object |
| 672 | { |
| 673 | FramebufferDescriptor* other = object; |
| 674 | if (![other isKindOfClass:[FramebufferDescriptor class]]) |
| 675 | return NO; |
| 676 | return other.sampleCount == self.sampleCount && |
| 677 | other.colorPixelFormat == self.colorPixelFormat && |
| 678 | other.depthPixelFormat == self.depthPixelFormat && |
| 679 | other.stencilPixelFormat == self.stencilPixelFormat; |
| 680 | } |
| 681 | |
| 682 | @end |
| 683 | |
| 684 | #pragma mark - MetalTexture implementation |
| 685 | |
| 686 | @implementation MetalTexture |
| 687 | - (instancetype)initWithTexture:(id<MTLTexture>)metalTexture |
| 688 | { |
| 689 | if ((self = [super init])) |
| 690 | self.metalTexture = metalTexture; |
| 691 | return self; |
| 692 | } |
| 693 | |
| 694 | @end |
| 695 | |
| 696 | #pragma mark - MetalContext implementation |
| 697 | |
| 698 | @implementation MetalContext |
| 699 | - (instancetype)init |
| 700 | { |
| 701 | if ((self = [super init])) |
| 702 | { |
| 703 | self.renderPipelineStateCache = [NSMutableDictionary dictionary]; |
| 704 | self.bufferCache = [NSMutableArray array]; |
| 705 | self.bufferCacheLock = [[NSObject alloc] init]; |
| 706 | _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); |
| 707 | } |
| 708 | return self; |
| 709 | } |
| 710 | |
| 711 | - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device |
| 712 | { |
| 713 | double now = GetMachAbsoluteTimeInSeconds(); |
| 714 | |
| 715 | @synchronized(self.bufferCacheLock) |
| 716 | { |
| 717 | |
| 718 | if (now - self.lastBufferCachePurge > 1.0) |
| 719 | { |
| 720 | NSMutableArray* survivors = [NSMutableArray array]; |
| 721 | for (MetalBuffer* candidate in self.bufferCache) |
| 722 | if (candidate.lastReuseTime > self.lastBufferCachePurge) |
| 723 | [survivors addObject:candidate]; |
| 724 | self.bufferCache = [survivors mutableCopy]; |
| 725 | self.lastBufferCachePurge = now; |
| 726 | } |
| 727 | |
| 728 | |
| 729 | MetalBuffer* bestCandidate = nil; |
| 730 | for (MetalBuffer* candidate in self.bufferCache) |
| 731 | if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime)) |
| 732 | bestCandidate = candidate; |
| 733 | |
| 734 | if (bestCandidate != nil) |
| 735 | { |
| 736 | [self.bufferCache removeObject:bestCandidate]; |
| 737 | bestCandidate.lastReuseTime = now; |
| 738 | return bestCandidate; |
| 739 | } |
| 740 | } |
| 741 | |
| 742 | |
| 743 | id<MTLBuffer> backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared]; |
| 744 | return [[MetalBuffer alloc] initWithBuffer:backing]; |
| 745 | } |
| 746 | |
| 747 | |
| 748 | - (id<MTLRenderPipelineState>)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id<MTLDevice>)device |
| 749 | { |
| 750 | NSError* error = nil; |
| 751 | |
| 752 | NSString* shaderSource = @"" |
| 753 | "#include <metal_stdlib>\n" |
| 754 | "using namespace metal;\n" |
| 755 | "\n" |
| 756 | "struct Uniforms {\n" |
| 757 | " float4x4 projectionMatrix;\n" |
| 758 | "};\n" |
| 759 | "\n" |
| 760 | "struct VertexIn {\n" |
| 761 | " float2 position [[attribute(0)]];\n" |
| 762 | " float2 texCoords [[attribute(1)]];\n" |
| 763 | " uchar4 color [[attribute(2)]];\n" |
| 764 | "};\n" |
| 765 | "\n" |
| 766 | "struct VertexOut {\n" |
| 767 | " float4 position [[position]];\n" |
| 768 | " float2 texCoords;\n" |
| 769 | " float4 color;\n" |
| 770 | "};\n" |
| 771 | "\n" |
| 772 | "vertex VertexOut vertex_main(VertexIn in [[stage_in]],\n" |
| 773 | " constant Uniforms &uniforms [[buffer(1)]]) {\n" |
| 774 | " VertexOut out;\n" |
| 775 | " out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n" |
| 776 | " out.texCoords = in.texCoords;\n" |
| 777 | " out.color = float4(in.color) / float4(255.0);\n" |
| 778 | " return out;\n" |
| 779 | "}\n" |
| 780 | "\n" |
| 781 | "fragment half4 fragment_main(VertexOut in [[stage_in]],\n" |
| 782 | " texture2d<half, access::sample> texture [[texture(0)]],\n" |
| 783 | " sampler textureSampler [[sampler(0)]]) {\n" |
| 784 | " half4 texColor = texture.sample(textureSampler, in.texCoords);\n" |
| 785 | " return half4(in.color) * texColor;\n" |
| 786 | "}\n"; |
| 787 | |
| 788 | id<MTLLibrary> library = [device newLibraryWithSource:shaderSource options:nil error:&error]; |
| 789 | if (library == nil) |
| 790 | { |
| 791 | NSLog(@"Error: failed to create Metal library: %@", error); |
| 792 | return nil; |
| 793 | } |
| 794 | |
| 795 | id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertex_main"]; |
| 796 | id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragment_main"]; |
| 797 | |
| 798 | if (vertexFunction == nil || fragmentFunction == nil) |
| 799 | { |
| 800 | NSLog(@"Error: failed to find Metal shader functions in library: %@", error); |
| 801 | return nil; |
| 802 | } |
| 803 | |
| 804 | MTLVertexDescriptor* vertexDescriptor = [MTLVertexDescriptor vertexDescriptor]; |
| 805 | vertexDescriptor.attributes[0].offset = offsetof(ImDrawVert, pos); |
| 806 | vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; |
| 807 | vertexDescriptor.attributes[0].bufferIndex = 0; |
| 808 | vertexDescriptor.attributes[1].offset = offsetof(ImDrawVert, uv); |
| 809 | vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; |
| 810 | vertexDescriptor.attributes[1].bufferIndex = 0; |
| 811 | vertexDescriptor.attributes[2].offset = offsetof(ImDrawVert, col); |
| 812 | vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; |
| 813 | vertexDescriptor.attributes[2].bufferIndex = 0; |
| 814 | vertexDescriptor.layouts[0].stepRate = 1; |
| 815 | vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; |
| 816 | vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert); |
| 817 | |
| 818 | MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; |
| 819 | pipelineDescriptor.vertexFunction = vertexFunction; |
| 820 | pipelineDescriptor.fragmentFunction = fragmentFunction; |
| 821 | pipelineDescriptor.vertexDescriptor = vertexDescriptor; |
| 822 | pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount; |
| 823 | pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat; |
| 824 | pipelineDescriptor.colorAttachments[0].blendingEnabled = YES; |
| 825 | pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; |
| 826 | pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; |
| 827 | pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; |
| 828 | pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; |
| 829 | pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; |
| 830 | pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; |
| 831 | pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat; |
| 832 | pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat; |
| 833 | |
| 834 | id<MTLRenderPipelineState> renderPipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error]; |
| 835 | if (error != nil) |
| 836 | NSLog(@"Error: failed to create Metal pipeline state: %@", error); |
| 837 | |
| 838 | return renderPipelineState; |
| 839 | } |
| 840 | |
| 841 | @end |
| 842 | |
| 843 | |
| 844 | |
| 845 | #endif |