ardour/libs/gtkmm2ext/nsglview.mm

431 lines
10 KiB
Plaintext

/*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* include order matter due to apple defines */
#include <gtkmm/window.h>
#include "gtkmm2ext/cairo_canvas.h"
#include "gtkmm2ext/nsglview.h"
#include "gtkmm2ext/rgb_macros.h"
#include <gdk/gdkquartz.h>
//#define NSVIEW_PROFILE
//#define DEBUG_NSVIEW_EXPOSURE
#include <OpenGL/gl.h>
#import <Cocoa/Cocoa.h>
/* the gtk-quartz library which ardour links against
* is patched to pass events directly through to
* NSView child-views (AU Plugin GUIs).
*
* In this particular case however we do want the
* events to reach the GTK widget instead of the
* NSView subview.
*
* If a NSVIew tag equals to the given magic-number,
* Gdk events propagate.
*/
#ifndef ARDOUR_CANVAS_NSVIEW_TAG
#error NO TAG
#define ARDOUR_CANVAS_NSVIEW_TAG 0x0
#endif
__attribute__ ((visibility ("hidden")))
@interface ArdourCanvasOpenGLView : NSOpenGLView
{
@private
Cairo::RefPtr<Cairo::ImageSurface> _surf;
Gtkmm2ext::CairoCanvas* _cairo_canvas;
NSInteger _tag;
unsigned int _texture_id;
int _width;
int _height;
float _scale;
bool _use_backing_scale;
GdkRectangle _expose_area;
}
@property (readwrite) NSInteger tag;
- (instancetype)initWithScale:(BOOL)use_backing_scale;
- (id) initWithFrame:(NSRect)frame;
- (void) dealloc;
- (void) setCairoCanvas:(Gtkmm2ext::CairoCanvas*)canvas;
- (void) reshape;
- (void) setNeedsDisplayInRect:(NSRect)rect;
- (void) setNeedsDisplay:(BOOL)yn;
- (void) drawRect:(NSRect)rect;
- (BOOL) canBecomeKeyWindow:(id)sender;
- (BOOL) acceptsFirstResponder:(id)sender;
- (void) reallocateTexture;
@end
@implementation ArdourCanvasOpenGLView
@synthesize tag = _tag;
- (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;
_scale = 1.;
_expose_area.x = _expose_area.y = _expose_area.width = _expose_area.height = 0;
if (self) {
self.tag = ARDOUR_CANVAS_NSVIEW_TAG;
[[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];
if (_use_backing_scale) {
[self setWantsBestResolutionOpenGLSurface:YES];
[self setWantsLayer:YES];
} else {
[self setWantsBestResolutionOpenGLSurface:NO];
}
[self reshape];
}
return self;
}
- (instancetype)initWithScale:(BOOL)use_backing_scale
{
_use_backing_scale = use_backing_scale;
[super init];
return self;
}
- (void) dealloc {
[[self openGLContext] makeCurrentContext];
glDeleteTextures (1, &_texture_id);
[NSOpenGLContext clearCurrentContext];
[super dealloc];
}
- (void) setCairoCanvas:(Gtkmm2ext::CairoCanvas*)canvas
{
_cairo_canvas = canvas;
}
- (BOOL) canBecomeKeyWindow:(id)sender{
return NO;
}
- (BOOL) acceptsFirstResponder:(id)sender{
return NO;
}
- (void) reallocateTexture
{
/* must be called withing GLContext */
int const w = _width * _scale;
int const h = _height * _scale;
glViewport (0, 0, w, h);
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,
w, h, 0,
GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
_expose_area.x = _expose_area.y = 0;
_expose_area.width = _width;
_expose_area.height = _height;
}
- (void) reshape
{
[super reshape];
[[self openGLContext] update];
NSRect bounds = [self bounds];
int width = bounds.size.width;
int height = bounds.size.height;
float scale = 1.0;
if (_use_backing_scale && [self window]) {
scale = [[self window] backingScaleFactor];
}
if (_width == width && _height == height && _scale == scale) {
return;
}
_width = width;
_height = height;
_scale = scale;
[[self openGLContext] makeCurrentContext];
[self reallocateTexture];
[NSOpenGLContext clearCurrentContext];
}
- (void) setNeedsDisplay:(BOOL)yn
{
if (yn) {
_expose_area.x = 0;
_expose_area.y = 0;
_expose_area.width = _width;
_expose_area.height = _height;
#ifdef DEBUG_NSVIEW_EXPOSURE
printf ("setNeedsDisplay: %4d x %4d\n", _width, _height);
#endif
}
[super setNeedsDisplay:yn];
}
- (void) setNeedsDisplayInRect:(NSRect)rect
{
#ifdef DEBUG_NSVIEW_EXPOSURE
printf ("setNeedsDisplayInRect: %4.0f x %4.0f @ %.0f + %.0f\n",
rect.size.width, rect.size.height, rect.origin.x, rect.origin.y);
#endif
if (_expose_area.width == 0 && _expose_area.height == 0) {
_expose_area.x = rect.origin.x;
_expose_area.y = rect.origin.y;
_expose_area.width = rect.size.width;
_expose_area.height = rect.size.height;
} else {
GdkRectangle r1 = {(int)rect.origin.x, (int)rect.origin.y, (int)rect.size.width, (int)rect.size.height};
gdk_rectangle_union (&r1, &_expose_area, &_expose_area);
}
GdkRectangle rw = {0, 0, _width, _height};
gdk_rectangle_intersect (&rw, &_expose_area, &_expose_area);
[super setNeedsDisplayInRect:rect];
}
- (void) drawRect:(NSRect)rect
{
#ifdef NSVIEW_PROFILE
const int64_t start = g_get_monotonic_time ();
#endif
float scale = 1.0;
if (_use_backing_scale && [self window]) {
scale = [[self window] backingScaleFactor];
}
if ( _scale != scale) {
_scale = scale;
[[self openGLContext] update];
[[self openGLContext] makeCurrentContext];
[self reallocateTexture];
} else {
[[self openGLContext] makeCurrentContext];
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClear(GL_COLOR_BUFFER_BIT);
/* call back into CairoCanvas */
cairo_rectangle_t cairo_rect;
cairo_rect.x = _expose_area.x;
cairo_rect.y = _expose_area.y;
cairo_rect.width = _expose_area.width;
cairo_rect.height = _expose_area.height;
_expose_area.x = _expose_area.y = _expose_area.width = _expose_area.height = 0;
int const w = _width * _scale;
int const h = _height * _scale;
if (!_surf || _surf->get_width () != w || _surf->get_height() != h) {
_surf = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, w, h);
cairo_rect.x = 0;
cairo_rect.y = 0;
cairo_rect.width = _width;
cairo_rect.height = _height;
}
#ifdef DEBUG_NSVIEW_EXPOSURE
printf ("NSGL::drawRect: ...... %4.0f x %4.0f @ %.0f + %.0f\n",
cairo_rect.width, cairo_rect.height, cairo_rect.x, cairo_rect.y);
#endif
Cairo::RefPtr<Cairo::Context> ctx = Cairo::Context::create (_surf);
ctx->scale (_scale, _scale);
ctx->rectangle (cairo_rect.x, cairo_rect.y, cairo_rect.width, cairo_rect.height);
ctx->clip ();
{
/* draw background color */
uint32_t col = _cairo_canvas->background_color ();
int r, g, b, a;
UINT_TO_RGBA (col, &r, &g, &b, &a);
ctx->set_source_rgba (r/255.0, g/255.0, b/255.0, a/255.0);
ctx->paint ();
}
_cairo_canvas->render (ctx, &cairo_rect);
_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,
w, h, /*border*/ 0,
GL_BGRA, GL_UNSIGNED_BYTE, imgdata);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, (GLfloat) h);
glVertex2f(-1.0f, -1.0f);
glTexCoord2f((GLfloat) w, (GLfloat) h);
glVertex2f(1.0f, -1.0f);
glTexCoord2f((GLfloat) w, 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];
[super setNeedsDisplay:NO];
#ifdef NSVIEW_PROFILE
const int64_t end = g_get_monotonic_time ();
const int64_t elapsed = end - start;
printf ("NSGL::drawRect (%d x %d) * %.1f in %f ms\n", _width, _height, _scale, elapsed / 1000.f);
#endif
}
@end
void*
Gtkmm2ext::nsglview_create (Gtkmm2ext::CairoCanvas* canvas, bool use_backing_scale)
{
if (g_getenv ("ARDOUR_NSGL") && (0 == atoi (g_getenv ("ARDOUR_NSGL")))) {
return 0;
}
/* the API is currently only used on intel mac
* for big-endian RGBA <> RGBA byte order of the texture
* will have to be swapped.
*/
#ifdef __ppc__
return 0;
#endif
ArdourCanvasOpenGLView* gl_view = [[ArdourCanvasOpenGLView alloc] initWithScale:use_backing_scale];
if (!gl_view) {
return 0;
}
[gl_view setCairoCanvas:canvas];
[gl_view setHidden:YES];
return gl_view;
}
void
Gtkmm2ext::nsglview_overlay (void* glv, GdkWindow* window)
{
ArdourCanvasOpenGLView* gl_view = (ArdourCanvasOpenGLView*) glv;
NSView* view = gdk_quartz_window_get_nsview (window);
[view addSubview:gl_view];
}
void
Gtkmm2ext::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
Gtkmm2ext::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)];
}
void
Gtkmm2ext::nsglview_set_visible (void* glv, bool vis)
{
ArdourCanvasOpenGLView* gl_view = (ArdourCanvasOpenGLView*) glv;
if (vis) {
[gl_view setHidden:NO];
} else {
[gl_view setHidden:YES];
}
}