Revert to use an image surface for CairoWidgets
[ardour.git] / libs / gtkmm2ext / nsglview.mm
1 /*
2  * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18
19 /* include order matter due to apple defines */
20 #include <gtkmm/window.h>
21
22 #include "gtkmm2ext/cairo_canvas.h"
23 #include "gtkmm2ext/nsglview.h"
24 #include "gtkmm2ext/rgb_macros.h"
25
26 #include <gdk/gdkquartz.h>
27
28 #include <OpenGL/gl.h>
29 #import  <Cocoa/Cocoa.h>
30
31 /* the gtk-quartz library which ardour links against
32  * is patched to pass events directly through to
33  * NSView child-views (AU Plugin GUIs).
34  *
35  * In this particular case however we do want the
36  * events to reach the GTK widget instead of the
37  * NSView subview.
38  *
39  * If a NSVIew tag equals to the given magic-number,
40  * Gdk events propagate.
41  */
42 #ifndef ARDOUR_CANVAS_NSVIEW_TAG
43 #define ARDOUR_CANVAS_NSVIEW_TAG 0x0
44 #endif
45
46 __attribute__ ((visibility ("hidden")))
47 @interface ArdourCanvasOpenGLView : NSOpenGLView
48 {
49 @private
50         unsigned int _texture_id;
51         int _width;
52         int _height;
53         Cairo::RefPtr<Cairo::ImageSurface> surf;
54         Gtkmm2ext::CairoCanvas *cairo_canvas;
55         NSInteger _tag;
56 }
57
58 @property (readwrite) NSInteger tag;
59
60 - (id) initWithFrame:(NSRect)frame;
61 - (void) dealloc;
62 - (void) setCairoCanvas:(Gtkmm2ext::CairoCanvas*)c;
63 - (void) reshape;
64 - (void) setNeedsDisplayInRect:(NSRect)rect;
65 - (void) drawRect:(NSRect)rect;
66 - (BOOL) canBecomeKeyWindow:(id)sender;
67 - (BOOL) acceptsFirstResponder:(id)sender;
68
69 @end
70
71 @implementation ArdourCanvasOpenGLView
72
73 @synthesize tag = _tag;
74
75 - (id) initWithFrame:(NSRect)frame
76 {
77         NSOpenGLPixelFormatAttribute pixelAttribs[16] = {
78                 NSOpenGLPFADoubleBuffer,
79                 NSOpenGLPFAAccelerated,
80                 NSOpenGLPFAColorSize, 32,
81                 NSOpenGLPFADepthSize, 32,
82                 NSOpenGLPFAMultisample,
83                 NSOpenGLPFASampleBuffers, 1,
84                 NSOpenGLPFASamples, 4,
85                 0
86         };
87
88         NSOpenGLPixelFormat* pixelFormat =
89                 [[NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs];
90
91         if (pixelFormat) {
92                 self = [super initWithFrame:frame pixelFormat:pixelFormat];
93                 [pixelFormat release];
94         } else {
95                 self = [super initWithFrame:frame];
96         }
97
98         _texture_id = 0;
99         _width = 0;
100         _height = 0;
101
102         if (self) {
103                 self.tag = ARDOUR_CANVAS_NSVIEW_TAG;
104                 [[self openGLContext] makeCurrentContext];
105                 glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
106                 glDisable (GL_DEPTH_TEST);
107                 glEnable (GL_BLEND);
108                 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
109                 glEnable (GL_TEXTURE_RECTANGLE_ARB);
110                 [NSOpenGLContext clearCurrentContext];
111
112                 [self reshape];
113         }
114
115         return self;
116 }
117
118 - (void) dealloc {
119         [[self openGLContext] makeCurrentContext];
120         glDeleteTextures (1, &_texture_id);
121         [NSOpenGLContext clearCurrentContext];
122
123         [super dealloc];
124 }
125
126 - (void) setCairoCanvas:(Gtkmm2ext::CairoCanvas*)c
127 {
128         cairo_canvas = c;
129 }
130
131 - (BOOL) canBecomeKeyWindow:(id)sender{
132         return NO;
133 }
134
135 - (BOOL) acceptsFirstResponder:(id)sender{
136         return NO;
137 }
138
139 - (void) reshape
140 {
141         [[self openGLContext] update];
142
143         NSRect bounds = [self bounds];
144         int    width  = bounds.size.width;
145         int    height = bounds.size.height;
146
147         if (_width == width && _height == height) {
148                 return;
149         }
150
151         [[self openGLContext] makeCurrentContext];
152
153         glViewport (0, 0, width, height);
154         glMatrixMode (GL_PROJECTION);
155         glLoadIdentity ();
156         glOrtho (-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);
157
158         glClear (GL_COLOR_BUFFER_BIT);
159
160         glDeleteTextures (1, &_texture_id);
161         glGenTextures (1, &_texture_id);
162         glBindTexture (GL_TEXTURE_RECTANGLE_ARB, _texture_id);
163         glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8,
164                         width, height, 0,
165                         GL_BGRA, GL_UNSIGNED_BYTE, NULL);
166         glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
167
168         glMatrixMode(GL_MODELVIEW);
169         glLoadIdentity();
170
171         [NSOpenGLContext clearCurrentContext];
172
173         _width  = width;
174         _height = height;
175 }
176
177 - (void) setNeedsDisplayInRect:(NSRect)rect
178 {
179         [super setNeedsDisplayInRect:rect];
180 #ifdef DEBUG_NSVIEW_EXPOSURE
181         printf ("needsDisplay: %5.1f %5.1f   %5.1f %5.1f\n",
182                         rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
183 #endif
184 }
185
186 - (void) drawRect:(NSRect)rect
187 {
188         [[self openGLContext] makeCurrentContext];
189
190         glMatrixMode(GL_MODELVIEW);
191         glLoadIdentity();
192         glClear(GL_COLOR_BUFFER_BIT);
193
194         /* call back into CairoCanvas */
195         cairo_rectangle_t cairo_rect;
196
197         cairo_rect.x = rect.origin.x;
198         cairo_rect.y = rect.origin.y;
199         cairo_rect.width = rect.size.width;
200         cairo_rect.height = rect.size.height;
201
202         if (!surf || surf->get_width () != _width || surf->get_height() != _height) {
203                 surf = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _width, _height);
204
205                 cairo_rect.x = 0;
206                 cairo_rect.y = 0;
207                 cairo_rect.width = _width;
208                 cairo_rect.height = _height;
209         }
210
211         Cairo::RefPtr<Cairo::Context> ctx = Cairo::Context::create (surf);
212
213         // TODO: check retina screen, scaling factor.
214         // cairo_surface_get_device_scale () or explicit scale
215
216         ctx->rectangle (cairo_rect.x, cairo_rect.y, cairo_rect.width, cairo_rect.height);
217         ctx->clip_preserve ();
218         {
219                 /* draw background color */
220                 uint32_t col = cairo_canvas->background_color ();
221                 int r, g, b, a;
222                 UINT_TO_RGBA (col, &r, &g, &b, &a);
223                 ctx->set_source_rgba (r/255.0, g/255.0, b/255.0, a/255.0);
224         }
225         ctx->fill ();
226
227 #ifdef DEBUG_NSVIEW_EXPOSURE
228         printf ("drawRect:     %.1f %.1f  %.1f %1.f\n",
229                         cairo_rect.x, cairo_rect.y, cairo_rect.width, cairo_rect.height);
230 #endif
231
232         cairo_canvas->render (ctx, &cairo_rect);
233
234         surf->flush ();
235         uint8_t* imgdata = surf->get_data ();
236
237         /* NOTE for big-endian (PPC), we'd need to flip byte-order
238          * RGBA <> RGBA for the texture.
239          * GtkCanvas does not use this nsview for PPC builds, yet
240          */
241
242         /* continue OpenGL */
243         glPushMatrix ();
244
245         glEnable(GL_TEXTURE_2D);
246         glBindTexture(GL_TEXTURE_RECTANGLE_ARB, _texture_id);
247         glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8,
248                         _width, _height, /*border*/ 0,
249                         GL_BGRA, GL_UNSIGNED_BYTE, imgdata);
250
251         glBegin(GL_QUADS);
252         glTexCoord2f(           0.0f, (GLfloat) _height);
253         glVertex2f(-1.0f, -1.0f);
254
255         glTexCoord2f((GLfloat) _width, (GLfloat) _height);
256         glVertex2f( 1.0f, -1.0f);
257
258         glTexCoord2f((GLfloat) _width, 0.0f);
259         glVertex2f( 1.0f,  1.0f);
260
261         glTexCoord2f(            0.0f, 0.0f);
262         glVertex2f(-1.0f,  1.0f);
263         glEnd();
264
265         glDisable(GL_TEXTURE_2D);
266         glPopMatrix();
267
268         ///
269
270         glFlush();
271         glSwapAPPLE();
272         [NSOpenGLContext clearCurrentContext];
273         [super setNeedsDisplay:NO];
274 }
275 @end
276
277
278 void*
279 Gtkmm2ext::nsglview_create (Gtkmm2ext::CairoCanvas* canvas)
280 {
281 /* the API is currently only used on intel mac
282  * for big-endian  RGBA <> RGBA byte order of the texture
283  * will have to be swapped.
284  */
285 #ifdef __ppc__
286         return 0;
287 #endif
288         ArdourCanvasOpenGLView* gl_view = [ArdourCanvasOpenGLView new];
289         if (!gl_view) {
290                 return 0;
291         }
292         [gl_view setCairoCanvas:canvas];
293         [gl_view setHidden:YES];
294         return gl_view;
295 }
296
297 void
298 Gtkmm2ext::nsglview_overlay (void* glv, GdkWindow* window)
299 {
300         ArdourCanvasOpenGLView* gl_view = (ArdourCanvasOpenGLView*) glv;
301         NSView* view = gdk_quartz_window_get_nsview (window);
302         [view addSubview:gl_view];
303 }
304
305 void
306 Gtkmm2ext::nsglview_resize (void* glv, int x, int y, int w, int h)
307 {
308         ArdourCanvasOpenGLView* gl_view = (ArdourCanvasOpenGLView*) glv;
309         [gl_view setFrame:NSMakeRect(x, y, w, h)];
310 }
311
312 void
313 Gtkmm2ext::nsglview_queue_draw (void* glv, int x, int y, int w, int h)
314 {
315         ArdourCanvasOpenGLView* gl_view = (ArdourCanvasOpenGLView*) glv;
316         [gl_view setNeedsDisplayInRect:NSMakeRect(x, y, w, h)];
317 #ifdef DEBUG_NSVIEW_EXPOSURE
318         printf ("Queue Draw    %5d %5d  %5d %5d\n", x, y, w, h);
319 #endif
320 }
321
322 void
323 Gtkmm2ext::nsglview_set_visible (void* glv, bool vis)
324 {
325         ArdourCanvasOpenGLView* gl_view = (ArdourCanvasOpenGLView*) glv;
326         if (vis) {
327                 [gl_view setHidden:NO];
328         } else {
329                 [gl_view setHidden:YES];
330         }
331 }