Prepare NSView/OpenGL Canvas (to speed up rendering on [mac]OS[X]
authorRobin Gareus <robin@gareus.org>
Sun, 19 Mar 2017 21:40:58 +0000 (22:40 +0100)
committerRobin Gareus <robin@gareus.org>
Sun, 19 Mar 2017 21:49:17 +0000 (22:49 +0100)
This avoids Coregraphics (cairo_quartz_surface..) competely.
The openGL texture bypasses CG's slow argb_image and CGSColorMask
methods.

libs/canvas/canvas.cc
libs/canvas/canvas/canvas.h
libs/canvas/canvas/nsglview.h [new file with mode: 0644]
libs/canvas/nsglview.mm [new file with mode: 0644]
libs/canvas/wscript

index a72700b94af1707cc87a190bb4f3fa17e7a30e7a..70fd36e8b541921c8e115f2cc8a23d5f43508b4d 100644 (file)
 #include "canvas/scroll_group.h"
 #include "canvas/utils.h"
 
+#ifdef __APPLE__
+#include "canvas/nsglview.h"
+#endif
+
 using namespace std;
 using namespace ArdourCanvas;
 
@@ -390,11 +394,18 @@ GtkCanvas::GtkCanvas ()
        , current_tooltip_item (0)
        , tooltip_window (0)
        , _in_dtor (false)
+       , _nsglview (0)
 {
        /* these are the events we want to know about */
        add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK |
                    Gdk::SCROLL_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
                    Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
+
+#ifdef __APPLE__NotYetToDueGdkForeignViewMousePatch // XXX
+# ifndef __ppc__ // would need to flip RGBA <> RGBA
+       _nsglview = nsglview_create (this);
+# endif
+#endif
 }
 
 void
@@ -755,6 +766,15 @@ GtkCanvas::item_going_away (Item* item, Rect bounding_box)
 
 }
 
+void
+GtkCanvas::on_realize ()
+{
+       Gtk::EventBox::on_realize();
+#ifdef __APPLE__
+       nsglview_overlay (_nsglview, get_window()->gobj());
+#endif
+}
+
 void
 GtkCanvas::on_size_allocate (Gtk::Allocation& a)
 {
@@ -771,6 +791,18 @@ GtkCanvas::on_size_allocate (Gtk::Allocation& a)
 #ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
        }
 #endif
+
+#ifdef __APPLE__
+       if (_nsglview) {
+               gint xx, yy;
+               gtk_widget_translate_coordinates(
+                               GTK_WIDGET(gobj()),
+                               GTK_WIDGET(get_toplevel()->gobj()),
+                               0, 0, &xx, &yy);
+               nsglview_resize (_nsglview, xx, yy, a.get_width(), a.get_height());
+       }
+#endif
+
 }
 
 /** Handler for GDK expose events.
@@ -783,6 +815,12 @@ GtkCanvas::on_expose_event (GdkEventExpose* ev)
        if (_in_dtor) {
                return true;
        }
+#ifdef __APPLE__
+       if (_nsglview) {
+               nsglview_queue_draw (_nsglview, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
+               return true;
+       }
+#endif
 
 #ifdef CANVAS_PROFILE
        const int64_t start = g_get_monotonic_time ();
@@ -1154,7 +1192,7 @@ GtkCanvas::unfocus (Item* item)
 }
 
 /** @return The visible area of the canvas, in window coordinates */
