// Dear ImGui: standalone macOS example using Cocoa + CPU framebuffer rendering. // 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 #import #include "imgui.h" #include "imgui_impl_cpu.h" #include "imgui_impl_osx.h" #include #include #include struct ExampleTimingHistory { double Samples[120]; int Count; int Offset; ExampleTimingHistory() : Count(0), Offset(0) { memset(Samples, 0, sizeof(Samples)); } void AddSample(double value_ms) { Samples[Offset] = value_ms; Offset = (Offset + 1) % IM_ARRAYSIZE(Samples); if (Count < IM_ARRAYSIZE(Samples)) Count++; } double GetAverageMs() const { if (Count == 0) return 0.0; double total = 0.0; for (int i = 0; i < Count; i++) total += Samples[i]; return total / (double)Count; } }; struct ExampleFramebuffer { ImVector Storage; ImGui_ImplCPU_Framebuffer CPU; CGColorSpaceRef ColorSpace; CGDataProviderRef DataProvider; CGImageRef Image; ExampleFramebuffer() : ColorSpace(nullptr), DataProvider(nullptr), Image(nullptr) { CPU.Pixels = nullptr; CPU.Width = 0; CPU.Height = 0; CPU.Pitch = 0; CPU.Format = nullptr; } }; static double ExampleGetTimeMs() { typedef std::chrono::steady_clock Clock; return std::chrono::duration(Clock::now().time_since_epoch()).count(); } static void DestroyFramebuffer(ExampleFramebuffer* framebuffer) { if (framebuffer->Image != nullptr) { CGImageRelease(framebuffer->Image); framebuffer->Image = nullptr; } if (framebuffer->DataProvider != nullptr) { CGDataProviderRelease(framebuffer->DataProvider); framebuffer->DataProvider = nullptr; } if (framebuffer->ColorSpace != nullptr) { CGColorSpaceRelease(framebuffer->ColorSpace); framebuffer->ColorSpace = nullptr; } framebuffer->CPU.Pixels = nullptr; framebuffer->CPU.Width = 0; framebuffer->CPU.Height = 0; framebuffer->CPU.Pitch = 0; framebuffer->Storage.clear(); } static ImU32 PackBGRA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) return ((ImU32)b << 24) | ((ImU32)g << 16) | ((ImU32)r << 8) | (ImU32)a; #else return (ImU32)b | ((ImU32)g << 8) | ((ImU32)r << 16) | ((ImU32)a << 24); #endif } static bool RecreateFramebuffer(ExampleFramebuffer* framebuffer, int width, int height) { DestroyFramebuffer(framebuffer); framebuffer->Storage.resize(width * height * 4); framebuffer->CPU.Pixels = framebuffer->Storage.Data; framebuffer->CPU.Width = width; framebuffer->CPU.Height = height; framebuffer->CPU.Pitch = width * 4; framebuffer->CPU.Format = ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_BGRA8888); if (framebuffer->CPU.Pixels == nullptr) return false; framebuffer->ColorSpace = CGColorSpaceCreateDeviceRGB(); if (framebuffer->ColorSpace == nullptr) { DestroyFramebuffer(framebuffer); return false; } framebuffer->DataProvider = CGDataProviderCreateWithData(nullptr, framebuffer->CPU.Pixels, (size_t)framebuffer->CPU.Pitch * (size_t)framebuffer->CPU.Height, nullptr); if (framebuffer->DataProvider == nullptr) { DestroyFramebuffer(framebuffer); return false; } framebuffer->Image = CGImageCreate((size_t)framebuffer->CPU.Width, (size_t)framebuffer->CPU.Height, 8, 32, (size_t)framebuffer->CPU.Pitch, framebuffer->ColorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, framebuffer->DataProvider, nullptr, false, kCGRenderingIntentDefault); if (framebuffer->Image == nullptr) { DestroyFramebuffer(framebuffer); return false; } return true; } static void ClearFramebuffer(ImGui_ImplCPU_Framebuffer* framebuffer, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { const ImU32 pixel = PackBGRA(r, g, b, a); for (int y = 0; y < framebuffer->Height; y++) { ImU32* dst = (ImU32*)(void*)(framebuffer->Pixels + (size_t)y * (size_t)framebuffer->Pitch); for (int x = 0; x < framebuffer->Width; x++) dst[x] = pixel; } } //----------------------------------------------------------------------------------- // AppView //----------------------------------------------------------------------------------- @interface AppView : NSView - (void)shutdownImGui; @end @implementation AppView { ExampleFramebuffer _framebuffer; ExampleTimingHistory _rasterTimes; ExampleTimingHistory _presentTimes; BOOL _imguiInitialized; BOOL _frameScheduled; } - (instancetype)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; return self; } - (BOOL)isFlipped { return YES; } - (void)viewDidMoveToWindow { [super viewDidMoveToWindow]; if (self.window != nil && !_imguiInitialized) [self initializeImGui]; } - (void)dealloc { [self shutdownImGui]; } - (void)initializeImGui { IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls ImGui::StyleColorsDark(); ImGui_ImplOSX_Init(self); ImGui_ImplCPU_Init(); _imguiInitialized = YES; [self scheduleNextFrameAfter:0.0]; } - (void)shutdownImGui { if (!_imguiInitialized) return; DestroyFramebuffer(&_framebuffer); ImGui_ImplCPU_Shutdown(); ImGui_ImplOSX_Shutdown(); ImGui::DestroyContext(); _imguiInitialized = NO; } // Queue the next draw on the main loop instead of pacing to display refresh. - (void)scheduleNextFrameAfter:(double)delay_seconds { if (_frameScheduled) return; _frameScheduled = YES; dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay_seconds * (double)NSEC_PER_SEC)); dispatch_after(when, dispatch_get_main_queue(), ^{ self->_frameScheduled = NO; [self setNeedsDisplay:YES]; }); } - (void)renderFrame { if (!_imguiInitialized) return; if (self.window == nil || [self.window isMiniaturized]) { [self scheduleNextFrameAfter:0.01]; return; } const CGFloat scale = self.window.backingScaleFactor > 0.0 ? self.window.backingScaleFactor : 1.0; const int logical_w = (int)llround(self.bounds.size.width); const int logical_h = (int)llround(self.bounds.size.height); const int framebuffer_w = (int)llround(self.bounds.size.width * scale); const int framebuffer_h = (int)llround(self.bounds.size.height * scale); if (framebuffer_w <= 0 || framebuffer_h <= 0) { [self scheduleNextFrameAfter:0.01]; return; } if (_framebuffer.CPU.Width != framebuffer_w || _framebuffer.CPU.Height != framebuffer_h) if (!RecreateFramebuffer(&_framebuffer, framebuffer_w, framebuffer_h)) return; // Start the Dear ImGui frame ImGui_ImplCPU_NewFrame(); ImGui_ImplOSX_NewFrame(self); ImGui::NewFrame(); // Our state static bool show_demo_window = true; static bool show_another_window = false; static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); // 1. Show the big demo window if (show_demo_window) ImGui::ShowDemoWindow(&show_demo_window); // 2. Show a simple window that we create ourselves. { static float f = 0.0f; static int counter = 0; ImGuiIO& io = ImGui::GetIO(); ImGui::Begin("Hello, world!"); ImGui::Text("This is some useful text."); ImGui::Checkbox("Demo Window", &show_demo_window); ImGui::Checkbox("Another Window", &show_another_window); ImGui::SliderFloat("float", &f, 0.0f, 1.0f); ImGui::ColorEdit3("clear color", (float*)&clear_color); if (ImGui::Button("Button")) counter++; ImGui::SameLine(); ImGui::Text("counter = %d", counter); ImGui::Text("Raster %.3f ms | Present %.3f ms", _rasterTimes.GetAverageMs(), _presentTimes.GetAverageMs()); ImGui::Text("Window %d x %d | Framebuffer %d x %d (scale %.2f)", logical_w, logical_h, framebuffer_w, framebuffer_h, scale); 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); ImGui::Text("Hello from another window!"); if (ImGui::Button("Close Me")) show_another_window = false; ImGui::End(); } // Rendering ImGui::Render(); const double raster_start_ms = ExampleGetTimeMs(); ClearFramebuffer(&_framebuffer.CPU, (unsigned char)(clear_color.x * 255), (unsigned char)(clear_color.y * 255), (unsigned char)(clear_color.z * 255), (unsigned char)(clear_color.w * 255)); ImGui_ImplCPU_RenderDrawData(ImGui::GetDrawData(), &_framebuffer.CPU); _rasterTimes.AddSample(ExampleGetTimeMs() - raster_start_ms); } - (void)drawRect:(NSRect)dirtyRect { IM_UNUSED(dirtyRect); [self renderFrame]; if (_framebuffer.Image != nullptr) { CGContextRef context = NSGraphicsContext.currentContext.CGContext; // CGImage draws with a bottom-up raster convention here, while the CPU framebuffer is top-down. const double present_start_ms = ExampleGetTimeMs(); CGContextSaveGState(context); CGContextTranslateCTM(context, 0.0, self.bounds.size.height); CGContextScaleCTM(context, 1.0, -1.0); CGContextDrawImage(context, CGRectMake(0.0, 0.0, self.bounds.size.width, self.bounds.size.height), _framebuffer.Image); CGContextRestoreGState(context); _presentTimes.AddSample(ExampleGetTimeMs() - present_start_ms); } [self scheduleNextFrameAfter:0.0]; } @end //----------------------------------------------------------------------------------- // AppDelegate //----------------------------------------------------------------------------------- @interface AppDelegate : NSObject @property (nonatomic, readonly) NSWindow* window; @property (nonatomic, readonly) AppView* view; @end @implementation AppDelegate { NSWindow* _window; AppView* _view; } @synthesize window = _window; @synthesize view = _view; - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)application { IM_UNUSED(application); return YES; } - (void)setupMenu { NSMenu* main_menu_bar = [[NSMenu alloc] init]; NSMenu* app_menu = [[NSMenu alloc] initWithTitle:@"Dear ImGui macOS+CPU Example"]; NSMenuItem* menu_item = [app_menu addItemWithTitle:@"Quit Dear ImGui macOS+CPU Example" action:@selector(terminate:) keyEquivalent:@"q"]; [menu_item setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; menu_item = [[NSMenuItem alloc] init]; [menu_item setSubmenu:app_menu]; [main_menu_bar addItem:menu_item]; [NSApp setMainMenu:main_menu_bar]; } - (void)applicationDidFinishLaunching:(NSNotification*)notification { IM_UNUSED(notification); [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; [self setupMenu]; NSRect view_rect = NSMakeRect(100.0, 100.0, 1280.0, 720.0); _window = [[NSWindow alloc] initWithContentRect:view_rect styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable backing:NSBackingStoreBuffered defer:NO]; [_window setTitle:@"Dear ImGui macOS+CPU Example"]; [_window setAcceptsMouseMovedEvents:YES]; [_window setOpaque:YES]; _view = [[AppView alloc] initWithFrame:_window.contentView.bounds]; [_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; [_window setContentView:_view]; [_window makeKeyAndOrderFront:NSApp]; [NSApp activateIgnoringOtherApps:YES]; } - (void)applicationWillTerminate:(NSNotification*)notification { IM_UNUSED(notification); [_view shutdownImGui]; } @end int main(int argc, char** argv) { IM_UNUSED(argc); IM_UNUSED(argv); @autoreleasepool { NSApplication* application = [NSApplication sharedApplication]; AppDelegate* delegate = [AppDelegate new]; [application setDelegate:delegate]; [application run]; } return 0; }