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