Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduced a new Metal renderer implementation and a new example illustrating usage of Metal on macOS and iOS #1929

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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