1 /*
2  * Copyright 2019, Björn Ståhl
3  * License: 3-Clause BSD, see COPYING file in arcan source repository.
4  * Reference: http://arcan-fe.com
5  * Description: The headless platform video implementation, uses egl in a
6  * displayless configuration to allow local processing for testing,
7  * verification and so on, with the option of exposing the default output via
8  * the encode frameserver.
9  */
10 
11 /*
12  * TODO:
13  * [ ] map_handle should be shared with egl-dri
14  * [ ] let DPMS state and map_handle(BADID) reflect in encode-output
15  * [ ] "resolution" switch reflect in encode output
16  */
17 
18 #include <stdlib.h>
19 #include <stdint.h>
20 #include <stdbool.h>
21 #include <math.h>
22 #include <unistd.h>
23 #include <dlfcn.h>
24 #include <stdio.h>
25 #include <fcntl.h>
26 #include <sys/types.h>
27 
28 #include "arcan_math.h"
29 #include "arcan_general.h"
30 #include "arcan_video.h"
31 #include "arcan_videoint.h"
32 #include "arcan_shmif.h"
33 #include "arcan_event.h"
34 #include "arcan_audio.h"
35 #include "arcan_frameserver.h"
36 #include "arcan_conductor.h"
37 #include "arcan_event.h"
38 
39 #include "agp/glfun.h"
40 #include "../platform.h"
41 
42 #define EGL_EGLEXT_PROTOTYPES
43 #define GL_GLEXT_PROTOTYPES
44 #define MESA_EGL_NO_X11_HEADERS
45 #include <EGL/egl.h>
46 #include <EGL/eglext.h>
47 
48 #include <drm.h>
49 #include <drm_fourcc.h>
50 #include <xf86drm.h>
51 #include <gbm.h>
52 
53 static struct {
54 	size_t width;
55 	size_t height;
56 	int deadline;
57 
58 	struct {
59 		struct arcan_frameserver* outctx;
60 		bool check_output;
61 		bool flip_y;
62 		bool block;
63 	} encode;
64 
65 	struct {
66 		EGLDisplay disp;
67 		EGLContext ctx;
68 		EGLSurface surf;
69 		EGLConfig cfg;
70 		EGLNativeWindowType wnd;
71 		struct gbm_device* gbmdev;
72 	} egl;
73 
74 	struct agp_vstore* vstore;
75 } global = {
76 	.deadline = 13,
77 	.encode = {
78 		.flip_y = true
79 	}
80 };
81 
82 #ifdef _DEBUG
83 #define DEBUG 1
84 #else
85 #define DEBUG 0
86 #endif
87 
88 /*
89  * same debugging / tracing setup as in egl-dri.c
90  */
91 #define debug_print(fmt, ...) \
92             do { if (DEBUG) arcan_warning("%lld:%s:%d:%s(): " fmt "\n",\
93 						arcan_timemillis(), "egl-dri:", __LINE__, __func__,##__VA_ARGS__); } while (0)
94 
95 
96 static char* envopts[] = {
97 	"ARCAN_VIDEO_ENCODE=encode_args",
98 	"Use encode frameserver as virtual output, see afsrv_encode for format",
99 	"ARCAN_VIDEO_DISABLE_PLATFORM=1",
100 	"Use the EGL default for the GL display rather than go through gbm/mesa",
101 	"ARCAN_VIDEO_REFRESH=n",
102 	"Set the simulated vsynch to n Hz",
103 	"ARCAN_VIDEO_DEVICE=/dev/dri/renderD128",
104 	"Set the render node to an explicit path",
105 	NULL
106 };
107 
spawn_encode_output()108 static void spawn_encode_output()
109 {
110 /*
111  * Terminate a current / pending connection if one can be found
112  */
113 	if (global.encode.outctx){
114 		arcan_frameserver_free(global.encode.outctx);
115 		global.encode.outctx = NULL;
116 	}
117 
118 /*
119  * Get the parameters / options from the config- layer
120  */
121 	uintptr_t tag;
122 	char* enc_arg;
123 	cfg_lookup_fun get_config = platform_config_lookup(&tag);
124 	if (!get_config("video_encode", 0, &enc_arg, tag))
125 		return;
126 
127 /*
128  * spawn the actual process
129  */
130 	struct frameserver_envp args = {
131 		.use_builtin = true,
132 		.custom_feed = 0xfeedface,
133 		.args.builtin.mode = "encode",
134 		.args.builtin.resource = enc_arg,
135 		.init_w = global.width,
136 		.init_h = global.height
137 	};
138 	struct arcan_frameserver* fsrv = platform_launch_fork(&args, 0);
139 	if (!fsrv){
140 		arcan_warning("(headless) couldn't spawn afsrv_encode\n");
141 		return;
142 	}
143 	debug_print("encode display output enabled");
144 	global.encode.outctx = fsrv;
145 }
146 
platform_video_shutdown()147 void platform_video_shutdown()
148 {
149 	debug_print("shutting down");
150 	if (global.encode.outctx){
151 		arcan_frameserver_free(global.encode.outctx);
152 	}
153 }
154 
platform_video_prepare_external()155 void platform_video_prepare_external()
156 {
157 }
158 
platform_video_restore_external()159 void platform_video_restore_external()
160 {
161 }
platform_video_display_edid(platform_display_id did,char ** out,size_t * sz)162 bool platform_video_display_edid(
163 	platform_display_id did, char** out, size_t* sz)
164 {
165 	*out = NULL;
166 	*sz = 0;
167 	return false;
168 }
169 
platform_video_dpms(platform_display_id disp,enum dpms_state state)170 enum dpms_state platform_video_dpms(
171 	platform_display_id disp, enum dpms_state state)
172 {
173 	return ADPMS_IGNORE;
174 }
175 
platform_video_recovery()176 void platform_video_recovery()
177 {
178 }
179 
platform_video_set_display_gamma(platform_display_id did,size_t n_ramps,uint16_t * r,uint16_t * g,uint16_t * b)180 bool platform_video_set_display_gamma(platform_display_id did,
181 	size_t n_ramps, uint16_t* r, uint16_t* g, uint16_t* b)
182 {
183 	return false;
184 }
185 
platform_video_get_display_gamma(platform_display_id did,size_t * n_ramps,uint16_t ** outb)186 bool platform_video_get_display_gamma(platform_display_id did,
187 	size_t* n_ramps, uint16_t** outb)
188 {
189 	return false;
190 }
191 
platform_video_gfxsym(const char * sym)192 void* platform_video_gfxsym(const char* sym)
193 {
194 	return dlsym(RTLD_DEFAULT, sym);
195 }
196 
platform_video_minimize()197 void platform_video_minimize()
198 {
199 }
200 
platform_video_reset(int id,int swap)201 void platform_video_reset(int id, int swap)
202 {
203 }
204 
platform_video_specify_mode(platform_display_id disp,struct monitor_mode mode)205 bool platform_video_specify_mode(platform_display_id disp, struct monitor_mode mode)
206 {
207 	return false;
208 }
209 
210 /*
211  * called as external from the headless input platform
212  */
headless_flush_encode_events()213 int headless_flush_encode_events()
214 {
215 	if (!global.encode.outctx)
216 		return FRV_NOFRAME;
217 
218 /* Prevent control_chld from emitting events about the state of the frameserver
219  * (it doesn't exist in the lua space) and instead substitute it with the exit
220  * request. Ideally we should probably just switch into a wait-relaunch pattern */
221 	arcan_event_maskall(arcan_event_defaultctx());
222 	if (!arcan_frameserver_control_chld(global.encode.outctx)){
223 		arcan_warning("(headless) output encoder died\n");
224 		global.encode.outctx = NULL;
225 		arcan_event_clearmask(arcan_event_defaultctx());
226 		arcan_event ev = {
227 			.category = EVENT_SYSTEM,
228 			.sys.kind = EVENT_SYSTEM_EXIT,
229 			.sys.errcode = 256 /* EXIT_SILENT */
230 		};
231 		arcan_event_enqueue(arcan_event_defaultctx(), &ev);
232 		return FRV_NOFRAME;
233 	}
234 	arcan_event_clearmask(arcan_event_defaultctx());
235 
236 	TRAMP_GUARD(FRV_NOFRAME, global.encode.outctx);
237 	arcan_event inev;
238 
239 /* !arcan_frameserver_control_chld -> _free() -> TERMINATED event */
240 
241 	while (arcan_event_poll(&global.encode.outctx->inqueue, &inev) > 0){
242 /* allow IO events to be forwarded as if the encode frameserver was actually
243  * an input device (which in the remoting stage it is) */
244 		if (inev.category == EVENT_IO){
245 			arcan_event_enqueue(arcan_event_defaultctx(), &inev);
246 			continue;
247 		}
248 
249 		if (inev.category != EVENT_EXTERNAL)
250 			continue;
251 
252 		switch (inev.ext.kind){
253 		default:
254 			debug_print("encoder-event: %s", arcan_shmif_eventstr(&inev, NULL, 0));
255 		break;
256 		}
257 	}
258 
259 	platform_fsrv_leave();
260 	return FRV_NOFRAME;
261 }
262 
readback_encode()263 static int readback_encode()
264 {
265 /* other side is still encoding / synching so don't overwrite the buffer */
266 	struct arcan_frameserver* out = global.encode.outctx;
267 	TRAMP_GUARD(0, out);
268 
269 /* not finished, fake it until we finish */
270 	if (out->shm.ptr->vready || global.encode.block){
271 		platform_fsrv_leave();
272 		return 0;
273 	}
274 
275 /* even if the store sizes have changed for some reason, we crop to the smallest */
276 	agp_activate_rendertarget(NULL);
277 
278 	struct agp_vstore* vs = global.vstore ? global.vstore : arcan_vint_world();
279 	size_t row_len = vs->w > out->desc.width ? out->desc.width : vs->w;
280 	size_t row_sz = row_len * sizeof(av_pixel);
281 	size_t n_rows = vs->h > out->desc.height ? out->desc.height : vs->h;
282 	size_t buf_sz = vs->w * vs->h * sizeof(av_pixel);
283 
284 /* recall, alloc_mem is default FATAL unless flagged otherwise */
285 	if (buf_sz != vs->vinf.text.s_raw){
286 		arcan_mem_free(vs->vinf.text.raw);
287 		vs->vinf.text.s_raw = buf_sz;
288 		vs->vinf.text.raw = arcan_alloc_mem(vs->vinf.text.s_raw,
289 			ARCAN_MEM_VBUFFER, ARCAN_MEM_BZERO, ARCAN_MEMALIGN_PAGE
290 		);
291 	}
292 
293 /* don't really guarantee color format and coding here when it is
294  * non-normal texture2D surfaces (where we statically pick formats
295  * to avoid repack). */
296 	agp_readback_synchronous(vs);
297 
298 	bool in_dirty = false;
299 	size_t x1 = row_len - 1;
300 	size_t x2 = 0, y1 = 0, y2 = 0;
301 
302 	shmif_pixel* dst = out->vbufs[0];
303 	shmif_pixel* src = vs->vinf.text.raw;
304 
305 	size_t dst_row = n_rows - 1;
306 	int dst_step = -1;
307 
308 	if (!global.encode.flip_y){
309 		dst_row = 0;
310 		dst_step = 1;
311 	}
312 
313 	for (size_t row = 0; row < n_rows; row++, dst_row += dst_step){
314 		av_pixel acc = 0;
315 
316 		for (size_t px = 0; px < row_len; px++){
317 			av_pixel a = src[row     * vs->w           + px];
318 			av_pixel b = dst[dst_row * out->desc.width + px];
319 			acc = acc | (a ^ b);
320 		}
321 
322 		if (!acc)
323 			continue;
324 
325 		if (!in_dirty){
326 			in_dirty = true;
327 			y1 = dst_row;
328 			y2 = dst_row;
329 		}
330 		else
331 			y1 = dst_row;
332 
333 /* grow / shrink the bounding volume */
334 		for (size_t tx = 0; tx < x1; tx++){
335 			if (
336 					(dst[dst_row * out->desc.width + tx] ^
337 					 src[row * out->desc.width + tx]) != 0){
338 				x1 = tx;
339 				break;
340 			}
341 		}
342 
343 		for (size_t tx = row_len-1; tx > x2; tx--){
344 			if (
345 					(dst[dst_row * out->desc.width + tx] ^
346 					 src[row * out->desc.width + tx]) != 0){
347 				x2 = tx;
348 				break;
349 			}
350 		}
351 
352 		memcpy(&dst[dst_row * out->desc.width], &src[row * vs->w], row_sz);
353 	}
354 
355 	if (!in_dirty){
356 		platform_fsrv_leave();
357 		return 1;
358 	}
359 
360 /* flag ok and commit dirty region */
361 	global.encode.outctx->shm.ptr->hints |= SHMIF_RHINT_SUBREGION;
362 
363 	struct arcan_shmif_region dirty = {
364 		.x1 = x1, .y1 = y1,
365 		.x2 = x2, .y2 = y2
366 	};
367 
368 	atomic_store(&global.encode.outctx->shm.ptr->dirty, dirty);
369 	atomic_store_explicit(
370 		&global.encode.outctx->shm.ptr->vready, true, memory_order_seq_cst);
371 
372 /* encode has more explicit frame signalling until we have futexes */
373 	platform_fsrv_pushevent(global.encode.outctx, &(struct arcan_event){
374 		.tgt.kind = TARGET_COMMAND_STEPFRAME,
375 		.category = EVENT_TARGET,
376 		.tgt.ioevs[0] = global.encode.outctx->vfcount++
377 	});
378 
379 	platform_fsrv_leave();
380 	return 1;
381 }
382 
platform_video_synch(uint64_t tick_count,float fract,video_synchevent pre,video_synchevent post)383 void platform_video_synch(uint64_t tick_count, float fract,
384 	video_synchevent pre, video_synchevent post)
385 {
386 	if (pre)
387 		pre();
388 
389 /*
390  * we can't spawn this in platform init as the agp_ and video stack context
391  * isn't available at that stage so it needs to be deferred here
392  */
393 	if (!global.encode.check_output && !global.encode.outctx){
394 		global.encode.check_output = true;
395 		spawn_encode_output();
396 	}
397 
398 /*
399  * normal refresh cycle, then leave the next possible deadline to the conductor
400  */
401 	size_t nd;
402 	arcan_bench_register_cost( arcan_vint_refresh(fract, &nd) );
403 
404 /*
405  * if there is no encoder listening run with the estimated fake synch
406  */
407 	if (!nd || !global.encode.outctx){
408 		arcan_conductor_fakesynch(global.deadline);
409 	}
410 
411 /*
412  * if there is an encoder set, try to synch it or 'fake-+yield' until
413  * the deadline has elapsed or synch succeeded
414  */
415 	else{
416 		unsigned long deadline = arcan_timemillis() + global.deadline;
417 
418 		while (!readback_encode()){
419 			unsigned step = arcan_conductor_yield(NULL, 0);
420 			if (arcan_timemillis() + step < deadline)
421 				arcan_timesleep(step);
422 		}
423 	}
424 
425 	if (post)
426 		post();
427 }
428 
platform_video_auth(int cardn,unsigned token)429 bool platform_video_auth(int cardn, unsigned token)
430 {
431 	return false;
432 }
433 
platform_video_cardhandle(int cardn,int * buffer_method,size_t * metadata_sz,uint8_t ** metadata)434 int platform_video_cardhandle(int cardn,
435 		int* buffer_method, size_t* metadata_sz, uint8_t** metadata)
436 {
437 	return -1;
438 }
439 
platform_video_envopts()440 const char** platform_video_envopts()
441 {
442 	return (const char**) envopts;
443 }
444 
platform_video_query_displays()445 void platform_video_query_displays()
446 {
447 }
448 
platform_video_displays(platform_display_id * dids,size_t * lim)449 size_t platform_video_displays(platform_display_id* dids, size_t* lim)
450 {
451 	if (dids && lim && *lim > 0){
452 		dids[0] = 0;
453 	}
454 
455 	if (lim)
456 		*lim = 1;
457 
458 	return 1;
459 }
460 
platform_video_map_handle(struct agp_vstore * dst,int64_t handle)461 bool platform_video_map_handle(struct agp_vstore* dst, int64_t handle)
462 {
463 	return false;
464 }
465 
platform_video_set_mode(platform_display_id disp,platform_mode_id mode,struct platform_mode_opts opts)466 bool platform_video_set_mode(platform_display_id disp,
467 	platform_mode_id mode, struct platform_mode_opts opts)
468 {
469 	return disp == 0 && mode == 0;
470 }
471 
platform_video_dimensions()472 struct monitor_mode platform_video_dimensions()
473 {
474 	return (struct monitor_mode){
475 		.width = global.width,
476 		.height = global.height
477 	};
478 }
479 
platform_video_query_modes(platform_display_id id,size_t * count)480 struct monitor_mode* platform_video_query_modes(
481 	platform_display_id id, size_t* count)
482 {
483 	static struct monitor_mode mode = {};
484 	mode.width  = global.width;
485 	mode.height = global.height;
486 	mode.depth  = sizeof(av_pixel) * 8;
487 	mode.refresh = 60; /* should be queried */
488 
489 	*count = 1;
490 	return &mode;
491 }
492 
platform_video_invalidate_map(struct agp_vstore * vstore,struct agp_region region)493 void platform_video_invalidate_map(
494 	struct agp_vstore* vstore, struct agp_region region)
495 {
496 /* NOP for the time being - might change for direct forwarding of client */
497 }
498 
platform_video_map_display(arcan_vobj_id vid,platform_display_id id,enum blitting_hint hint)499 bool platform_video_map_display(
500 	arcan_vobj_id vid, platform_display_id id, enum blitting_hint hint)
501 {
502 	struct display_layer_cfg cfg = {
503 		.opacity = 1.0,
504 		.hint = hint
505 	};
506 
507 	return platform_video_map_display_layer(vid, id, 0, cfg) >= 0;
508 }
509 
platform_video_map_display_layer(arcan_vobj_id id,platform_display_id disp,size_t layer_index,struct display_layer_cfg cfg)510 ssize_t platform_video_map_display_layer(arcan_vobj_id id,
511 	platform_display_id disp, size_t layer_index, struct display_layer_cfg cfg)
512 {
513 	if (disp != 0 || layer_index > 0)
514 		return -1;
515 
516 	arcan_vobject* vobj = arcan_video_getobject(id);
517 
518 /*
519  * unmap any existing one
520  */
521 	if (global.vstore && global.vstore != arcan_vint_world()){
522 		arcan_vint_drop_vstore(global.vstore);
523 		global.vstore = NULL;
524 	}
525 
526 /*
527  * disable output temporarily if it's there
528  */
529 	if (id == ARCAN_EID){
530 		global.encode.block = true;
531 		return 0;
532 	}
533 
534 	global.encode.block = false;
535 
536 /*
537  * switch to the global output
538  */
539 	if (id == ARCAN_VIDEO_WORLDID || !vobj){
540 		arcan_warning("(headless) map display, worldid or no object, invert-y\n");
541 		global.encode.flip_y = true;
542 		return 0;
543 	}
544 
545 	if (vobj->vstore->txmapped != TXSTATE_TEX2D){
546 		arcan_warning("(headless) map display called with bad source vobj\n");
547 		return -1;
548 	}
549 
550 /*
551  * Refcount the new and mark as mapped, this is the place to add an indirect
552  * rendertarget- apply/transform pass in order to handle mapping hints. Should
553  * possible be done in the AGP stage though in the same way we should handle
554  * repack-reblit
555  */
556 	vobj->vstore->refcount++;
557 	bool isrt = arcan_vint_findrt(vobj) != NULL;
558 	global.encode.flip_y = !isrt;
559 	global.vstore = vobj->vstore;
560 	arcan_warning("(headless) mapped source, invert-y: %d\n", global.encode.flip_y);
561 
562 	return 0;
563 }
564 
platform_video_decay()565 size_t platform_video_decay()
566 {
567 	return 0;
568 }
569 
570 /*
571  * this should be solvable with the same code used by egl-dri
572  */
platform_video_map_buffer(struct agp_vstore * vs,struct agp_buffer_plane * planes,size_t n)573 bool platform_video_map_buffer(
574 	struct agp_vstore* vs, struct agp_buffer_plane* planes, size_t n)
575 {
576 	return false;
577 }
578 
platform_video_capstr()579 const char* platform_video_capstr()
580 {
581 	return "Video Platform (HEADLESS)";
582 }
583 
platform_video_preinit()584 void platform_video_preinit()
585 {
586 }
587 
lookup_fenv(void * tag,const char * sym,bool req)588 static void* lookup_fenv(void* tag, const char* sym, bool req)
589 {
590 	return eglGetProcAddress(sym);
591 }
592 
platform_video_init(uint16_t width,uint16_t height,uint8_t bpp,bool fs,bool frames,const char * capt)593 bool platform_video_init(uint16_t width,
594 	uint16_t height, uint8_t bpp, bool fs, bool frames, const char* capt)
595 {
596 	global.width = width;
597 	global.height = height;
598 
599 /* some trival default as default is -w 0 -h 0 */
600 	if (!global.width)
601 		global.width = 640;
602 	if (!global.height)
603 		global.height = 480;
604 
605 	const EGLint attribs[] = {
606 		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
607 		EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
608 		EGL_RED_SIZE, 8,
609 		EGL_BLUE_SIZE, 8,
610 		EGL_ALPHA_SIZE, 8,
611 		EGL_DEPTH_SIZE, 16,
612 		EGL_NONE
613 	};
614 
615 	int major_version = 2;
616 	int minor_version = 1;
617 
618 /* Normal EGL progression:
619  * API -> Display -> Configuration -> Context */
620 	if (strcmp(agp_ident(), "OPENGL21") == 0){
621 		if (!eglBindAPI(EGL_OPENGL_API)){
622 			arcan_warning("(headless) couldn't bind openGL API\n");
623 			return false;
624 		}
625 	}
626 	else if (strcmp(agp_ident(), "GLES3") == 0){
627 		if (!eglBindAPI(EGL_OPENGL_ES_API)){
628 			arcan_warning("(headless) couldn't bind gles- API\n");
629 			return false;
630 		}
631 #ifdef GLES3
632 		major_version = 3;
633 #endif
634 		minor_version = 0;
635 	}
636 	else {
637 		arcan_fatal("unhandled agp platform: %s\n", agp_ident());
638 		return false;
639 	}
640 
641 	PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display =
642 		(PFNEGLGETPLATFORMDISPLAYEXTPROC)
643 		eglGetProcAddress("eglGetPlatformDisplayEXT");
644 	debug_print("platform_display_support: %d", get_platform_display != NULL);
645 
646 	uintptr_t tag;
647 	cfg_lookup_fun get_config = platform_config_lookup(&tag);
648 
649 /* this is not right for nvidia, and would possibly pick nouveau even in the
650  * presence of the binary driver, we have the same issue with streams */
651 	if (!get_config("video_disable_platform", 0, NULL, tag) && get_platform_display){
652 		char* node;
653 		int devfd = -1;
654 
655 /* let the user control which specific render node, since we "don't"
656  * have a display server to connect to we don't really have any way
657  * of knowing which one to use */
658 		if (get_config("video_device", 0, &node, tag)){
659 			devfd = open(node, O_RDWR | O_CLOEXEC);
660 			free(node);
661 			debug_print("using device: %s", node);
662 		}
663 
664 		if (-1 == devfd){
665 			devfd = open("/dev/dri/renderD128", O_RDWR | O_CLOEXEC);
666 		}
667 
668 /* the render node / device could be open, start with gbm, this might
669  * trigger the nouveau problem above */
670 		if (-1 != devfd){
671 			global.egl.gbmdev = gbm_create_device(devfd);
672 			debug_print("gbm device: %d", global.egl.gbmdev != NULL);
673 
674 			if (global.egl.gbmdev){
675 				global.egl.disp = get_platform_display(
676 					EGL_PLATFORM_GBM_KHR, (void*) global.egl.gbmdev, NULL);
677 				debug_print("gbm platform: %d", global.egl.disp != NULL);
678 			}
679 		}
680 
681 /* last resort, try the mesa surfaceless one to get software rendering */
682 		if (!global.egl.disp){
683 #ifndef EGL_PLATFORM_SURFACELESS_MESA
684 #define EGL_PLATFORM_SURFACELESS_MESA 0x31DD
685 #endif
686 		arcan_warning("couldn't get a gbm platform, trying surfaceless/software\n");
687 		global.egl.disp = get_platform_display(
688 			EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, NULL);
689 		}
690 	}
691 
692 /* if we don't have the option to specify a platform display, just
693  * go with whatever the default display happens to be */
694 	if (!global.egl.disp){
695 		global.egl.disp = eglGetDisplay((EGLNativeDisplayType) NULL);
696 		debug_print("fallback / default egl 'NULL' platform");
697 	}
698 
699 	EGLint major, minor;
700 	if (!eglInitialize(global.egl.disp, &major, &minor)){
701 		arcan_warning("(headless) couldn't initialize EGL\n");
702 		return false;
703 	}
704 
705 /* workaround for GLdispatch filtering the dlsym lookup of functions,
706  * this should probably get someting less ret^H ill-conceived */
707 	static struct agp_fenv fenv;
708 	agp_glinit_fenv(&fenv, lookup_fenv, NULL);
709 	agp_setenv(&fenv);
710 
711 	EGLint nc;
712 	if (!eglGetConfigs(global.egl.disp, NULL, 0, &nc) || 0 == nc){
713 		arcan_warning("(headless) no valid EGL configuration\n");
714 		return false;
715 	}
716 
717 	if (!eglChooseConfig(global.egl.disp, attribs, &global.egl.cfg, 1, &nc)){
718 		arcan_warning("(headless) couldn't pick a suitable EGL configuration\n");
719 		return false;
720 	}
721 
722 /*
723  * Default is ~75Hz (no real need to be very precise, but % logic clock) Then
724  * let user override. This will only be effective if we don't tie the output to
725  * the encode/remoting stage.
726  */
727 	char* node;
728 	if (get_config("video_refresh", 0, &node, tag)){
729 		float hz = strtoul("node", NULL, 10);
730 		if (hz)
731 			global.deadline = 1.0 / hz;
732 		free(node);
733 		debug_print("deadline changed to %d", global.deadline);
734 	}
735 
736 	EGLint cas[] = {
737 		EGL_CONTEXT_CLIENT_VERSION, 2,
738 		EGL_NONE, EGL_NONE,
739 		EGL_NONE, EGL_NONE,
740 		EGL_NONE, EGL_NONE,
741 		EGL_NONE, EGL_NONE,
742 		EGL_NONE
743 	};
744 
745 	int ofs = 2;
746 	cas[ofs++] = EGL_CONTEXT_MAJOR_VERSION_KHR;
747 	cas[ofs++] = major_version;
748 	cas[ofs++] = EGL_CONTEXT_MINOR_VERSION_KHR;
749 	cas[ofs++] = minor_version;
750 
751 	global.egl.ctx =
752 		eglCreateContext(global.egl.disp, global.egl.cfg, NULL, cas);
753 
754 	if (!global.egl.ctx)
755 		return false;
756 
757 /*
758  * Options:
759  *  EGL_KHR_Surfaceless_Context
760  *  Pbuffer
761  */
762 
763 	eglMakeCurrent(
764 		global.egl.disp, EGL_NO_SURFACE, EGL_NO_SURFACE, global.egl.ctx);
765 
766 	return true;
767 }
768