Skip to content

Commit

Permalink
Introduced a new Metal renderer implementation and a new example illu…
Browse files Browse the repository at this point in the history
…strating usage of Metal on macOS and iOS (partially addresses ocornut#1873)
  • Loading branch information
warrenm committed Jul 5, 2018
1 parent deb7aa2 commit d64157e
Show file tree
Hide file tree
Showing 18 changed files with 1,678 additions and 2 deletions.
6 changes: 6 additions & 0 deletions examples/example_apple_metal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# iOS / OSX Metal example

## Introduction

This example shows how to render ImGui with Metal. It is based on the cross-platform game template provided with Xcode as of Xcode 9.

19 changes: 19 additions & 0 deletions examples/example_apple_metal/Shared/AppDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

#import <TargetConditionals.h>

#if TARGET_OS_IPHONE

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

#else

#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate>
@end

#endif
12 changes: 12 additions & 0 deletions examples/example_apple_metal/Shared/AppDelegate.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

#import "AppDelegate.h"

@implementation AppDelegate

#if TARGET_OS_OSX
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return YES;
}
#endif

@end
9 changes: 9 additions & 0 deletions examples/example_apple_metal/Shared/Renderer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

#import <MetalKit/MetalKit.h>

@interface Renderer : NSObject <MTKViewDelegate>

-(nonnull instancetype)initWithView:(nonnull MTKView *)view;

@end

132 changes: 132 additions & 0 deletions examples/example_apple_metal/Shared/Renderer.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@

#import "Renderer.h"
#import <Metal/Metal.h>

#include "imgui.h"
#include "imgui_impl_metal.h"

#if TARGET_OS_OSX
#include "imgui_impl_osx.h"
#endif

@interface Renderer ()
@property (nonatomic, strong) id <MTLDevice> device;
@property (nonatomic, strong) id <MTLCommandQueue> commandQueue;
@end

@implementation Renderer

-(nonnull instancetype)initWithView:(nonnull MTKView *)view;
{
self = [super init];
if(self)
{
_device = view.device;
_commandQueue = [_device newCommandQueue];

IMGUI_CHECKVERSION();
ImGui::CreateContext();
(void)ImGui::GetIO();

ImGui_ImplMetal_Init(_device);

ImGui::StyleColorsDark();
}

return self;
}

- (void)drawInMTKView:(MTKView *)view
{
ImGuiIO &io = ImGui::GetIO();
io.DisplaySize.x = view.bounds.size.width;
io.DisplaySize.y = view.bounds.size.height;

#if TARGET_OS_OSX
CGFloat framebufferScale = view.window.screen.backingScaleFactor ?: NSScreen.mainScreen.backingScaleFactor;
#else
CGFloat framebufferScale = view.window.screen.scale ?: UIScreen.mainScreen.scale;
#endif
io.DisplayFramebufferScale = ImVec2(framebufferScale, framebufferScale);

io.DeltaTime = 1 / float(view.preferredFramesPerSecond ?: 60);

id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];

static bool show_demo_window = true;
static bool show_another_window = false;
static float clear_color[4] = { 0.28f, 0.36f, 0.5f, 1.0f };

MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
if(renderPassDescriptor != nil)
{
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0], clear_color[1], clear_color[2], clear_color[3]);

// Here, you could do additional rendering work, including other passes as necessary.

id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];

[renderEncoder pushDebugGroup:@"Draw ImGui"];

ImGui_ImplMetal_NewFrame(renderPassDescriptor);
#if TARGET_OS_OSX
ImGui_ImplOSX_NewFrame(view);
#endif
ImGui::NewFrame();

{
static float f = 0.0f;
static int counter = 0;
ImGui::Text("Hello, world!"); // Display some text (you can use a format string too)
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

ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our windows open/close state
ImGui::Checkbox("Another Window", &show_another_window);

if (ImGui::Button("Button")) // Buttons return true when clicked (NB: 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 / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
}

// 2. Show another simple window. In most cases you will use an explicit Begin/End pair to name your windows.
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();
}

// 3. Show the ImGui demo window. Most of the sample code is in ImGui::ShowDemoWindow(). Read its code to learn more about Dear ImGui!
if (show_demo_window)
{
// Normally user code doesn't need/want to call this because positions are saved in .ini file anyway.
// Here we just want to make the demo initial state a bit more friendly!
ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiCond_FirstUseEver);
ImGui::ShowDemoWindow(&show_demo_window);
}

ImGui::Render();
ImDrawData *drawData = ImGui::GetDrawData();
ImGui_ImplMetal_RenderDrawData(drawData, commandBuffer, renderEncoder);

[renderEncoder popDebugGroup];

[renderEncoder endEncoding];

