From c371fc511500acada422cdcf93658fc25e5106de Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Sun, 19 Mar 2017 22:40:58 +0100 Subject: Prepare NSView/OpenGL Canvas (to speed up rendering on [mac]OS[X] This avoids Coregraphics (cairo_quartz_surface..) competely. The openGL texture bypasses CG's slow argb_image and CGSColorMask methods. --- libs/canvas/canvas.cc | 40 ++++++- libs/canvas/canvas/canvas.h | 4 + libs/canvas/canvas/nsglview.h | 15 +++ libs/canvas/nsglview.mm | 269 ++++++++++++++++++++++++++++++++++++++++++ libs/canvas/wscript | 4 + 5 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 libs/canvas/canvas/nsglview.h create mode 100644 libs/canvas/nsglview.mm (limited to 'libs/canvas') diff --git a/libs/canvas/canvas.cc b/libs/canvas/canvas.cc index a72700b94a..70fd36e8b5 100644 --- a/libs/canvas/canvas.cc +++ b/libs/canvas/canvas.cc @@ -44,6 +44,10 @@ #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 ()); diff --git a/libs/canvas/canvas/canvas.h b/libs/canvas/canvas/canvas.h index 7932705483..20adfa4c45 100644 --- a/libs/canvas/canvas/canvas.h +++ b/libs/canvas/canvas/canvas.h @@ -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 index 0000000000..e18a0f68ab --- /dev/null +++ b/libs/canvas/canvas/nsglview.h @@ -0,0 +1,15 @@ +#ifndef __CANVAS_NSGLVIEW_H__ +#define __CANVAS_NSGLVIEW_H__ + +#include + +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 index 0000000000..09d0ae1342 --- /dev/null +++ b/libs/canvas/nsglview.mm @@ -0,0 +1,269 @@ +/* + Copyright (C) 2011 Paul Davis + Copyright (C) 2012 David Robillard + Copyright (C) 2017 Robin Gareus + + 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 + +#include "canvas/canvas.h" +#include "canvas/utils.h" +#include "canvas/nsglview.h" + +#include + +#include +#import + +__attribute__ ((visibility ("hidden"))) +@interface ArdourCanvasOpenGLView : NSOpenGLView +{ +@private + unsigned int _texture_id; + int _width; + int _height; + Cairo::RefPtr 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 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)]; +} diff --git a/libs/canvas/wscript b/libs/canvas/wscript index 6294fe0d05..cdb6156f15 100644 --- a/libs/canvas/wscript +++ b/libs/canvas/wscript @@ -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') -- cgit v1.2.3