Files
imgui/examples/example_apple_cpu/main.mm
T
2026-03-15 12:33:32 +01:00

425 lines
13 KiB
Plaintext

// 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 <Cocoa/Cocoa.h>
#include "imgui.h"
#include "imgui_impl_cpu.h"
#include "imgui_impl_osx.h"
#include <chrono>
#include <cmath>
#include <string.h>
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<unsigned char> 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<double, std::milli>(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 <NSApplicationDelegate>
@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;
}