-Rect
+ArdourCanvas::Rect
 GtkCanvas::visible_area () const
 {
        return Rect (0, 0, get_allocation().get_width (), get_allocation().get_height ());
index 793270548356fdf4d8e9a85b86294d16999aed34..20adfa4c458c0123fed72fb632f0cb0ed3956030 100644 (file)
@@ -215,6 +215,8 @@ public:
         bool on_enter_notify_event (GdkEventCrossing*);
         bool on_leave_notify_event (GdkEventCrossing*);
 
+       void on_realize ();
+
        bool button_handler (GdkEventButton *);
        bool motion_notify_handler (GdkEventMotion *);
         bool deliver_event (GdkEvent *);
@@ -249,6 +251,8 @@ private:
        bool really_start_tooltip_timeout ();
 
        bool _in_dtor;
+
+       void* _nsglview;
 };
 
 /** A GTK::Alignment with a GtkCanvas inside it plus some Gtk::Adjustments for
diff --git a/libs/canvas/canvas/nsglview.h b/libs/canvas/canvas/nsglview.h
new file mode 100644 (file)
index 0000000..e18a0f6
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef __CANVAS_NSGLVIEW_H__
+#define __CANVAS_NSGLVIEW_H__
+
+#include <gdk/gdk.h>
+
+namespace ArdourCanvas
+{
+       class GtkCanvas;
+
+       void* nsglview_create (GtkCanvas*);
+       void  nsglview_overlay (void*, GdkWindow*);
+       void  nsglview_resize (void*, int x, int y, int w, int h);
+       void  nsglview_queue_draw (void*, int x, int y, int w, int h);
+}
+#endif
diff --git a/libs/canvas/nsglview.mm b/libs/canvas/nsglview.mm
new file mode 100644 (file)
index 0000000..09d0ae1
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+    Copyright (C) 2011 Paul Davis
+    Copyright (C) 2012 David Robillard <http://drobilla.net>
+    Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/* include order matter due to apple defines */
+#include <gtkmm/window.h>
+
+#include "canvas/canvas.h"
+#include "canvas/utils.h"
+#include "canvas/nsglview.h"
+
+#include <gdk/gdkquartz.h>
+
+#include <OpenGL/gl.h>
+#import  <Cocoa/Cocoa.h>
+
+__attribute__ ((visibility ("hidden")))
+@interface ArdourCanvasOpenGLView : NSOpenGLView
+{
+@private
+       unsigned int _texture_id;
+       int _width;
+       int _height;
+       Cairo::RefPtr<Cairo::ImageSurface> surf;
+       ArdourCanvas::GtkCanvas *gtkcanvas;
+}
+
+- (id) initWithFrame:(NSRect)frame;
+- (void) dealloc;
+- (void) set_ardour_canvas:(ArdourCanvas::GtkCanvas*)c;
+- (void) reshape;
+- (void) drawRect:(NSRect)rect;
+- (BOOL) canBecomeKeyWindow:(id)sender;
+- (BOOL) acceptsFirstResponder:(id)sender;
+
+@end
+
+@implementation ArdourCanvasOpenGLView
+
+- (id) initWithFrame:(NSRect)frame
+{
+       NSOpenGLPixelFormatAttribute pixelAttribs[16] = {
+               NSOpenGLPFADoubleBuffer,
+               NSOpenGLPFAAccelerated,
+               NSOpenGLPFAColorSize, 32,
+               NSOpenGLPFADepthSize, 32,
+               NSOpenGLPFAMultisample,
+               NSOpenGLPFASampleBuffers, 1,
+               NSOpenGLPFASamples, 4,
+               0
+       };
+
+       NSOpenGLPixelFormat* pixelFormat =
+               [[NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs];
+
+       if (pixelFormat) {
+               self = [super initWithFrame:frame pixelFormat:pixelFormat];
+               [pixelFormat release];
+       } else {
+               self = [super initWithFrame:frame];
+       }
+
+       _texture_id = 0;
+       _width = 0;
+       _height = 0;
+
+       if (self) {
+
+               [[self openGLContext] makeCurrentContext];
+               glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
+               glDisable (GL_DEPTH_TEST);
+               glEnable (GL_BLEND);
+               glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+               glEnable (GL_TEXTURE_RECTANGLE_ARB);
+               [NSOpenGLContext clearCurrentContext];
+
+               [self reshape];
+       }
+
+       return self;
+}
+
+- (void) dealloc {
+       [[self openGLContext] makeCurrentContext];
+       glDeleteTextures (1, &_texture_id);
+       [NSOpenGLContext clearCurrentContext];
+
+       [super dealloc];
+}
+
+- (void) set_ardour_canvas:(ArdourCanvas::GtkCanvas*)c
+{
+       gtkcanvas = c;
+}
+
+- (BOOL) canBecomeKeyWindow:(id)sender{
+       return NO;
+}
+
+- (BOOL) acceptsFirstResponder:(id)sender{
+       return NO;
+}
+
+- (void) reshape
+{
+       [[self openGLContext] update];
+
+       NSRect bounds = [self bounds];
+       int    width  = bounds.size.width;
+       int    height = bounds.size.height;
+
+       if (_width == width && _height == height) {
+               return;
+       }
+
+       [[self openGLContext] makeCurrentContext];
+
+       glViewport (0, 0, width, height);
+       glMatrixMode (GL_PROJECTION);
+       glLoadIdentity ();
+       glOrtho (-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);
+
+       glClear (GL_COLOR_BUFFER_BIT);
+
+       glDeleteTextures (1, &_texture_id);
+       glGenTextures (1, &_texture_id);
+       glBindTexture (GL_TEXTURE_RECTANGLE_ARB, _texture_id);
+       glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8,
+                       width, height, 0,
+                       GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+       glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
+
+       glMatrixMode(GL_MODELVIEW);
+       glLoadIdentity();
+
+       [NSOpenGLContext clearCurrentContext];
+
+       _width  = width;
+       _height = height;
+}
+
+- (void) drawRect:(NSRect)rect
+{
+       [[self openGLContext] makeCurrentContext];
+
+       glMatrixMode(GL_MODELVIEW);
+       glLoadIdentity();
+       glClear(GL_COLOR_BUFFER_BIT);
+
+       /* call back into GtkCanvas */
+
+       ArdourCanvas::Rect crect (rect.origin.x, rect.origin.y,
+       rect.size.width + rect.origin.x,
+       rect.size.height + rect.origin.y);
+
+       if (!surf || surf->get_width () != _width || surf->get_height() != _height) {
+               surf = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _width, _height);
+
+               crect.x0 = crect.y0 = 0;
+               crect.x1 = _width;
+               crect.y1 = _height;
+       }
+
+       Cairo::RefPtr<Cairo::Context> ctx = Cairo::Context::create (surf);
+
+       // TODO: check retina screen, scaling factor.
+       // cairo_surface_get_device_scale () or explicit scale
+
+       ctx->rectangle (crect.x0, crect.y0, crect.width(), crect.height());
+       ctx->clip_preserve ();
+       /* draw background color */
+       ArdourCanvas::set_source_rgba (ctx, gtkcanvas->background_color ());
+       ctx->fill ();
+
+       gtkcanvas->render (crect, ctx);
+
+       surf->flush ();
+       uint8_t* imgdata = surf->get_data ();
+
+       /* NOTE for big-endian (PPC), we'd need to flip byte-order
+        * RGBA <> RGBA for the texture.
+        * GtkCanvas does not use this nsview for PPC builds, yet
+        */
+
+       /* continue OpenGL */
+       glPushMatrix ();
+
+       glEnable(GL_TEXTURE_2D);
+       glBindTexture(GL_TEXTURE_RECTANGLE_ARB, _texture_id);
+       glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8,
+                       _width, _height, /*border*/ 0,
+                       GL_BGRA, GL_UNSIGNED_BYTE, imgdata);
+
+       glBegin(GL_QUADS);
+       glTexCoord2f(           0.0f, (GLfloat) _height);
+       glVertex2f(-1.0f, -1.0f);
+
+       glTexCoord2f((GLfloat) _width, (GLfloat) _height);
+       glVertex2f( 1.0f, -1.0f);
+
+       glTexCoord2f((GLfloat) _width, 0.0f);
+       glVertex2f( 1.0f,  1.0f);
+
+       glTexCoord2f(            0.0f, 0.0f);
+       glVertex2f(-1.0f,  1.0f);
+       glEnd();
+
+       glDisable(GL_TEXTURE_2D);
+       glPopMatrix();
+
+       ///
+
+       glFlush();
+       glSwapAPPLE();
+       [NSOpenGLContext clearCurrentContext];
+}
+
+@end
+
+
+void*
+ArdourCanvas::nsglview_create (GtkCanvas* canvas)
+{
+       ArdourCanvasOpenGLView* gl_view = [ArdourCanvasOpenGLView new];
+       if (!gl_view) {
+               return 0;
+       }
+       [gl_view set_ardour_canvas:canvas];
+       return gl_view;
+}
+
+void
+ArdourCanvas::nsglview_overlay (void* glv, GdkWindow* window)
+{
+       ArdourCanvasOpenGLView* gl_view = (ArdourCanvasOpenGLView*) glv;
+       NSView* view = gdk_quartz_window_get_nsview (window);
+       [view addSubview:gl_view];
+}
+
+void
+ArdourCanvas::nsglview_resize (void* glv, int x, int y, int w, int h)
+{
+       ArdourCanvasOpenGLView* gl_view = (ArdourCanvasOpenGLView*) glv;
+       [gl_view setFrame:NSMakeRect(x, y, w, h)];
+}
+
+void
+ArdourCanvas::nsglview_queue_draw (void* glv, int x, int y, int w, int h)
+{
+       ArdourCanvasOpenGLView* gl_view = (ArdourCanvasOpenGLView*) glv;
+       [gl_view setNeedsDisplayInRect:NSMakeRect(x, y, w, h)];
+}
index 6294fe0d051f4fd93d1dbe9e94a58f84cc59eed7..cdb6156f15e84a134df74292677ad4db2653f4a4 100644 (file)
@@ -3,6 +3,7 @@ from waflib.extras import autowaf as autowaf
 from waflib import Options
 from waflib import TaskGen
 import os
+import sys
 
 # Version of this package (even if built as a child)
 MAJOR = '0'
@@ -96,6 +97,9 @@ def build(bld):
     obj.install_path = bld.env['LIBDIR']
     obj.defines      += [ 'PACKAGE="' + I18N_PACKAGE + '"' ]
 
+    if sys.platform == 'darwin':
+        obj.source += [ 'nsglview.mm']
+
     # canvas unit-tests are outdated
     if False and bld.env['BUILD_TESTS'] and bld.is_defined('HAVE_CPPUNIT'):
             unit_testobj              = bld(features = 'cxx cxxprogram')