1/*
2  Copyright 2019-2020 David Robillard <d@drobilla.net>
3
4  Permission to use, copy, modify, and/or distribute this software for any
5  purpose with or without fee is hereby granted, provided that the above
6  copyright notice and this permission notice appear in all copies.
7
8  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15*/
16
17#include "implementation.h"
18#include "mac.h"
19#include "stub.h"
20
21#include "pugl/gl.h"
22
23#ifndef __MAC_10_10
24#  define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core
25#endif
26
27@interface PuglOpenGLView : NSOpenGLView
28@end
29
30@implementation PuglOpenGLView {
31@public
32  PuglView* puglview;
33}
34
35- (id)initWithFrame:(NSRect)frame
36{
37  const bool     compat  = puglview->hints[PUGL_USE_COMPAT_PROFILE];
38  const unsigned samples = (unsigned)puglview->hints[PUGL_SAMPLES];
39  const int      major   = puglview->hints[PUGL_CONTEXT_VERSION_MAJOR];
40  const unsigned profile =
41    ((compat || major < 3) ? NSOpenGLProfileVersionLegacy
42                           : (major >= 4 ? NSOpenGLProfileVersion4_1Core
43                                         : NSOpenGLProfileVersion3_2Core));
44
45  // Set attributes to default if they are unset
46  // (There is no GLX_DONT_CARE equivalent on MacOS)
47  if (puglview->hints[PUGL_DEPTH_BITS] == PUGL_DONT_CARE) {
48    puglview->hints[PUGL_DEPTH_BITS] = 0;
49  }
50  if (puglview->hints[PUGL_STENCIL_BITS] == PUGL_DONT_CARE) {
51    puglview->hints[PUGL_STENCIL_BITS] = 0;
52  }
53  if (puglview->hints[PUGL_SAMPLES] == PUGL_DONT_CARE) {
54    puglview->hints[PUGL_SAMPLES] = 1;
55  }
56  if (puglview->hints[PUGL_DOUBLE_BUFFER] == PUGL_DONT_CARE) {
57    puglview->hints[PUGL_DOUBLE_BUFFER] = 1;
58  }
59  if (puglview->hints[PUGL_SWAP_INTERVAL] == PUGL_DONT_CARE) {
60    puglview->hints[PUGL_SWAP_INTERVAL] = 1;
61  }
62
63  const unsigned colorSize = (unsigned)(puglview->hints[PUGL_RED_BITS] +
64                                        puglview->hints[PUGL_BLUE_BITS] +
65                                        puglview->hints[PUGL_GREEN_BITS] +
66                                        puglview->hints[PUGL_ALPHA_BITS]);
67
68  // clang-format off
69  NSOpenGLPixelFormatAttribute pixelAttribs[17] = {
70    NSOpenGLPFADoubleBuffer,
71    NSOpenGLPFAAccelerated,
72    NSOpenGLPFAOpenGLProfile, profile,
73    NSOpenGLPFAColorSize,     colorSize,
74    NSOpenGLPFADepthSize,     (unsigned)puglview->hints[PUGL_DEPTH_BITS],
75    NSOpenGLPFAStencilSize,   (unsigned)puglview->hints[PUGL_STENCIL_BITS],
76    NSOpenGLPFAMultisample,   samples ? 1 : 0,
77    NSOpenGLPFASampleBuffers, samples ? 1 : 0,
78    NSOpenGLPFASamples,       samples,
79    0};
80  // clang-format on
81
82  NSOpenGLPixelFormat* pixelFormat =
83    [[NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs];
84
85  if (pixelFormat) {
86    self = [super initWithFrame:frame pixelFormat:pixelFormat];
87    [pixelFormat release];
88  } else {
89    self = [super initWithFrame:frame];
90  }
91
92  [self setWantsBestResolutionOpenGLSurface:YES];
93
94  if (self) {
95    [[self openGLContext] makeCurrentContext];
96    [self reshape];
97  }
98  return self;
99}
100
101- (void)reshape
102{
103  PuglWrapperView* wrapper = (PuglWrapperView*)[self superview];
104
105  [super reshape];
106  [wrapper setReshaped];
107}
108
109- (void)drawRect:(NSRect)rect
110{
111  PuglWrapperView* wrapper = (PuglWrapperView*)[self superview];
112  [wrapper dispatchExpose:rect];
113}
114
115@end
116
117static PuglStatus
118puglMacGlCreate(PuglView* view)
119{
120  PuglInternals*  impl     = view->impl;
121  PuglOpenGLView* drawView = [PuglOpenGLView alloc];
122
123  drawView->puglview = view;
124  [drawView initWithFrame:[impl->wrapperView bounds]];
125  if (view->hints[PUGL_RESIZABLE]) {
126    [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
127  } else {
128    [drawView setAutoresizingMask:NSViewNotSizable];
129  }
130
131  impl->drawView = drawView;
132  return PUGL_SUCCESS;
133}
134
135static PuglStatus
136puglMacGlDestroy(PuglView* view)
137{
138  PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView;
139
140  [drawView removeFromSuperview];
141  [drawView release];
142
143  view->impl->drawView = nil;
144  return PUGL_SUCCESS;
145}
146
147static PuglStatus
148puglMacGlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose))
149{
150  PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView;
151
152  [[drawView openGLContext] makeCurrentContext];
153  return PUGL_SUCCESS;
154}
155
156static PuglStatus
157puglMacGlLeave(PuglView* view, const PuglEventExpose* expose)
158{
159  PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView;
160
161  if (expose) {
162    [[drawView openGLContext] flushBuffer];
163  }
164
165  [NSOpenGLContext clearCurrentContext];
166
167  return PUGL_SUCCESS;
168}
169
170PuglGlFunc
171puglGetProcAddress(const char* name)
172{
173  CFBundleRef framework =
174    CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
175
176  CFStringRef symbol = CFStringCreateWithCString(
177    kCFAllocatorDefault, name, kCFStringEncodingASCII);
178
179  PuglGlFunc func =
180    (PuglGlFunc)CFBundleGetFunctionPointerForName(framework, symbol);
181
182  CFRelease(symbol);
183
184  return func;
185}
186
187PuglStatus
188puglEnterContext(PuglView* view)
189{
190  return view->backend->enter(view, NULL);
191}
192
193PuglStatus
194puglLeaveContext(PuglView* view)
195{
196  return view->backend->leave(view, NULL);
197}
198
199const PuglBackend*
200puglGlBackend(void)
201{
202  static const PuglBackend backend = {puglStubConfigure,
203                                      puglMacGlCreate,
204                                      puglMacGlDestroy,
205                                      puglMacGlEnter,
206                                      puglMacGlLeave,
207                                      puglStubGetContext};
208
209  return &backend;
210}
211