[commandBuffer presentDrawable:view.currentDrawable];
}

[commandBuffer commit];
}

- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size
{
}

@end
20 changes: 20 additions & 0 deletions examples/example_apple_metal/Shared/ViewController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

#import <Metal/Metal.h>
#import <MetalKit/MetalKit.h>
#import "Renderer.h"

#if TARGET_OS_IPHONE

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@end

#else

#import <Cocoa/Cocoa.h>

@interface ViewController : NSViewController
@end

#endif
130 changes: 130 additions & 0 deletions examples/example_apple_metal/Shared/ViewController.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@

#import "ViewController.h"
#import "Renderer.h"
#include "imgui.h"

#if TARGET_OS_OSX
#include "imgui_impl_osx.h"
#endif

@interface ViewController ()
@property (nonatomic, readonly) MTKView *mtkView;
@property (nonatomic, strong) Renderer *renderer;
@end

@implementation ViewController

- (MTKView *)mtkView {
return (MTKView *)self.view;
}

- (void)viewDidLoad
{
[super viewDidLoad];

self.mtkView.device = MTLCreateSystemDefaultDevice();

if (!self.mtkView.device) {
NSLog(@"Metal is not supported");
abort();
}

self.renderer = [[Renderer alloc] initWithView:self.mtkView];

[self.renderer mtkView:self.mtkView drawableSizeWillChange:self.mtkView.bounds.size];

self.mtkView.delegate = self.renderer;

#if TARGET_OS_OSX
// Add a tracking area in order to receive mouse events whenever the mouse is within the bounds of our view
NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
options:NSTrackingMouseMoved | NSTrackingInVisibleRect | NSTrackingActiveAlways
owner:self
userInfo:nil];
[self.view addTrackingArea:trackingArea];

// If we want to receive key events, we either need to be in the responder chain of the key view,
// or else we can install a local monitor. The consequence of this heavy-handed approach is that
// we receive events for all controls, not just ImGui widgets. If we had native controls in our
// window, we'd want to be much more careful than just ingesting the complete event stream, though
// we do make an effort to be good citizens by passing along events when ImGui doesn't want to capture.
NSEventMask eventMask = NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged | NSEventTypeScrollWheel;
[NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^NSEvent * _Nullable(NSEvent *event) {
BOOL wantsCapture = ImGui_ImplOSX_HandleEvent(event, self.view);
if (event.type == NSEventTypeKeyDown && wantsCapture) {
return nil;
} else {
return event;
}

}];

ImGui_ImplOSX_Init();
#endif
}

#if TARGET_OS_OSX

- (void)mouseMoved:(NSEvent *)event {
ImGui_ImplOSX_HandleEvent(event, self.view);
}

- (void)mouseDown:(NSEvent *)event {
ImGui_ImplOSX_HandleEvent(event, self.view);
}

- (void)mouseUp:(NSEvent *)event {
ImGui_ImplOSX_HandleEvent(event, self.view);
}

- (void)mouseDragged:(NSEvent *)event {
ImGui_ImplOSX_HandleEvent(event, self.view);
}

- (void)scrollWheel:(NSEvent *)event {
ImGui_ImplOSX_HandleEvent(event, self.view);
}

#elif TARGET_OS_IOS

// This touch mapping is super cheesy/hacky. We treat any touch on the screen
// as if it were a depressed left mouse button, and we don't bother handling
// multitouch correctly at all. This causes the "cursor" to behave very erratically
// when there are multiple active touches. But for demo purposes, single-touch
// interaction actually works surprisingly well.
- (void)updateIOWithTouchEvent:(UIEvent *)event {
UITouch *anyTouch = event.allTouches.anyObject;
CGPoint touchLocation = [anyTouch locationInView:self.view];
ImGuiIO &io = ImGui::GetIO();
io.MousePos = ImVec2(touchLocation.x, touchLocation.y);

BOOL hasActiveTouch = NO;
for (UITouch *touch in event.allTouches) {
if (touch.phase != UITouchPhaseEnded && touch.phase != UITouchPhaseCancelled) {
hasActiveTouch = YES;
break;
}
}
io.MouseDown[0] = hasActiveTouch;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self updateIOWithTouchEvent:event];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self updateIOWithTouchEvent:event];
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self updateIOWithTouchEvent:event];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self updateIOWithTouchEvent:event];
}

#endif

@end

23 changes: 23 additions & 0 deletions examples/example_apple_metal/Shared/main.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

#import <TargetConditionals.h>

#if TARGET_OS_IPHONE

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

#else

#import <Cocoa/Cocoa.h>

int main(int argc, const char * argv[]) {
return NSApplicationMain(argc, argv);
}

#endif
Loading

0 comments on commit d64157e

Please sign in to comment.