diff --git a/app/internal/wm/gl_macos.go b/app/internal/wm/gl_macos.go index 10929bb2..40d16e21 100644 --- a/app/internal/wm/gl_macos.go +++ b/app/internal/wm/gl_macos.go @@ -5,6 +5,8 @@ package wm import ( + "errors" + "gioui.org/gpu" "gioui.org/internal/gl" ) @@ -15,8 +17,9 @@ import ( #include #include -__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLView(void); -__attribute__ ((visibility ("hidden"))) CFTypeRef gio_contextForView(CFTypeRef viewRef); +__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLContext(void); +__attribute__ ((visibility ("hidden"))) void gio_prepareContext(void); +__attribute__ ((visibility ("hidden"))) void gio_setContextView(CFTypeRef ctx, CFTypeRef view); __attribute__ ((visibility ("hidden"))) void gio_makeCurrentContext(CFTypeRef ctx); __attribute__ ((visibility ("hidden"))) void gio_flushContextBuffer(CFTypeRef ctx); __attribute__ ((visibility ("hidden"))) void gio_clearCurrentContext(void); @@ -26,20 +29,23 @@ __attribute__ ((visibility ("hidden"))) void gio_unlockContext(CFTypeRef ctxRef) import "C" type context struct { - c *gl.Functions - ctx C.CFTypeRef - view C.CFTypeRef -} - -func init() { - viewFactory = func() C.CFTypeRef { - return C.gio_createGLView() - } + c *gl.Functions + ctx C.CFTypeRef + view C.CFTypeRef + prepared bool } func newContext(w *window) (*context, error) { view := w.contextView() - ctx := C.gio_contextForView(view) + ctx := C.gio_createGLContext() + if ctx == 0 { + return nil, errors.New("gl: failed to create NSOpenGLContext") + } + // [NSOpenGLContext setView] must run on the main thread. Fortunately, + // newContext is only called during a [NSView draw] on the main thread. + w.w.Func(func() { + C.gio_setContextView(ctx, view) + }) c := &context{ ctx: ctx, view: view, @@ -52,14 +58,9 @@ func (c *context) API() gpu.API { } func (c *context) Release() { - c.Lock() - defer c.Unlock() C.gio_clearCurrentContext() - // We could release the context with [view clearGLContext] - // and rely on [view openGLContext] auto-creating a new context. - // However that second context is not properly set up by - // OpenGLContextView, so we'll stay on the safe side and keep - // the first context around. + C.CFRelease(c.ctx) + c.ctx = 0 } func (c *context) Present() error { @@ -80,6 +81,10 @@ func (c *context) MakeCurrent() error { c.Lock() defer c.Unlock() C.gio_makeCurrentContext(c.ctx) + if !c.prepared { + c.prepared = true + C.gio_prepareContext() + } return nil } diff --git a/app/internal/wm/gl_macos.m b/app/internal/wm/gl_macos.m index 576aa40e..4f29208b 100644 --- a/app/internal/wm/gl_macos.m +++ b/app/internal/wm/gl_macos.m @@ -9,91 +9,21 @@ #include #include "_cgo_export.h" -static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) { - NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil]; - if (!event.hasPreciseScrollingDeltas) { - // dx and dy are in rows and columns. - dx *= 10; - dy *= 10; - } - gio_onMouse((__bridge CFTypeRef)view, typ, [NSEvent pressedMouseButtons], p.x, p.y, dx, dy, [event timestamp], [event modifierFlags]); -} - -@interface GioView : NSOpenGLView +@interface GioGLContext : NSOpenGLContext @end -@implementation GioView -- (instancetype)initWithFrame:(NSRect)frameRect - pixelFormat:(NSOpenGLPixelFormat *)format { - return [super initWithFrame:frameRect pixelFormat:format]; +@implementation GioGLContext +- (void) notifyUpdate:(NSNotification*)notification { + CGLLockContext([self CGLContextObj]); + [self update]; + CGLUnlockContext([self CGLContextObj]); } -- (void)prepareOpenGL { - [super prepareOpenGL]; - // Bind a default VBA to emulate OpenGL ES 2. - GLuint defVBA; - glGenVertexArrays(1, &defVBA); - glBindVertexArray(defVBA); - glEnable(GL_FRAMEBUFFER_SRGB); -} -- (BOOL)isFlipped { - return YES; -} -- (void)update { - [super update]; - [self setNeedsDisplay:YES]; -} -- (void)drawRect:(NSRect)r { - gio_onDraw((__bridge CFTypeRef)self); -} -- (void)mouseDown:(NSEvent *)event { - handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0); -} -- (void)mouseUp:(NSEvent *)event { - handleMouse(self, event, GIO_MOUSE_UP, 0, 0); -} -- (void)middleMouseDown:(NSEvent *)event { - handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0); -} -- (void)middletMouseUp:(NSEvent *)event { - handleMouse(self, event, GIO_MOUSE_UP, 0, 0); -} -- (void)rightMouseDown:(NSEvent *)event { - handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0); -} -- (void)rightMouseUp:(NSEvent *)event { - handleMouse(self, event, GIO_MOUSE_UP, 0, 0); -} -- (void)mouseMoved:(NSEvent *)event { - handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0); -} -- (void)mouseDragged:(NSEvent *)event { - handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0); -} -- (void)scrollWheel:(NSEvent *)event { - CGFloat dx = -event.scrollingDeltaX; - CGFloat dy = -event.scrollingDeltaY; - handleMouse(self, event, GIO_MOUSE_SCROLL, dx, dy); -} -- (void)keyDown:(NSEvent *)event { - NSString *keys = [event charactersIgnoringModifiers]; - gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], true); - [self interpretKeyEvents:[NSArray arrayWithObject:event]]; -} -- (void)keyUp:(NSEvent *)event { - NSString *keys = [event charactersIgnoringModifiers]; - gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], false); -} -- (void)insertText:(id)string { - const char *utf8 = [string UTF8String]; - gio_onText((__bridge CFTypeRef)self, (char *)utf8); -} -- (void)doCommandBySelector:(SEL)sel { - // Don't pass commands up the responder chain. - // They will end up in a beep. +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end -CFTypeRef gio_createGLView(void) { +CFTypeRef gio_createGLContext(void) { @autoreleasepool { NSOpenGLPixelFormatAttribute attr[] = { NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, @@ -105,43 +35,54 @@ CFTypeRef gio_createGLView(void) { NSOpenGLPFAAllowOfflineRenderers, 0 }; - id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; + NSOpenGLPixelFormat *pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; - NSRect frame = NSMakeRect(0, 0, 0, 0); - GioView* view = [[GioView alloc] initWithFrame:frame pixelFormat:pixFormat]; - - [view setWantsBestResolutionOpenGLSurface:YES]; - [view setWantsLayer:YES]; // The default in Mojave. - - return CFBridgingRetain(view); + GioGLContext *ctx = [[GioGLContext alloc] initWithFormat:pixFormat shareContext: nil]; + return CFBridgingRetain(ctx); } } -void gio_setNeedsDisplay(CFTypeRef viewRef) { - NSOpenGLView *view = (__bridge NSOpenGLView *)viewRef; - [view setNeedsDisplay:YES]; -} - -CFTypeRef gio_contextForView(CFTypeRef viewRef) { - NSOpenGLView *view = (__bridge NSOpenGLView *)viewRef; - return (__bridge CFTypeRef)view.openGLContext; +void gio_setContextView(CFTypeRef ctxRef, CFTypeRef viewRef) { + GioGLContext *ctx = (__bridge GioGLContext *)ctxRef; + NSView *view = (__bridge NSView *)viewRef; + [ctx setView:view]; + [[NSNotificationCenter defaultCenter] addObserver:ctx + selector:@selector(notifyUpdate:) + name:NSViewGlobalFrameDidChangeNotification + object:view]; } void gio_clearCurrentContext(void) { - [NSOpenGLContext clearCurrentContext]; + @autoreleasepool { + [NSOpenGLContext clearCurrentContext]; + } } void gio_makeCurrentContext(CFTypeRef ctxRef) { - NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef; - [ctx makeCurrentContext]; + @autoreleasepool { + NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef; + [ctx makeCurrentContext]; + } } void gio_lockContext(CFTypeRef ctxRef) { - NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef; - CGLLockContext([ctx CGLContextObj]); + @autoreleasepool { + NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef; + CGLLockContext([ctx CGLContextObj]); + } } void gio_unlockContext(CFTypeRef ctxRef) { - NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef; - CGLUnlockContext([ctx CGLContextObj]); + @autoreleasepool { + NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef; + CGLUnlockContext([ctx CGLContextObj]); + } +} + +void gio_prepareContext(void) { + // Bind a default VBA to emulate OpenGL ES 2. + GLuint defVBA; + glGenVertexArrays(1, &defVBA); + glBindVertexArray(defVBA); + glEnable(GL_FRAMEBUFFER_SRGB); } diff --git a/app/internal/wm/os_macos.go b/app/internal/wm/os_macos.go index b9266d83..4ad04b4f 100644 --- a/app/internal/wm/os_macos.go +++ b/app/internal/wm/os_macos.go @@ -42,6 +42,7 @@ __attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void); __attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length); __attribute__ ((visibility ("hidden"))) void gio_setNeedsDisplay(CFTypeRef viewRef); __attribute__ ((visibility ("hidden"))) void gio_toggleFullScreen(CFTypeRef windowRef); +__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight); __attribute__ ((visibility ("hidden"))) void gio_makeKeyAndOrderFront(CFTypeRef windowRef); __attribute__ ((visibility ("hidden"))) NSPoint gio_cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft); @@ -73,8 +74,6 @@ type window struct { // viewMap is the mapping from Cocoa NSViews to Go windows. var viewMap = make(map[C.CFTypeRef]*window) -var viewFactory func() C.CFTypeRef - // launched is closed when applicationDidFinishLaunching is called. var launched = make(chan struct{}) @@ -401,7 +400,7 @@ func NewWindow(win Callbacks, opts *Options) error { } func newWindow(opts *Options) (*window, error) { - view := viewFactory() + view := C.gio_createView() if view == 0 { return nil, errors.New("CreateWindow: failed to create view") } diff --git a/app/internal/wm/os_macos.m b/app/internal/wm/os_macos.m index 7980d532..89053fe6 100644 --- a/app/internal/wm/os_macos.m +++ b/app/internal/wm/os_macos.m @@ -42,6 +42,73 @@ } @end +static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) { + NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil]; + if (!event.hasPreciseScrollingDeltas) { + // dx and dy are in rows and columns. + dx *= 10; + dy *= 10; + } + gio_onMouse((__bridge CFTypeRef)view, typ, [NSEvent pressedMouseButtons], p.x, p.y, dx, dy, [event timestamp], [event modifierFlags]); +} + +@interface GioView : NSView +@end + +@implementation GioView +- (BOOL)isFlipped { + return YES; +} +- (void)drawRect:(NSRect)r { + gio_onDraw((__bridge CFTypeRef)self); +} +- (void)mouseDown:(NSEvent *)event { + handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0); +} +- (void)mouseUp:(NSEvent *)event { + handleMouse(self, event, GIO_MOUSE_UP, 0, 0); +} +- (void)middleMouseDown:(NSEvent *)event { + handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0); +} +- (void)middletMouseUp:(NSEvent *)event { + handleMouse(self, event, GIO_MOUSE_UP, 0, 0); +} +- (void)rightMouseDown:(NSEvent *)event { + handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0); +} +- (void)rightMouseUp:(NSEvent *)event { + handleMouse(self, event, GIO_MOUSE_UP, 0, 0); +} +- (void)mouseMoved:(NSEvent *)event { + handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0); +} +- (void)mouseDragged:(NSEvent *)event { + handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0); +} +- (void)scrollWheel:(NSEvent *)event { + CGFloat dx = -event.scrollingDeltaX; + CGFloat dy = -event.scrollingDeltaY; + handleMouse(self, event, GIO_MOUSE_SCROLL, dx, dy); +} +- (void)keyDown:(NSEvent *)event { + NSString *keys = [event charactersIgnoringModifiers]; + gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], true); + [self interpretKeyEvents:[NSArray arrayWithObject:event]]; +} +- (void)keyUp:(NSEvent *)event { + NSString *keys = [event charactersIgnoringModifiers]; + gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], false); +} +- (void)insertText:(id)string { + const char *utf8 = [string UTF8String]; + gio_onText((__bridge CFTypeRef)self, (char *)utf8); +} +- (void)doCommandBySelector:(SEL)sel { + // Don't pass commands up the responder chain. + // They will end up in a beep. +} +@end // Delegates are weakly referenced from their peers. Nothing // else holds a strong reference to our window delegate, so // keep a single global reference instead. @@ -86,6 +153,11 @@ CGFloat gio_getViewBackingScale(CFTypeRef viewRef) { return [view.window backingScaleFactor]; } +void gio_setNeedsDisplay(CFTypeRef viewRef) { + NSView *view = (__bridge NSView *)viewRef; + [view setNeedsDisplay:YES]; +} + void gio_hideCursor() { @autoreleasepool { [NSCursor hide]; @@ -229,6 +301,16 @@ void gio_setTitle(CFTypeRef windowRef, const char *title) { window.title = [NSString stringWithUTF8String: title]; } +CFTypeRef gio_createView(void) { + @autoreleasepool { + NSRect frame = NSMakeRect(0, 0, 0, 0); + GioView* view = [[GioView alloc] initWithFrame:frame]; + [view setWantsBestResolutionOpenGLSurface:YES]; + [view setWantsLayer:YES]; // The default in Mojave. + return CFBridgingRetain(view); + } +} + @implementation GioAppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; diff --git a/app/internal/wm/window.go b/app/internal/wm/window.go index 2801824c..965f5c6e 100644 --- a/app/internal/wm/window.go +++ b/app/internal/wm/window.go @@ -55,6 +55,10 @@ type FrameEvent struct { type Callbacks interface { SetDriver(d Driver) Event(e event.Event) + // Func runs a function during an Event. This is required for platforms + // that require coordination between the rendering goroutine and the system + // main thread. + Func(f func()) } type Context interface { diff --git a/app/window.go b/app/window.go index 8740f5b5..37931d8b 100644 --- a/app/window.go +++ b/app/window.go @@ -56,7 +56,8 @@ type Window struct { } type callbacks struct { - w *Window + w *Window + funcs chan func() } // queue is an event.Queue implementation that distributes system events @@ -104,6 +105,7 @@ func NewWindow(options ...Option) *Window { driverFuncs: make(chan func()), dead: make(chan struct{}), } + w.callbacks.funcs = make(chan func()) w.callbacks.w = w go w.run(opts) return w @@ -299,11 +301,22 @@ func (c *callbacks) SetDriver(d wm.Driver) { func (c *callbacks) Event(e event.Event) { select { case c.w.in <- e: - <-c.w.ack + for { + select { + case <-c.w.ack: + return + case f := <-c.funcs: + f() + } + } case <-c.w.dead: } } +func (c *callbacks) Func(f func()) { + c.funcs <- f +} + func (w *Window) waitAck() { // Send a dummy event; when it gets through we // know the application has processed the previous event.