Skip to content

Instantly share code, notes, and snippets.

@xslattery
Last active October 1, 2018 13:24
Show Gist options
  • Select an option

  • Save xslattery/31f7d8e43c284c51c19866c3044f66de to your computer and use it in GitHub Desktop.

Select an option

Save xslattery/31f7d8e43c284c51c19866c3044f66de to your computer and use it in GitHub Desktop.
This is an example of manually driving a MacOS Metal application with a loop. Including events and rendering.
//
// platform_macos_metal_example.mm
// macos-platform-metal-example
//
#import <Cocoa/Cocoa.h>
#import <MetalKit/MetalKit.h>
bool running = true;
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end
@implementation AppDelegate
- (id)init {
if(self = [super init]) {
id menubar = [NSMenu new];
[NSApp setMainMenu:menubar];
id appMenuItem = [NSMenuItem new];
[menubar addItem:appMenuItem];
id appMenu = [NSMenu new];
[appMenuItem setSubmenu:appMenu];
id appName = [[NSProcessInfo processInfo] processName];
id quitTitle = [@"Quit " stringByAppendingString:appName];
id quitMenuItem = [[NSMenuItem alloc] initWithTitle:quitTitle action:@selector(terminate:) keyEquivalent:@"q"];
[appMenu addItem:quitMenuItem];
}
return self;
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { return YES; }
- (void)applicationWillFinishLaunching:(NSNotification *)notification {}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {}
- (void)applicationWillTerminate:(NSNotification *)aNotification { running = false; }
@end
/////////////////////////////
@interface WindowDelegate : NSObject<NSWindowDelegate>
@end
@implementation WindowDelegate
- (void)windowWillClose:(id)sender { running = false; }
- (NSSize)windowWillResize: (NSWindow*)window toSize:(NSSize)frameSize { return frameSize; }
@end
///////////////////////
@interface MetalView : MTKView
@end
@implementation MetalView
- (id)initWithFrame:(NSRect)size {
if(self = [super initWithFrame:size]) {
// NOTE(Xavier): 2018-10-1 This is required for mouseEntered/Exited to work
// Create a tracking area to keep track of the mouse movements and events:
NSTrackingAreaOptions options = (NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved);
NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil];
[self addTrackingArea:area];
}
return self;
}
- (BOOL)acceptsFirstResponder { return YES; }
- (BOOL)acceptsFirstMouse:(NSEvent *)event { return YES; }
// NOTE(Xavier): 2018-10-1
// Having these prevent a beep but are not necessary
// if the main loop handles the event.
- (void)flagsChanged:(NSEvent*)event {}
- (void)keyDown:(NSEvent*)event {}
- (void)keyUp:(NSEvent*)event {}
- (void)mouseDown:(NSEvent *)event {}
- (void)mouseUp:(NSEvent *)event {}
- (void)mouseDragged:(NSEvent *)event {}
- (void)rightMouseDown:(NSEvent *)event {}
- (void)rightMouseUp:(NSEvent *)event {}
- (void)rightMouseDragged:(NSEvent *)event {}
- (void)otherMouseDown:(NSEvent *)event {}
- (void)otherMouseUp:(NSEvent *)event {}
- (void)otherMouseDragged:(NSEvent *)event {}
- (void)mouseMoved:(NSEvent *)event {}
- (void)mouseEntered:(NSEvent *)event {}
- (void)mouseExited:(NSEvent *)event {}
- (void)scrollWheel:(NSEvent *)event {}
@end
/////////////////////////////////////////////////
int main(int argc, const char * argv[]) {
[NSApplication sharedApplication];
[NSApp setDelegate:[AppDelegate new]];
[NSApp activateIgnoringOtherApps:YES];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp finishLaunching];
NSRect contentSize = NSMakeRect(500.0, 500.0, 640.0, 480.0);
NSUInteger windowStyleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
NSWindow *window = [[NSWindow alloc] initWithContentRect:contentSize styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:YES];
window.backgroundColor = [NSColor whiteColor];
window.title = @"MyBareMetalApp";
[window setDelegate:[WindowDelegate new]];
[window makeKeyAndOrderFront:nil];
MetalView *metalView = [[MetalView alloc] initWithFrame:contentSize];
[window setContentView:metalView];
[window makeFirstResponder:metalView];
metalView.device = MTLCreateSystemDefaultDevice();
metalView.preferredFramesPerSecond = 30;
metalView.framebufferOnly = YES;
metalView.sampleCount = 1;
metalView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
metalView.depthStencilPixelFormat = MTLPixelFormatDepth32Float;
id<MTLDevice> device = metalView.device;
id<MTLCommandQueue> commandQueue = [device newCommandQueue];
running = true;
while (running) {
NSEvent* event;
do {
event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES];
switch ([event type]) {
case NSEventTypeFlagsChanged: { [NSApp sendEvent:event]; } break;
case NSEventTypeKeyDown: { [NSApp sendEvent:event]; } break;
case NSEventTypeKeyUp: { [NSApp sendEvent:event]; } break;
case NSEventTypeLeftMouseDown: { [NSApp sendEvent:event]; } break;
case NSEventTypeLeftMouseUp: { [NSApp sendEvent:event]; } break;
case NSEventTypeLeftMouseDragged: { [NSApp sendEvent:event]; } break;
case NSEventTypeRightMouseDown: { [NSApp sendEvent:event]; } break;
case NSEventTypeRightMouseUp: { [NSApp sendEvent:event]; } break;
case NSEventTypeRightMouseDragged: { [NSApp sendEvent:event]; } break;
case NSEventTypeOtherMouseDown: { [NSApp sendEvent:event]; } break;
case NSEventTypeOtherMouseUp: { [NSApp sendEvent:event]; } break;
case NSEventTypeOtherMouseDragged: { [NSApp sendEvent:event]; } break;
case NSEventTypeMouseMoved: { [NSApp sendEvent:event]; } break;
case NSEventTypeMouseEntered: { [NSApp sendEvent:event]; } break;
case NSEventTypeMouseExited: { [NSApp sendEvent:event]; } break;
case NSEventTypeScrollWheel: { [NSApp sendEvent:event]; } break;
default: { [NSApp sendEvent:event]; } break;
}
} while (event != nil);
id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
commandBuffer.label = @"MyCommand";
MTLRenderPassDescriptor *renderPassDescriptor = metalView.currentRenderPassDescriptor;
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 1.0, 0.0, 1.0);
if(renderPassDescriptor != nil) {
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
renderEncoder.label = @"MyRenderEncoder";
[renderEncoder endEncoding];
[commandBuffer presentDrawable:metalView.currentDrawable];
}
[commandBuffer commit];
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment