1/**
2 * @file opengl.m Video driver for OpenGL on MacOSX
3 *
4 * Copyright (C) 2010 - 2015 Creytiv.com
5 */
6#include <Cocoa/Cocoa.h>
7#include <OpenGL/gl.h>
8#include <OpenGL/glext.h>
9#include <re.h>
10#include <rem.h>
11#include <baresip.h>
12
13
14/**
15 * @defgroup opengl opengl
16 *
17 * Video display module for OpenGL on MacOSX
18 */
19
20
21#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 101200)
22#define NSTitledWindowMask         NSWindowStyleMaskTitled
23#define NSClosableWindowMask       NSWindowStyleMaskClosable
24#define NSMiniaturizableWindowMask NSWindowStyleMaskMiniaturizable
25#endif
26
27
28struct vidisp_st {
29	const struct vidisp *vd;        /**< Inheritance (1st)     */
30	struct vidsz size;              /**< Current size          */
31	NSOpenGLContext *ctx;
32	NSWindow *win;
33	GLhandleARB PHandle;
34	char *prog;
35};
36
37
38static struct vidisp *vid;       /**< OPENGL Video-display      */
39
40
41static const char *FProgram=
42  "uniform sampler2DRect Ytex;\n"
43  "uniform sampler2DRect Utex,Vtex;\n"
44  "void main(void) {\n"
45  "  float nx,ny,r,g,b,y,u,v;\n"
46  "  vec4 txl,ux,vx;"
47  "  nx=gl_TexCoord[0].x;\n"
48  "  ny=%d.0-gl_TexCoord[0].y;\n"
49  "  y=texture2DRect(Ytex,vec2(nx,ny)).r;\n"
50  "  u=texture2DRect(Utex,vec2(nx/2.0,ny/2.0)).r;\n"
51  "  v=texture2DRect(Vtex,vec2(nx/2.0,ny/2.0)).r;\n"
52
53  "  y=1.1643*(y-0.0625);\n"
54  "  u=u-0.5;\n"
55  "  v=v-0.5;\n"
56
57  "  r=y+1.5958*v;\n"
58  "  g=y-0.39173*u-0.81290*v;\n"
59  "  b=y+2.017*u;\n"
60
61  "  gl_FragColor=vec4(r,g,b,1.0);\n"
62  "}\n";
63
64
65static void destructor(void *arg)
66{
67	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
68	struct vidisp_st *st = arg;
69
70	if (st->ctx) {
71		[st->ctx clearDrawable];
72		[st->ctx release];
73	}
74
75	[st->win close];
76
77	if (st->PHandle) {
78		glUseProgramObjectARB(0);
79		glDeleteObjectARB(st->PHandle);
80	}
81
82	mem_deref(st->prog);
83
84	[pool release];
85}
86
87
88static int create_window(struct vidisp_st *st)
89{
90	NSRect rect = NSMakeRect(0, 0, 100, 100);
91	NSUInteger style;
92
93	if (st->win)
94		return 0;
95
96	style = NSTitledWindowMask |
97		NSClosableWindowMask |
98		NSMiniaturizableWindowMask;
99
100	st->win = [[NSWindow alloc] initWithContentRect:rect
101				    styleMask:style
102				    backing:NSBackingStoreBuffered
103				    defer:FALSE];
104	if (!st->win) {
105		warning("opengl: could not create NSWindow\n");
106		return ENOMEM;
107	}
108
109	[st->win setLevel:NSFloatingWindowLevel];
110
111	return 0;
112}
113
114
115static void opengl_reset(struct vidisp_st *st, const struct vidsz *sz)
116{
117	if (st->PHandle) {
118		glUseProgramObjectARB(0);
119		glDeleteObjectARB(st->PHandle);
120		st->PHandle = 0;
121		st->prog = mem_deref(st->prog);
122	}
123
124	st->size = *sz;
125}
126
127
128static int setup_shader(struct vidisp_st *st, int width, int height)
129{
130	GLhandleARB FSHandle, PHandle;
131	const char *progv[1];
132	char buf[1024];
133	int err, i;
134
135	if (st->PHandle)
136		return 0;
137
138	err = re_sdprintf(&st->prog, FProgram, height);
139	if (err)
140		return err;
141
142	glMatrixMode(GL_PROJECTION);
143	glLoadIdentity();
144	glOrtho(0, width, 0, height, -1, 1);
145	glViewport(0, 0, width, height);
146	glClearColor(0, 0, 0, 0);
147	glColor3f(1.0f, 0.84f, 0.0f);
148	glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
149
150	/* Set up program objects. */
151	PHandle = glCreateProgramObjectARB();
152	FSHandle = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
153
154	/* Compile the shader. */
155	progv[0] = st->prog;
156	glShaderSourceARB(FSHandle, 1, progv, NULL);
157	glCompileShaderARB(FSHandle);
158
159	/* Print the compilation log. */
160	glGetObjectParameterivARB(FSHandle, GL_OBJECT_COMPILE_STATUS_ARB, &i);
161	if (i != 1) {
162		warning("opengl: shader compile failed\n");
163		return ENOSYS;
164	}
165
166	glGetInfoLogARB(FSHandle, sizeof(buf), NULL, buf);
167
168	/* Create a complete program object. */
169	glAttachObjectARB(PHandle, FSHandle);
170	glLinkProgramARB(PHandle);
171
172	/* And print the link log. */
173	glGetInfoLogARB(PHandle, sizeof(buf), NULL, buf);
174
175	/* Finally, use the program. */
176	glUseProgramObjectARB(PHandle);
177
178	st->PHandle = PHandle;
179
180	return 0;
181}
182
183
184static int alloc(struct vidisp_st **stp, const struct vidisp *vd,
185		 struct vidisp_prm *prm, const char *dev,
186		 vidisp_resize_h *resizeh, void *arg)
187{
188	NSOpenGLPixelFormatAttribute attr[] = {
189		NSOpenGLPFAColorSize, 32,
190		NSOpenGLPFADepthSize, 16,
191		NSOpenGLPFADoubleBuffer,
192		0
193	};
194	NSOpenGLPixelFormat *fmt;
195	NSAutoreleasePool *pool;
196	struct vidisp_st *st;
197	GLint vsync = 1;
198	int err = 0;
199
200	(void)dev;
201	(void)resizeh;
202	(void)arg;
203
204	pool = [[NSAutoreleasePool alloc] init];
205	if (!pool)
206		return ENOMEM;
207
208	st = mem_zalloc(sizeof(*st), destructor);
209	if (!st)
210		return ENOMEM;
211
212	st->vd = vd;
213
214	fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
215	if (!fmt) {
216		err = ENOMEM;
217		warning("opengl: Failed creating OpenGL format\n");
218		goto out;
219	}
220
221	st->ctx = [[NSOpenGLContext alloc] initWithFormat:fmt
222					   shareContext:nil];
223
224	[fmt release];
225
226	if (!st->ctx) {
227		err = ENOMEM;
228		warning("opengl: Failed creating OpenGL context\n");
229		goto out;
230	}
231
232	/* Use provided view, or create our own */
233	if (prm && prm->view) {
234		[st->ctx setView:prm->view];
235	}
236	else {
237		err = create_window(st);
238		if (err)
239			goto out;
240
241		if (prm)
242			prm->view = [st->win contentView];
243	}
244
245	/* Enable vertical sync */
246	[st->ctx setValues:&vsync forParameter:NSOpenGLCPSwapInterval];
247
248 out:
249	if (err)
250		mem_deref(st);
251	else
252		*stp = st;
253
254	[pool release];
255
256	return err;
257}
258
259
260static inline void draw_yuv(GLhandleARB PHandle, int height,
261			    const uint8_t *Ytex, int linesizeY,
262			    const uint8_t *Utex, int linesizeU,
263			    const uint8_t *Vtex, int linesizeV)
264{
265	int i;
266
267	/* This might not be required, but should not hurt. */
268	glEnable(GL_TEXTURE_2D);
269
270	/* Select texture unit 1 as the active unit and bind the U texture. */
271	glActiveTexture(GL_TEXTURE1);
272	i = glGetUniformLocationARB(PHandle, "Utex");
273	glUniform1iARB(i,1);  /* Bind Utex to texture unit 1 */
274	glBindTexture(GL_TEXTURE_RECTANGLE_EXT,1);
275
276	glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
277			GL_TEXTURE_MAG_FILTER,GL_LINEAR);
278	glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
279			GL_TEXTURE_MIN_FILTER,GL_LINEAR);
280	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
281	glTexImage2D(GL_TEXTURE_RECTANGLE_EXT,0,GL_LUMINANCE,
282		     linesizeU, height/2, 0,
283		     GL_LUMINANCE,GL_UNSIGNED_BYTE,Utex);
284
285	/* Select texture unit 2 as the active unit and bind the V texture. */
286	glActiveTexture(GL_TEXTURE2);
287	i = glGetUniformLocationARB(PHandle, "Vtex");
288	glBindTexture(GL_TEXTURE_RECTANGLE_EXT,2);
289	glUniform1iARB(i,2);  /* Bind Vtext to texture unit 2 */
290
291	glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
292			GL_TEXTURE_MAG_FILTER,GL_LINEAR);
293	glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
294			GL_TEXTURE_MIN_FILTER,GL_LINEAR);
295
296	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
297	glTexImage2D(GL_TEXTURE_RECTANGLE_EXT,0,GL_LUMINANCE,
298		     linesizeV, height/2, 0,
299		     GL_LUMINANCE,GL_UNSIGNED_BYTE,Vtex);
300
301	/* Select texture unit 0 as the active unit and bind the Y texture. */
302	glActiveTexture(GL_TEXTURE0);
303	i = glGetUniformLocationARB(PHandle,"Ytex");
304	glUniform1iARB(i,0);  /* Bind Ytex to texture unit 0 */
305	glBindTexture(GL_TEXTURE_RECTANGLE_EXT,3);
306
307	glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
308			GL_TEXTURE_MAG_FILTER,GL_LINEAR);
309	glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
310			GL_TEXTURE_MIN_FILTER,GL_LINEAR);
311	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
312
313	glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_LUMINANCE,
314		     linesizeY, height, 0,
315		     GL_LUMINANCE, GL_UNSIGNED_BYTE, Ytex);
316}
317
318
319static inline void draw_blit(int width, int height)
320{
321	glClear(GL_COLOR_BUFFER_BIT);
322
323	/* Draw image */
324
325	glBegin(GL_QUADS);
326	{
327		glTexCoord2i(0, 0);
328		glVertex2i(0, 0);
329		glTexCoord2i(width, 0);
330		glVertex2i(width, 0);
331		glTexCoord2i(width, height);
332		glVertex2i(width, height);
333		glTexCoord2i(0, height);
334		glVertex2i(0, height);
335	}
336	glEnd();
337}
338
339
340static inline void draw_rgb(const uint8_t *pic, int w, int h)
341{
342	glEnable(GL_TEXTURE_RECTANGLE_EXT);
343	glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 1);
344
345	glTextureRangeAPPLE(GL_TEXTURE_RECTANGLE_EXT, w * h * 2, pic);
346
347	glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
348			GL_TEXTURE_STORAGE_HINT_APPLE,
349			GL_STORAGE_SHARED_APPLE);
350	glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
351
352	glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER,
353			GL_NEAREST);
354	glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER,
355			GL_NEAREST);
356	glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_S,
357			GL_CLAMP_TO_EDGE);
358	glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T,
359			GL_CLAMP_TO_EDGE);
360	glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
361
362	glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA, w, h, 0,
363		     GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pic);
364
365	/* draw */
366	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
367	glEnable(GL_TEXTURE_2D);
368
369	glViewport(0, 0, w, h);
370
371	glMatrixMode(GL_PROJECTION);
372	glLoadIdentity();
373
374	glOrtho( (GLfloat)0, (GLfloat)w, (GLfloat)0, (GLfloat)h, -1.0, 1.0);
375
376	glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 1);
377
378	glMatrixMode(GL_TEXTURE);
379	glLoadIdentity();
380
381	glBegin(GL_QUADS);
382	{
383		glTexCoord2f(0.0f, 0.0f);
384		glVertex2f(0.0f, h);
385		glTexCoord2f(0.0f, h);
386		glVertex2f(0.0f, 0.0f);
387		glTexCoord2f(w, h);
388		glVertex2f(w, 0.0f);
389		glTexCoord2f(w, 0.0f);
390		glVertex2f(w, h);
391	}
392	glEnd();
393}
394
395
396static int display(struct vidisp_st *st, const char *title,
397		   const struct vidframe *frame)
398{
399	NSAutoreleasePool *pool;
400	bool upd = false;
401	int err = 0;
402
403	pool = [[NSAutoreleasePool alloc] init];
404	if (!pool)
405		return ENOMEM;
406
407	if (!vidsz_cmp(&st->size, &frame->size)) {
408		if (st->size.w && st->size.h) {
409			info("opengl: reset: %u x %u  --->  %u x %u\n",
410			     st->size.w, st->size.h,
411			     frame->size.w, frame->size.h);
412		}
413
414		opengl_reset(st, &frame->size);
415
416		upd = true;
417	}
418
419	if (upd && st->win) {
420
421		const NSSize size = {frame->size.w, frame->size.h};
422		char capt[256];
423
424		[st->win setContentSize:size];
425
426		if (title) {
427			re_snprintf(capt, sizeof(capt), "%s - %u x %u",
428				    title, frame->size.w, frame->size.h);
429		}
430		else {
431			re_snprintf(capt, sizeof(capt), "%u x %u",
432				    frame->size.w, frame->size.h);
433		}
434
435		[st->win setTitle:[NSString stringWithUTF8String:capt]];
436
437		[st->win makeKeyAndOrderFront:nil];
438		[st->win display];
439		[st->win center];
440
441		[st->ctx clearDrawable];
442		[st->ctx setView:[st->win contentView]];
443	}
444
445	[st->ctx makeCurrentContext];
446
447	if (frame->fmt == VID_FMT_YUV420P) {
448
449		if (!st->PHandle) {
450
451			debug("opengl: using Vertex shader with YUV420P\n");
452
453			err = setup_shader(st, frame->size.w, frame->size.h);
454			if (err)
455				goto out;
456		}
457
458		draw_yuv(st->PHandle, frame->size.h,
459			 frame->data[0], frame->linesize[0],
460			 frame->data[1], frame->linesize[1],
461			 frame->data[2], frame->linesize[2]);
462		draw_blit(frame->size.w, frame->size.h);
463	}
464	else if (frame->fmt == VID_FMT_RGB32) {
465
466		glClearColor(0, 0, 0, 0);
467		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
468
469		glViewport(0, 0, frame->size.w, frame->size.h);
470
471		glMatrixMode(GL_PROJECTION);
472		glLoadIdentity();
473
474		glMatrixMode(GL_MODELVIEW);
475		glLoadIdentity();
476
477		draw_rgb(frame->data[0], frame->size.w, frame->size.h);
478	}
479	else {
480		warning("opengl: unknown pixel format %s\n",
481			vidfmt_name(frame->fmt));
482		err = EINVAL;
483	}
484
485	[st->ctx flushBuffer];
486
487 out:
488	[pool release];
489
490	return err;
491}
492
493
494static void hide(struct vidisp_st *st)
495{
496	if (!st)
497		return;
498
499	[st->win orderOut:nil];
500}
501
502
503static int module_init(void)
504{
505	NSApplication *app;
506	int err;
507
508	app = [NSApplication sharedApplication];
509	if (!app)
510		return ENOSYS;
511
512	err = vidisp_register(&vid, baresip_vidispl(),
513			      "opengl", alloc, NULL, display, hide);
514	if (err)
515		return err;
516
517	return 0;
518}
519
520
521static int module_close(void)
522{
523	vid = mem_deref(vid);
524
525	return 0;
526}
527
528
529EXPORT_SYM const struct mod_export DECL_EXPORTS(opengl) = {
530	"opengl",
531	"vidisp",
532	module_init,
533	module_close,
534};
535