diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a4d3ed459..cc84ae2d3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -572,6 +572,9 @@ jobs: - name: Build macOS example_sdl2_metal run: make -C examples/example_sdl2_metal + - name: Build macOS example_sdl3_metal4 + run: make -C examples/example_sdl3_metal4 + - name: Build macOS example_sdl2_opengl2 run: make -C examples/example_sdl2_opengl2 if: github.event_name == 'workflow_run' diff --git a/.gitignore b/.gitignore index a8b4e38e3..83c0ab423 100644 --- a/.gitignore +++ b/.gitignore @@ -72,6 +72,7 @@ examples/example_sdl2_opengl3/example_sdl2_opengl3 examples/example_sdl2_sdlrenderer2/example_sdl2_sdlrenderer2 examples/example_sdl2_vulkan/example_sdl2_vulkan examples/example_sdl3_metal/example_sdl3_metal +examples/example_sdl3_metal4/example_sdl3_metal4 examples/example_sdl3_opengl3/example_sdl3_opengl3 examples/example_sdl3_sdlgpu3/example_sdl3_sdlgpu3 examples/example_sdl3_sdlrenderer3/example_sdl3_sdlrenderer3 diff --git a/backends/imgui_impl_metal4.h b/backends/imgui_impl_metal4.h index 8af2c8b6b..7f9ec2d16 100644 --- a/backends/imgui_impl_metal4.h +++ b/backends/imgui_impl_metal4.h @@ -1,8 +1,8 @@ -// dear imgui: Renderer Backend for Metal +// dear imgui: Renderer Backend for Metal 4 // This needs to be used along with a Platform Backend (e.g. OSX) // Implemented features: -// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: User texture binding. Use 'MTLTexture.gpuResourceID' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). @@ -24,55 +24,31 @@ #ifdef __OBJC__ -@class MTLRenderPassDescriptor; -@protocol MTLDevice, MTLCommandBuffer, MTLRenderCommandEncoder; +@class MTL4RenderPassDescriptor; +@protocol MTLDevice, MTL4CommandBuffer, MTL4RenderCommandEncoder, MTL4CommandQueue; +// framesInFlight must match the number of frames your application keeps in flight (e.g. the size of your own +// command buffer/allocator ring). The backend uses it to size its own per-frame-in-flight resources (constant +// buffer, vertex/index buffer cache) so the CPU never overwrites a slot the GPU may still be reading. // Follow "Getting Started" link and check examples/ folder to learn about using backends! -IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id device); -IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor); -IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, - id commandBuffer, - id commandEncoder); +IMGUI_IMPL_API bool ImGui_ImplMetal4_Init(id device, id commandQueue, int framesInFlight); +IMGUI_IMPL_API void ImGui_ImplMetal4_Shutdown(); +// frameInFlightIndex must match the slot you use to index your own per-frame-in-flight resources +// (e.g. the same index used to pick your command buffer/allocator), and must be < framesInFlight passed to Init(). +IMGUI_IMPL_API void ImGui_ImplMetal4_NewFrame(MTL4RenderPassDescriptor* renderPassDescriptor, int frameInFlightIndex); +IMGUI_IMPL_API void ImGui_ImplMetal4_RenderDrawData(ImDrawData* drawData, + id commandBuffer, + id commandEncoder); // Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id device); -IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); +IMGUI_IMPL_API bool ImGui_ImplMetal4_CreateDeviceObjects(id device); +IMGUI_IMPL_API void ImGui_ImplMetal4_DestroyDeviceObjects(); // (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually. -IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex); +IMGUI_IMPL_API void ImGui_ImplMetal4_UpdateTexture(ImTextureData* tex); #endif -//----------------------------------------------------------------------------- -// C++ API -//----------------------------------------------------------------------------- - -// Enable Metal C++ binding support with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file -// More info about using Metal from C++: https://developer.apple.com/metal/cpp/ - -#ifdef IMGUI_IMPL_METAL_CPP -#include -#ifndef __OBJC__ - -// Follow "Getting Started" link and check examples/ folder to learn about using backends! -IMGUI_IMPL_API bool ImGui_ImplMetal_Init(MTL::Device* device); -IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor); -IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, - MTL::CommandBuffer* commandBuffer, - MTL::RenderCommandEncoder* commandEncoder); - -// Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device); -IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); - -// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually. -IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex); - -#endif -#endif - //----------------------------------------------------------------------------- #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm index 652668c78..1114b470b 100644 --- a/backends/imgui_impl_metal4.mm +++ b/backends/imgui_impl_metal4.mm @@ -1,4 +1,4 @@ -// dear imgui: Renderer Backend for Metal +// dear imgui: Renderer Backend for Metal 4 // This needs to be used along with a Platform Backend (e.g. OSX) // Implemented features: @@ -14,43 +14,26 @@ // - Documentation https://dearimgui.com/docs (same as your local docs/ folder). // - Introduction, links and more at the top of imgui.cpp +// FIXME: Metal-cpp support +// FIXME?: Texture view pool support + // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-04-28: Added support for standard draw callbacks (in platform_io): DrawCallback_SetSamplerLinear and DrawCallback_SetSamplerNearest. (#9378, #9381) -// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState (others are not yet supported). (#9378) -// 2026-04-14: Metal: use a dedicated bufferCacheLock to avoid crashing when bufferCache is replaced by a new object while being used for @synchronize(). (#9367) -// 2026-04-03: Metal: avoid redundant vertex buffer bind in SetupRenderState. (#9343) -// 2026-03-19: Fixed issue in ImGui_ImplMetal_RenderDrawData() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310) -// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. -// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplMetal_CreateFontsTexture() and ImGui_ImplMetal_DestroyFontsTexture(). -// 2025-02-03: Metal: Crash fix. (#8367) -// 2025-01-08: Metal: Fixed memory leaks when using metal-cpp (#8276, #8166) or when using multiple contexts (#7419). -// 2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'. -// 2022-07-05: Metal: Add dispatch synchronization. -// 2022-06-30: Metal: Use __bridge for ARC based systems. -// 2022-06-01: Metal: Fixed null dereference on exit inside command buffer completion handler. -// 2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts. -// 2022-01-03: Metal: Ignore ImDrawCmd where ElemCount == 0 (very rare but can technically be manufactured by user code). -// 2021-12-30: Metal: Added Metal C++ support. Enable with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file. -// 2021-08-24: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted. (#4464) -// 2021-05-19: Metal: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) -// 2021-02-18: Metal: Change blending equation to preserve alpha in output buffer. -// 2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst. -// 2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. -// 2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. -// 2019-02-11: Metal: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. -// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. -// 2018-07-05: Metal: Added new Metal backend implementation. +// 2026-29-06: Metal 4: Added new Metal 4 backend implementation. #include "imgui.h" #ifndef IMGUI_DISABLE -#include "imgui_impl_metal.h" +#include "imgui_impl_metal4.h" #import #import -#pragma mark - Support classes +#pragma mark - Support classes and structs + +struct ImGui_Metal4_ConstantData +{ + float ModelViewProjectionMatrix[4][4]; +}; -// A wrapper around a MTLBuffer object that knows the last time it was reused @interface MetalBuffer : NSObject @property (nonatomic, strong) id buffer; @property (nonatomic, assign) double lastReuseTime; @@ -64,7 +47,7 @@ @property (nonatomic, assign) MTLPixelFormat colorPixelFormat; @property (nonatomic, assign) MTLPixelFormat depthPixelFormat; @property (nonatomic, assign) MTLPixelFormat stencilPixelFormat; -- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor; +- (instancetype)initWithRenderPassDescriptor:(MTL4RenderPassDescriptor*)renderPassDescriptor; @end @interface MetalTexture : NSObject @@ -77,83 +60,63 @@ // font texture, and manages the reusable buffer cache. @interface MetalContext : NSObject @property (nonatomic, strong) id device; +@property (nonatomic, strong) id commandQueue; @property (nonatomic, strong) id depthStencilState; +@property (nonatomic, strong) id argumentTable; @property (nonatomic, strong) id samplerStateLinear; @property (nonatomic, strong) id samplerStateNearest; -@property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient -@property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors -@property (nonatomic, strong) NSMutableArray* bufferCache; +@property (nonatomic, strong) id residencySet; +@property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; +@property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; +@property (nonatomic, assign) NSUInteger framesInFlight; +@property (nonatomic, assign) NSUInteger currentFrameSlot; +@property (nonatomic, strong) NSArray>* constantBuffers; +@property (nonatomic, assign) ImGui_Metal4_ConstantData** constantBufferContentsArray; +@property (nonatomic, strong) NSMutableArray*>* bufferCaches; @property (nonatomic, strong) NSObject* bufferCacheLock; @property (nonatomic, assign) double lastBufferCachePurge; +- (id)currentConstantBuffer; +- (ImGui_Metal4_ConstantData*)currentConstantBufferContents; - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device; - (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device; @end -struct ImGui_ImplMetal_Data +struct ImGui_ImplMetal4_Data { - MetalContext* SharedMetalContext; - id RenderCommandEncoder; + MetalContext* SharedMetalContext; + id RenderCommandEncoder; - ImGui_ImplMetal_Data() { memset((void*)this, 0, sizeof(*this)); } + ImGui_ImplMetal4_Data() { memset((void*)this, 0, sizeof(*this)); } }; -static ImGui_ImplMetal_Data* ImGui_ImplMetal_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } -static void ImGui_ImplMetal_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal_GetBackendData()); } +static ImGui_ImplMetal4_Data* ImGui_ImplMetal4_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal4_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +static void ImGui_ImplMetal4_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal4_GetBackendData()); } static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); } -#ifdef IMGUI_IMPL_METAL_CPP - -#pragma mark - Dear ImGui Metal C++ Backend API - -bool ImGui_ImplMetal_Init(MTL::Device* device) -{ - return ImGui_ImplMetal_Init((__bridge id)(device)); -} - -void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor) -{ - ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor)); -} - -void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, - MTL::CommandBuffer* commandBuffer, - MTL::RenderCommandEncoder* commandEncoder) -{ - ImGui_ImplMetal_RenderDrawData(draw_data, - (__bridge id)(commandBuffer), - (__bridge id)(commandEncoder)); - -} - -bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) -{ - return ImGui_ImplMetal_CreateDeviceObjects((__bridge id)(device)); -} - -#endif // #ifdef IMGUI_IMPL_METAL_CPP - #pragma mark - Dear ImGui Metal Backend API -void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) +void ImGui_ImplMetal4_NewFrame(MTL4RenderPassDescriptor* renderPassDescriptor, int frameInFlightIndex) { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); - IM_ASSERT(bd != nil && "Context or backend not initialized! Did you call ImGui_ImplMetal_Init()?"); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); + IM_ASSERT(bd != nil && "Context or backend not initialized! Did you call ImGui_ImplMetal4_Init()?"); + IM_ASSERT(frameInFlightIndex < bd->SharedMetalContext.framesInFlight && "frameInFlightIndex out of range! See framesInFlight passed to ImGui_ImplMetal4_Init()."); #ifdef IMGUI_IMPL_METAL_CPP bd->SharedMetalContext.framebufferDescriptor = [[[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]autorelease]; #else bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]; #endif + bd->SharedMetalContext.currentFrameSlot = (NSUInteger)frameInFlightIndex; if (bd->SharedMetalContext.depthStencilState == nil) - ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device); + ImGui_ImplMetal4_CreateDeviceObjects(bd->SharedMetalContext.device); } -static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, id commandBuffer, - id commandEncoder, id renderPipelineState, +static void ImGui_ImplMetal4_SetupRenderState(ImDrawData* draw_data, id commandBuffer, + id commandEncoder, id renderPipelineState, MetalBuffer* vertexBuffer, size_t vertexBufferOffset) { IM_UNUSED(commandBuffer); - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); [commandEncoder setCullMode:MTLCullModeNone]; [commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState]; @@ -184,23 +147,24 @@ static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, idSharedMetalContext currentConstantBufferContents]; + memcpy(constantBufferContents->ModelViewProjectionMatrix, ortho_projection, sizeof(ortho_projection)); + id argumentTable = bd->SharedMetalContext.argumentTable; + [argumentTable setAddress:[bd->SharedMetalContext currentConstantBuffer].gpuAddress atIndex:1]; + [argumentTable setAddress:(vertexBuffer.buffer.gpuAddress + vertexBufferOffset) attributeStride:sizeof(ImDrawVert) atIndex:0]; + [argumentTable setSamplerState:bd->SharedMetalContext.samplerStateLinear.gpuResourceID atIndex:0]; + [commandEncoder setArgumentTable:argumentTable atStages:MTLRenderStageVertex | MTLRenderStageFragment]; [commandEncoder setRenderPipelineState:renderPipelineState]; - [commandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateLinear atIndex:0]; - - [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:vertexBufferOffset atIndex:0]; } -// Draw callbacks -static void ImGui_ImplMetal_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. -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]; } -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]; } +static void ImGui_ImplMetal4_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplMetal4_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); [bd->SharedMetalContext.argumentTable setSamplerState:bd->SharedMetalContext.samplerStateLinear.gpuResourceID atIndex:0]; } +static void ImGui_ImplMetal4_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); [bd->SharedMetalContext.argumentTable setSamplerState:bd->SharedMetalContext.samplerStateNearest.gpuResourceID atIndex:0]; } -// Metal Render function. -void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id commandBuffer, id commandEncoder) +void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, id commandBuffer, id commandEncoder) { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); MetalContext* ctx = bd->SharedMetalContext; // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) @@ -214,7 +178,7 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id if (draw_data->Textures != nullptr) for (ImTextureData* tex : *draw_data->Textures) if (tex->Status != ImTextureStatus_OK) - ImGui_ImplMetal_UpdateTexture(tex); + ImGui_ImplMetal4_UpdateTexture(tex); // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame // The hit rate for this cache should be very near 100%. @@ -234,12 +198,15 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device]; bd->RenderCommandEncoder = commandEncoder; - ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); + ImGui_ImplMetal4_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + // Before rendering command lists, commit residency set + [bd->SharedMetalContext.residencySet commit]; + // Render command lists size_t vertexBufferOffset = 0; size_t indexBufferOffset = 0; @@ -254,8 +221,8 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id if (pcmd->UserCallback) { // User callback, registered via ImDrawList::AddCallback() - if (pcmd->UserCallback == ImGui_ImplMetal_DrawCallback_ResetRenderState) - ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); + if (pcmd->UserCallback == ImGui_ImplMetal4_DrawCallback_ResetRenderState) + ImGui_ImplMetal4_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); else pcmd->UserCallback(draw_list, pcmd); } @@ -288,14 +255,19 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id // Bind texture, Draw ImTextureID tex_id = pcmd->GetTexID(); if (tex_id != ImTextureID_Invalid) - [commandEncoder setFragmentTexture:(__bridge id)(void*)(intptr_t)(tex_id) atIndex:0]; + { + id texture = (__bridge id)(void*)(intptr_t)tex_id; + [bd->SharedMetalContext.argumentTable setTexture:texture.gpuResourceID atIndex:0]; + } - [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0]; + [bd->SharedMetalContext.argumentTable setAddress:(vertexBuffer.buffer.gpuAddress + vertexBufferOffset + (pcmd->VtxOffset * sizeof(ImDrawVert))) attributeStride:sizeof(ImDrawVert) atIndex:0]; + + size_t indexBufferCmdOffset = indexBufferOffset + (pcmd->IdxOffset * sizeof(ImDrawIdx)); [commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle - indexCount:pcmd->ElemCount - indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32 - indexBuffer:indexBuffer.buffer - indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)]; + indexCount:pcmd->ElemCount + indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32 + indexBuffer:indexBuffer.buffer.gpuAddress + indexBufferCmdOffset + indexBufferLength:indexBuffer.buffer.length - indexBufferCmdOffset]; } } @@ -304,18 +276,16 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id } MetalContext* sharedMetalContext = bd->SharedMetalContext; - [commandBuffer addCompletedHandler:^(id) + @synchronized(sharedMetalContext.bufferCacheLock) { - @synchronized(sharedMetalContext.bufferCacheLock) - { - [sharedMetalContext.bufferCache addObject:vertexBuffer]; - [sharedMetalContext.bufferCache addObject:indexBuffer]; - } - }]; + NSMutableArray* slotCache = sharedMetalContext.bufferCaches[sharedMetalContext.currentFrameSlot]; + [slotCache addObject:vertexBuffer]; + [slotCache addObject:indexBuffer]; + } bd->RenderCommandEncoder = nil; } -static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex) +static void ImGui_ImplMetal4_DestroyTexture(ImTextureData* tex) { if (MetalTexture* backend_tex = (__bridge_transfer MetalTexture*)(tex->BackendUserData)) { @@ -329,9 +299,9 @@ static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex) tex->SetStatus(ImTextureStatus_Destroyed); } -void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex) +void ImGui_ImplMetal4_UpdateTexture(ImTextureData* tex) { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); if (tex->Status == ImTextureStatus_WantCreate) { // Create and upload new texture to graphics system @@ -348,12 +318,10 @@ void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex) height:(NSUInteger)tex->Height mipmapped:NO]; textureDescriptor.usage = MTLTextureUsageShaderRead; - #if TARGET_OS_OSX || TARGET_OS_MACCATALYST - textureDescriptor.storageMode = MTLStorageModeManaged; - #else textureDescriptor.storageMode = MTLStorageModeShared; - #endif + id texture = [bd->SharedMetalContext.device newTextureWithDescriptor:textureDescriptor]; + [bd->SharedMetalContext.residencySet addAllocation:texture]; [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)tex->Width, (NSUInteger)tex->Height) mipmapLevel:0 withBytes:tex->Pixels bytesPerRow:(NSUInteger)tex->Width * 4]; MetalTexture* backend_tex = [[MetalTexture alloc] initWithTexture:texture]; @@ -378,18 +346,29 @@ void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) { - ImGui_ImplMetal_DestroyTexture(tex); + ImGui_ImplMetal4_DestroyTexture(tex); } } -bool ImGui_ImplMetal_CreateDeviceObjects(id device) +bool ImGui_ImplMetal4_CreateDeviceObjects(id device) { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); + + MTLResidencySetDescriptor* residencySetDescriptor = [[MTLResidencySetDescriptor alloc] init]; + residencySetDescriptor.initialCapacity = 1000; + + NSError* error = nil; + bd->SharedMetalContext.residencySet = [device newResidencySetWithDescriptor:residencySetDescriptor error:&error]; + IM_ASSERT(bd->SharedMetalContext.residencySet != nil && error == nil); + + [bd->SharedMetalContext.commandQueue addResidencySet:bd->SharedMetalContext.residencySet]; + MTLDepthStencilDescriptor* depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; depthStencilDescriptor.depthWriteEnabled = NO; depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init]; + samplerDescriptor.supportArgumentBuffers = YES; samplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear; samplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear; samplerDescriptor.mipFilter = MTLSamplerMipFilterLinear; @@ -398,61 +377,84 @@ bool ImGui_ImplMetal_CreateDeviceObjects(id device) samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest; samplerDescriptor.mipFilter = MTLSamplerMipFilterNearest; bd->SharedMetalContext.samplerStateNearest = [device newSamplerStateWithDescriptor:samplerDescriptor]; -#ifdef IMGUI_IMPL_METAL_CPP - [samplerDescriptor release]; - [depthStencilDescriptor release]; -#endif + + NSMutableArray>* constantBuffers = [NSMutableArray array]; + ImGui_Metal4_ConstantData** constantBufferContentsArray = (ImGui_Metal4_ConstantData**)malloc(sizeof(ImGui_Metal4_ConstantData*) * bd->SharedMetalContext.framesInFlight); + for (NSUInteger i = 0; i < bd->SharedMetalContext.framesInFlight; i++) + { + id constantBuffer = [device newBufferWithLength:sizeof(ImGui_Metal4_ConstantData) options:MTLResourceStorageModeShared]; + [constantBuffers addObject:constantBuffer]; + constantBufferContentsArray[i] = (ImGui_Metal4_ConstantData*)constantBuffer.contents; + [bd->SharedMetalContext.residencySet addAllocation:constantBuffer]; + } + bd->SharedMetalContext.constantBuffers = constantBuffers; + bd->SharedMetalContext.constantBufferContentsArray = constantBufferContentsArray; + + MTL4ArgumentTableDescriptor* argumentTableDescriptor = [[MTL4ArgumentTableDescriptor alloc] init]; + argumentTableDescriptor.maxBufferBindCount = 8; + argumentTableDescriptor.maxTextureBindCount = 8; + argumentTableDescriptor.maxSamplerStateBindCount = 8; + argumentTableDescriptor.supportAttributeStrides = YES; // required: vertex buffer is bound via setAddress:stride:atIndex: for stage_in fetch + + bd->SharedMetalContext.argumentTable = [device newArgumentTableWithDescriptor:argumentTableDescriptor error:&error]; + IM_ASSERT(bd->SharedMetalContext.argumentTable != nil && error == nil); return true; } -void ImGui_ImplMetal_DestroyDeviceObjects() +void ImGui_ImplMetal4_DestroyDeviceObjects() { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); // Destroy all textures for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) if (tex->RefCount == 1) - ImGui_ImplMetal_DestroyTexture(tex); + ImGui_ImplMetal4_DestroyTexture(tex); [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects]; bd->SharedMetalContext.samplerStateLinear = nil; bd->SharedMetalContext.samplerStateNearest = nil; } -bool ImGui_ImplMetal_Init(id device) +bool ImGui_ImplMetal4_Init(id device, id commandQueue, int framesInFlight) { ImGuiIO& io = ImGui::GetIO(); IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + IM_ASSERT(framesInFlight > 0 && "framesInFlight must be at least 1!"); - ImGui_ImplMetal_Data* bd = IM_NEW(ImGui_ImplMetal_Data)(); + ImGui_ImplMetal4_Data* bd = IM_NEW(ImGui_ImplMetal4_Data)(); io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_metal"; + io.BackendRendererName = "imgui_impl_metal4"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - platform_io.DrawCallback_ResetRenderState = ImGui_ImplMetal_DrawCallback_ResetRenderState; - platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplMetal_DrawCallback_SetSamplerLinear; - platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplMetal_DrawCallback_SetSamplerNearest; + platform_io.DrawCallback_ResetRenderState = ImGui_ImplMetal4_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplMetal4_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplMetal4_DrawCallback_SetSamplerNearest; bd->SharedMetalContext = [[MetalContext alloc] init]; bd->SharedMetalContext.device = device; - + bd->SharedMetalContext.commandQueue = commandQueue; + bd->SharedMetalContext.framesInFlight = (NSUInteger)framesInFlight; + NSMutableArray*>* bufferCaches = [NSMutableArray array]; + for (NSUInteger i = 0; i < framesInFlight; i++) + [bufferCaches addObject:[NSMutableArray array]]; + bd->SharedMetalContext.bufferCaches = bufferCaches; return true; } -void ImGui_ImplMetal_Shutdown() +void ImGui_ImplMetal4_Shutdown() { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); IM_UNUSED(bd); IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); ImGuiIO& io = ImGui::GetIO(); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - ImGui_ImplMetal_DestroyDeviceObjects(); - ImGui_ImplMetal_DestroyBackendData(); + ImGui_ImplMetal4_DestroyDeviceObjects(); + ImGui_ImplMetal4_DestroyBackendData(); io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; @@ -460,8 +462,6 @@ void ImGui_ImplMetal_Shutdown() platform_io.ClearRendererHandlers(); } -#pragma mark - MetalBuffer implementation - @implementation MetalBuffer - (instancetype)initWithBuffer:(id)buffer { @@ -477,7 +477,7 @@ void ImGui_ImplMetal_Shutdown() #pragma mark - FramebufferDescriptor implementation @implementation FramebufferDescriptor -- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor +- (instancetype)initWithRenderPassDescriptor:(MTL4RenderPassDescriptor*)renderPassDescriptor { if ((self = [super init])) { @@ -542,39 +542,57 @@ void ImGui_ImplMetal_Shutdown() if ((self = [super init])) { self.renderPipelineStateCache = [NSMutableDictionary dictionary]; - self.bufferCache = [NSMutableArray array]; self.bufferCacheLock = [[NSObject alloc] init]; _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); } return self; } +- (void)dealloc +{ + free(_constantBufferContentsArray); +} + +- (id)currentConstantBuffer +{ + return self.constantBuffers[self.currentFrameSlot]; +} + +- (ImGui_Metal4_ConstantData*)currentConstantBufferContents +{ + return self.constantBufferContentsArray[self.currentFrameSlot]; +} + - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device { double now = GetMachAbsoluteTimeInSeconds(); + NSMutableArray* slotCache = self.bufferCaches[self.currentFrameSlot]; @synchronized(self.bufferCacheLock) { // Purge old buffers that haven't been useful for a while if (now - self.lastBufferCachePurge > 1.0) { - NSMutableArray* survivors = [NSMutableArray array]; - for (MetalBuffer* candidate in self.bufferCache) - if (candidate.lastReuseTime > self.lastBufferCachePurge) - [survivors addObject:candidate]; - self.bufferCache = [survivors mutableCopy]; + for (NSMutableArray* cache in self.bufferCaches) + { + NSMutableArray* survivors = [NSMutableArray array]; + for (MetalBuffer* candidate in cache) + if (candidate.lastReuseTime > self.lastBufferCachePurge) + [survivors addObject:candidate]; + [cache setArray:survivors]; + } self.lastBufferCachePurge = now; } - // See if we have a buffer we can reuse + // See if we have a buffer we can reuse, from this frame-in-flight slot's own cache MetalBuffer* bestCandidate = nil; - for (MetalBuffer* candidate in self.bufferCache) + for (MetalBuffer* candidate in slotCache) if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime)) bestCandidate = candidate; if (bestCandidate != nil) { - [self.bufferCache removeObject:bestCandidate]; + [slotCache removeObject:bestCandidate]; bestCandidate.lastReuseTime = now; return bestCandidate; } @@ -582,51 +600,54 @@ void ImGui_ImplMetal_Shutdown() // No luck; make a new buffer id backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared]; + [self.residencySet addAllocation:backing]; return [[MetalBuffer alloc] initWithBuffer:backing]; } -// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. +const char* shaderCode = R"( +#include +using namespace metal; + +struct Uniforms { + float4x4 projectionMatrix; +}; + +struct VertexIn { + float2 position [[attribute(0)]]; + float2 texCoords [[attribute(1)]]; + uchar4 color [[attribute(2)]]; +}; + +struct VertexOut { + float4 position [[position]]; + float2 texCoords; + float4 color; +}; + +vertex VertexOut vertex_main(VertexIn in [[stage_in]], + constant Uniforms &uniforms [[buffer(1)]]) +{ + VertexOut out; + out.position = uniforms.projectionMatrix * float4(in.position, 0, 1); + out.texCoords = in.texCoords; + out.color = float4(in.color) / float4(255.0); + return out; +} + +fragment half4 fragment_main(VertexOut in [[stage_in]], + texture2d texture [[texture(0)]], + sampler textureSampler [[sampler(0)]]) +{ + half4 texColor = texture.sample(textureSampler, in.texCoords); + return half4(in.color) * texColor; +} +)"; + - (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device { NSError* error = nil; - NSString* shaderSource = @"" - "#include \n" - "using namespace metal;\n" - "\n" - "struct Uniforms {\n" - " float4x4 projectionMatrix;\n" - "};\n" - "\n" - "struct VertexIn {\n" - " float2 position [[attribute(0)]];\n" - " float2 texCoords [[attribute(1)]];\n" - " uchar4 color [[attribute(2)]];\n" - "};\n" - "\n" - "struct VertexOut {\n" - " float4 position [[position]];\n" - " float2 texCoords;\n" - " float4 color;\n" - "};\n" - "\n" - "vertex VertexOut vertex_main(VertexIn in [[stage_in]],\n" - " constant Uniforms &uniforms [[buffer(1)]]) {\n" - " VertexOut out;\n" - " out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n" - " out.texCoords = in.texCoords;\n" - " out.color = float4(in.color) / float4(255.0);\n" - " return out;\n" - "}\n" - "\n" - "fragment half4 fragment_main(VertexOut in [[stage_in]],\n" - " texture2d texture [[texture(0)]],\n" - " sampler textureSampler [[sampler(0)]]) {\n" - " half4 texColor = texture.sample(textureSampler, in.texCoords);\n" - " return half4(in.color) * texColor;\n" - "}\n"; - - id library = [device newLibraryWithSource:shaderSource options:nil error:&error]; + id library = [device newLibraryWithSource:[NSString stringWithUTF8String:shaderCode] options:nil error:&error]; if (library == nil) { NSLog(@"Error: failed to create Metal library: %@", error); diff --git a/examples/example_sdl3_metal4/Makefile b/examples/example_sdl3_metal4/Makefile new file mode 100644 index 000000000..55c83416c --- /dev/null +++ b/examples/example_sdl3_metal4/Makefile @@ -0,0 +1,48 @@ +# +# You will need SDL3 (http://www.libsdl.org): +# brew install sdl3 +# + +#CXX = g++ +#CXX = clang++ + +EXE = example_sdl3_metal4 +IMGUI_DIR = ../.. +SOURCES = main.mm +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_sdl3.cpp $(IMGUI_DIR)/backends/imgui_impl_metal4.mm +OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) + +LIBS = -framework Metal -framework MetalKit -framework Cocoa -framework IOKit -framework CoreVideo -framework QuartzCore +LIBS += `pkg-config --libs sdl3` +LIBS += -L/usr/local/lib -L/opt/local/lib + +CXXFLAGS += `pkg-config --cflags sdl3` +CXXFLAGS += -I/usr/local/include -I/opt/local/include +CXXFLAGS += -std=c++11 -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +CXXFLAGS += -Wall -Wformat +CFLAGS = $(CXXFLAGS) + +%.o:%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/backends/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:%.mm + $(CXX) $(CXXFLAGS) -ObjC++ -fobjc-weak -fobjc-arc -c -o $@ $< + +%.o:$(IMGUI_DIR)/backends/%.mm + $(CXX) $(CXXFLAGS) -ObjC++ -fobjc-weak -fobjc-arc -c -o $@ $< + +all: $(EXE) + @echo Build complete + +$(EXE): $(OBJS) + $(CXX) -o $@ $^ $(CXXFLAGS) $(LIBS) + +clean: + rm -f $(EXE) $(OBJS) diff --git a/examples/example_sdl3_metal4/main.mm b/examples/example_sdl3_metal4/main.mm new file mode 100644 index 000000000..f0bb8f0ca --- /dev/null +++ b/examples/example_sdl3_metal4/main.mm @@ -0,0 +1,239 @@ +// Dear ImGui: standalone example application for SDL3 + Metal 4 +// (SDL is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan/Metal graphics context creation, etc.) + +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#include "imgui.h" +#include "imgui_impl_sdl3.h" +#include "imgui_impl_metal4.h" +#include // printf, fprintf +#include + +#import +#import + +#define FRAMES_IN_FLIGHT 3 + +// Main code +int main(int, char**) +{ + // Setup SDL + // [If using SDL_MAIN_USE_CALLBACKS: all code below until the main loop starts would likely be your SDL_AppInit() function] + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) + { + printf("Error: SDL_Init(): %s\n", SDL_GetError()); + return 1; + } + + // Create SDL window graphics context + float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); + SDL_WindowFlags window_flags = SDL_WINDOW_METAL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+Metal example", (int)(1280 * main_scale), (int)(800 * main_scale), window_flags); + if (window == nullptr) + { + printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); + return 1; + } + SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + SDL_ShowWindow(window); + + // Create Metal device _before_ creating the view/layer + id metalDevice = MTLCreateSystemDefaultDevice(); + if (!metalDevice) + { + printf("Error: failed to create Metal device.\n"); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + SDL_MetalView view = SDL_Metal_CreateView(window); + CAMetalLayer* layer = (__bridge CAMetalLayer*)SDL_Metal_GetLayer(view); + layer.device = metalDevice; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm; + + id commandQueue = [layer.device newMTL4CommandQueue]; + id sharedEvent = [layer.device newSharedEvent]; + MTL4RenderPassDescriptor* renderPassDescriptor = [MTL4RenderPassDescriptor new]; + + [commandQueue addResidencySet:layer.residencySet]; + + id commandBuffers[FRAMES_IN_FLIGHT]; + id commandAllocators[FRAMES_IN_FLIGHT]; + for (int i = 0; i < FRAMES_IN_FLIGHT; i++) + { + commandAllocators[i] = [layer.device newCommandAllocator]; + commandBuffers[i] = [layer.device newCommandBuffer]; + } + uint32_t frameIndex = 0; + uint32_t frameInFlight = 0; + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (in docking branch: using io.ConfigDpiScaleFonts=true automatically overrides this for every window depending on the current monitor) + + // Setup Platform/Renderer backends + ImGui_ImplMetal4_Init(layer.device, commandQueue, FRAMES_IN_FLIGHT); + ImGui_ImplSDL3_InitForMetal(window); + + // Load Fonts + // - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap(). + // This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold. + // - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit). + // - Read 'docs/FONTS.md' for more instructions and details. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefaultVector(); + //io.Fonts->AddFontDefaultBitmap(); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); + //IM_ASSERT(font != nullptr); + + // Our state + bool show_demo_window = true; + bool show_another_window = false; + float clear_color[4] = { 0.45f, 0.55f, 0.60f, 1.00f }; + + // Main loop + bool done = false; + while (!done) + { + @autoreleasepool + { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + // [If using SDL_MAIN_USE_CALLBACKS: call ImGui_ImplSDL3_ProcessEvent() from your SDL_AppEvent() function] + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL3_ProcessEvent(&event); + if (event.type == SDL_EVENT_QUIT) + done = true; + if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window)) + done = true; + } + + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppIterate() function] + if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) + { + SDL_Delay(10); + continue; + } + + int width, height; + SDL_GetWindowSizeInPixels(window, &width, &height); + + layer.drawableSize = CGSizeMake(width, height); + id drawable = [layer nextDrawable]; + + uint64_t waitValue = (frameIndex >= FRAMES_IN_FLIGHT) ? (frameIndex - FRAMES_IN_FLIGHT + 1) : 0; + [sharedEvent waitUntilSignaledValue:waitValue timeoutMS:1000]; + [commandAllocators[frameInFlight] reset]; + [commandBuffers[frameInFlight] beginCommandBufferWithAllocator:commandAllocators[frameInFlight]]; + + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0] * clear_color[3], clear_color[1] * clear_color[3], clear_color[2] * clear_color[3], clear_color[3]); + renderPassDescriptor.colorAttachments[0].texture = drawable.texture; + renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; + renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + id renderEncoder = [commandBuffers[frameInFlight] renderCommandEncoderWithDescriptor:renderPassDescriptor]; + [renderEncoder pushDebugGroup:@"ImGui demo"]; + + // Start the Dear ImGui frame + ImGui_ImplMetal4_NewFrame(renderPassDescriptor, frameInFlight); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + ImDrawData* draw_data = ImGui::GetDrawData(); + ImGui_ImplMetal4_RenderDrawData(draw_data, commandBuffers[frameInFlight], renderEncoder); + + [renderEncoder popDebugGroup]; + [renderEncoder endEncoding]; + [commandBuffers[frameInFlight] endCommandBuffer]; + + id to_commit[] = { commandBuffers[frameInFlight] }; + + [commandQueue waitForDrawable:drawable]; + [commandQueue commit:to_commit count:1]; + + [commandQueue signalEvent:sharedEvent value:frameIndex + 1]; + [commandQueue signalDrawable:drawable]; + [drawable present]; + + frameIndex++; + frameInFlight = (frameInFlight + 1) % FRAMES_IN_FLIGHT; + } + } + + // Cleanup + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppQuit() function] + ImGui_ImplMetal4_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImGui::DestroyContext(); + + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +}