1 /*
2  * Björn Ståhl
3  * License: 3-Clause BSD, see COPYING file in the arcan source repository.
4  * Reference: https://arcan-fe.com
5  * Description: The conductor is responsible for figuring out what to synch
6  * and what to do in the periods of time where one or many displays and GPUs
7  * are busy synching. Since this is a task with many tradeoffs and context
8  * specific heuristics, it exposes a user facing set of strategies to chose
9  * from.
10  */
11 #include <stdint.h>
12 #include <stdlib.h>
13 #include <inttypes.h>
14 #include <stdbool.h>
15 #include <stdio.h>
16 #include <unistd.h>
17 #include <stdatomic.h>
18 
19 #include "arcan_math.h"
20 #include "arcan_general.h"
21 #include "arcan_shmif.h"
22 #include "arcan_event.h"
23 #include "arcan_audio.h"
24 #include "arcan_frameserver.h"
25 #include "arcan_conductor.h"
26 #include "arcan_lua.h"
27 #include "arcan_video.h"
28 #include "arcan_videoint.h"
29 #include "arcan_mem.h"
30 
31 #include "../platform/platform.h"
32 #include "../platform/video_platform.h"
33 
34 /* defined in platform.h, used in psep open, shared memory */
35 _Atomic uint64_t* volatile arcan_watchdog_ping = NULL;
36 static size_t gpu_lock_bitmap;
37 
arcan_conductor_enable_watchdog()38 void arcan_conductor_enable_watchdog()
39 {
40 	arcan_watchdog_ping = arcan_alloc_mem(system_page_size,
41 		ARCAN_MEM_SHARED, ARCAN_MEM_BZERO, ARCAN_MEMALIGN_NATURAL);
42 	atomic_store(arcan_watchdog_ping, arcan_timemillis());
43 }
44 
arcan_conductor_toggle_watchdog()45 void arcan_conductor_toggle_watchdog()
46 {
47 	if (arcan_watchdog_ping){
48 		if (atomic_load(arcan_watchdog_ping)){
49 			atomic_store(arcan_watchdog_ping, 0);
50 		}
51 		else
52 			atomic_store(arcan_watchdog_ping, arcan_timemillis());
53 	}
54 }
55 
56 /*
57  * checklist:
58  *  [ ] Actual setup to realtime- plot the different timings and stages. This can
59  *      be done partially now in that the key sites are instrumented and can log
60  *      into a buffer accessible from Lua, but for faster/better realtime the
61  *      build should be patched to map those points to tracey.
62  *
63  *  [ ] dma-bufs are getting a sync_file API for better fencing which should be
64  *      added to the compose- eval ("can we finish the current set within the
65  *      time alotted"), and before that propagates, we should use the
66  *      pending-set part along with POLLIN on the dma-buf set to determine if
67  *      we should compose with the new set or the last-safe set.
68  *
69  *  [ ] parallelize PBO uploads
70  *      (thought: test the systemic effects of not doing shm->gpu in process but
71  *      rather have an 'uploader proxy' (like we'd do with wayland) and pass the
72  *      descriptors around instead.
73  *
74  *  [x] perform resize- ack during synch period
75  *      [ ] multi-thread resize-ack/evproc.
76  *      right now we are 'blocking' on resize- still, though there aren't any
77  *      GPU resources modified directly based on the resize stage as such, those
78  *      are deferred until the actual frame commit. The later are still hard to
79  *      multithread, but just ack-/verify- should be easier.
80  *
81  *  [ ] posix anon-semaphores on shmpage (OSX blocking)
82  *      or drop the semaphores entirely (yes please) and switch to futexes, alas
83  *      then we still have the problem of those not being a multiplexable primitives
84  *      and needing a separate path for OSX.
85  *
86  *  [ ] defer GCs to low-load / embarassing pause in thread during synch etc.
87  *      since we now 'know' when we are waiting for the GPU to unlock, this is a
88  *      good spot to manually step the Lua GCing.
89  *
90  *  [ ] perform readbacks in possible delay periods might break some GPU drivers
91  *
92  *  [ ] thread rendertarget processing
93  *      this would again be better for something like vulkan where we tie the
94  *      rendertarget to a unique pipeline (they are much alike)
95  */
96 static struct {
97 	uint64_t tick_count;
98 	int64_t set_deadline;
99 	double render_cost;
100 	double transfer_cost;
101 	uint8_t timestep;
102 	bool in_frame;
103 } conductor = {
104 	.render_cost = 4,
105 	.transfer_cost = 1,
106 	.timestep = 2
107 };
108 
109 static ssize_t find_frameserver(struct arcan_frameserver* fsrv);
110 
111 /*
112  * To add new options here,
113  *
114  *  Add the corresponding lock- setting to setsynch then add preframe hook at
115  *  preframe_synch and deadline adjustment in postframe_synch. Any 'during
116  *  synch' yield specific options goes into conductor_yield. For strategies
117  *  that work with a focus target, also handle arcan_conductor_focus.
118  */
119 static char* synchopts[] = {
120 	"vsynch", "release clients on vsynch",
121 	"immediate", "release clients as soon as buffers are synched",
122 	"processing", "synch to ready displays, 100% CPU",
123 	"powersave", "synch to clock tick (~25Hz)",
124 	"adaptive", "defer composition",
125 	"tight", "defer composition, delay client-wake",
126 	NULL
127 };
128 
129 static struct {
130 	struct arcan_frameserver** ref;
131 	size_t count;
132 	size_t used;
133 	struct arcan_frameserver* focus;
134 } frameservers;
135 
136 enum synchopts {
137 /* wait for display, wake clients after vsynch */
138 	SYNCH_VSYNCH = 0,
139 /* wait for display, wake client after buffer ack */
140 	SYNCH_IMMEDIATE,
141 /* don't wait for display, wake client immediately */
142 	SYNCH_PROCESSING,
143 /* update display on slow clock (25Hz), wake clients after display */
144 	SYNCH_POWERSAVE,
145 /* defer composition, wake clients after vsynch */
146 	SYNCH_ADAPTIVE,
147 /* defer composition, wake clients after half-time */
148 	SYNCH_TIGHT
149 };
150 
151 static int synchopt = SYNCH_IMMEDIATE;
152 
153 /*
154  * difference between step/unlock is that step performs a polling step
155  * where transfers might occur, unlock simply awakes clients that did
156  * contribute a frame last pass but has been locked since
157  */
unlock_herd()158 static void unlock_herd()
159 {
160 	for (size_t i = 0; i < frameservers.count; i++)
161 		if (frameservers.ref[i]){
162 			TRACE_MARK_ONESHOT("conductor", "synchronization",
163 				TRACE_SYS_DEFAULT, frameservers.ref[i]->vid, 0, "unlock-herd");
164 			arcan_frameserver_releaselock(frameservers.ref[i]);
165 		}
166 }
167 
step_herd(int mode)168 static void step_herd(int mode)
169 {
170 	uint64_t start = arcan_timemillis();
171 
172 	TRACE_MARK_ENTER("conductor", "synchronization",
173 		TRACE_SYS_DEFAULT, mode, 0, "step-herd");
174 
175 	arcan_frameserver_lock_buffers(0);
176 	arcan_video_pollfeed();
177 	arcan_frameserver_lock_buffers(mode);
178 	uint64_t stop = arcan_timemillis();
179 
180 	conductor.transfer_cost =
181 		0.8 * (double)(stop - start) +
182 		0.2 * conductor.transfer_cost;
183 
184 	TRACE_MARK_ENTER("conductor", "synchronization",
185 		TRACE_SYS_DEFAULT, mode, conductor.transfer_cost, "step-herd");
186 }
187 
internal_yield()188 static void internal_yield()
189 {
190 	arcan_event_poll_sources(arcan_event_defaultctx(), conductor.timestep);
191 	TRACE_MARK_ONESHOT("conductor", "yield",
192 		TRACE_SYS_DEFAULT, 0, conductor.timestep, "step");
193 }
194 
alloc_frameserver_struct()195 static void alloc_frameserver_struct()
196 {
197 	if (frameservers.ref)
198 		return;
199 
200 	frameservers.count = 16;
201 	size_t buf_sz = sizeof(void*) * frameservers.count;
202 	frameservers.ref = arcan_alloc_mem(buf_sz,
203 		ARCAN_MEM_VSTRUCT, ARCAN_MEM_BZERO, ARCAN_MEMALIGN_NATURAL);
204 
205 	memset(frameservers.ref, '\0', sizeof(void*) * frameservers.count);
206 }
207 
arcan_conductor_lock_gpu(size_t gpu_id,int fence,arcan_gpu_lockhandler lockh)208 void arcan_conductor_lock_gpu(
209 	size_t gpu_id, int fence, arcan_gpu_lockhandler lockh)
210 {
211 /*
212  * this interface needs some more thinking, but the callback chain will
213  * be that video_synch -> lock_gpu[gpu_id, fence_fd] and a process callback
214  * when there's data on the fence_fd (which might potentially call unlock)
215  */
216 	gpu_lock_bitmap |= 1 << gpu_id;
217 	TRACE_MARK_ENTER("conductor", "gpu", TRACE_SYS_DEFAULT, gpu_id, 0, "");
218 }
219 
arcan_conductor_release_gpu(size_t gpu_id)220 void arcan_conductor_release_gpu(size_t gpu_id)
221 {
222 	gpu_lock_bitmap &= ~(1 << gpu_id);
223 	TRACE_MARK_EXIT("conductor", "gpu", TRACE_SYS_DEFAULT, gpu_id, 0, "");
224 }
225 
arcan_conductor_gpus_locked()226 size_t arcan_conductor_gpus_locked()
227 {
228 	return gpu_lock_bitmap;
229 }
230 
arcan_conductor_register_display(size_t gpu_id,size_t disp_id,enum synch_method method,float rate,arcan_vobj_id obj)231 void arcan_conductor_register_display(size_t gpu_id,
232 		size_t disp_id, enum synch_method method, float rate, arcan_vobj_id obj)
233 {
234 /* need to know which display and which vobj is needed to be updated in order
235  * to fulfill the requirement of the display synch so that we can schedule it
236  * accordingly, later the full DAG- would also be calculated here to resolve
237  * which agp- stores are involved and if they have an affinity on a locked
238  * GPU or not so that we can MT GPU updates */
239 	char buf[48];
240 	snprintf(buf, 48, "register:%zu:%zu:%zu", gpu_id, disp_id, (size_t) obj);
241 	TRACE_MARK_ONESHOT("conductor", "display", TRACE_SYS_DEFAULT, gpu_id, 0, buf);
242 }
243 
arcan_conductor_release_display(size_t gpu_id,size_t disp_id)244 void arcan_conductor_release_display(size_t gpu_id, size_t disp_id)
245 {
246 /* remove from set of known displays so its rate doesn't come into account */
247 	char buf[24];
248 	snprintf(buf, 24, "release:%zu:%zu", gpu_id, disp_id);
249 	TRACE_MARK_ONESHOT("conductor", "display", TRACE_SYS_DEFAULT, gpu_id, 0, buf);
250 }
251 
arcan_conductor_register_frameserver(struct arcan_frameserver * fsrv)252 void arcan_conductor_register_frameserver(struct arcan_frameserver* fsrv)
253 {
254 	size_t dst_i = 0;
255 	alloc_frameserver_struct();
256 
257 /* safeguard */
258 	ssize_t src_i = find_frameserver(fsrv);
259 	if (-1 != src_i){
260 		TRACE_MARK_ONESHOT("conductor", "frameserver",
261 			TRACE_SYS_ERROR, fsrv->vid, 0, "add on known");
262 		return;
263 
264 	}
265 
266 /* check for a gap */
267 	if (frameservers.used < frameservers.count){
268 		for (size_t i = 0; i < frameservers.count; i++){
269 			if (!frameservers.ref[i]){
270 				dst_i = i;
271 				break;
272 			}
273 		}
274 	}
275 /* or grow and add */
276 	else {
277 		size_t nbuf_sz = frameservers.count * 2 * sizeof(void*);
278 		struct arcan_frameserver** newref = arcan_alloc_mem(
279 			nbuf_sz, ARCAN_MEM_VSTRUCT, ARCAN_MEM_BZERO, ARCAN_MEMALIGN_NATURAL);
280 		memcpy(newref, frameservers.ref, frameservers.count * sizeof(void*));
281 		arcan_mem_free(frameservers.ref);
282 		frameservers.ref = newref;
283 		dst_i = frameservers.count;
284 		frameservers.count *= 2;
285 	}
286 
287 	frameservers.used++;
288 	frameservers.ref[dst_i] = fsrv;
289 	TRACE_MARK_ONESHOT("conductor", "frameserver",
290 		TRACE_SYS_DEFAULT, fsrv->vid, 0, "register");
291 
292 /*
293  * other approach is to run a monitor thread here that futexes on the flags
294  * and responds immediately instead, moving the polling etc. details to the
295  * other layers.
296  *
297  * what to keep in mind then is the whole 'events go from Lua, drain-function
298  * from the threads isnt safe'
299  */
300 }
301 
arcan_conductor_yield(struct conductor_display * disps,size_t pset_count)302 int arcan_conductor_yield(struct conductor_display* disps, size_t pset_count)
303 {
304 	arcan_audio_refresh();
305 
306 /* by returning false here we tell the platform to not even wait for synch
307  * signal from screens but rather continue immediately */
308 	if (synchopt == SYNCH_PROCESSING){
309 		TRACE_MARK_ONESHOT("conductor", "display",
310 			TRACE_SYS_FAST, 0, frameservers.used, "synch-processing");
311 		return -1;
312 	}
313 
314 	for (size_t i=0, j=frameservers.used; i < frameservers.count && j > 0; i++){
315 		if (frameservers.ref[i]){
316 			arcan_vint_pollfeed(frameservers.ref[i]->vid, false);
317 			j--;
318 		}
319 	}
320 
321 /* same as other timesleep calls, should be replaced with poll and pollset */
322 	return conductor.timestep;
323 }
324 
find_frameserver(struct arcan_frameserver * fsrv)325 static ssize_t find_frameserver(struct arcan_frameserver* fsrv)
326 {
327 	for (size_t i = 0; i < frameservers.count; i++)
328 		if (frameservers.ref[i] == fsrv)
329 			return i;
330 
331 	return -1;
332 }
333 
arcan_conductor_synchopts()334 const char** arcan_conductor_synchopts()
335 {
336 	return (const char**) synchopts;
337 }
338 
arcan_conductor_setsynch(const char * arg)339 void arcan_conductor_setsynch(const char* arg)
340 {
341 	int ind = 0;
342 
343 	while(synchopts[ind]){
344 		if (strcmp(synchopts[ind], arg) == 0){
345 			synchopt = (ind > 0 ? ind / 2 : ind);
346 			break;
347 		}
348 
349 		ind += 2;
350 	}
351 
352 	TRACE_MARK_ONESHOT("conductor",
353 		"synchronization", TRACE_SYS_DEFAULT, 0, 0, arg);
354 
355 /*
356  * There might be small conflicts of unlock/lock behavior as the strategies
357  * swap, solving the state transition graphics to get this entirely seamless is
358  * probably not worth it.
359  * 0: always, 1: no buffers, 2: allow buffers, manual release
360  */
361 	switch (synchopt){
362 	case SYNCH_VSYNCH:
363 	case SYNCH_ADAPTIVE:
364 	case SYNCH_POWERSAVE:
365 		arcan_frameserver_lock_buffers(2);
366 	break;
367 	case SYNCH_TIGHT:
368 		arcan_frameserver_lock_buffers(2);
369 	break;
370 	case SYNCH_IMMEDIATE:
371 	case SYNCH_PROCESSING:
372 		arcan_frameserver_lock_buffers(0);
373 		unlock_herd();
374 	break;
375 	}
376 }
377 
arcan_conductor_focus(struct arcan_frameserver * fsrv)378 void arcan_conductor_focus(struct arcan_frameserver* fsrv)
379 {
380 /* Focus- based strategies might need to do special flag modification both
381  * on set and remove. This table takes care of 'unset' */
382 	switch (synchopt){
383 	default:
384 	break;
385 	}
386 
387 	ssize_t dst_i = fsrv ? find_frameserver(fsrv) : -1;
388 	if (-1 == dst_i)
389 		return;
390 
391 	if (frameservers.focus)
392 		frameservers.focus->flags.locked = false;
393 
394 	TRACE_MARK_ONESHOT("conductor", "synchronization",
395 		TRACE_SYS_DEFAULT, fsrv->vid, 0, "synch-focus");
396 
397 /* And this is for 'set' */
398 	frameservers.focus = fsrv;
399 
400 	switch(synchopt){
401 	default:
402 	break;
403 	}
404 }
405 
arcan_conductor_deregister_frameserver(struct arcan_frameserver * fsrv)406 void arcan_conductor_deregister_frameserver(struct arcan_frameserver* fsrv)
407 {
408 /* not all present frameservers will be registered with the conductor */
409 	ssize_t dst_i = find_frameserver(fsrv);
410 	if (!frameservers.used || -1 == dst_i){
411 		arcan_warning("deregister_frameserver() on unknown fsrv @ %zd\n", dst_i);
412 		return;
413 	}
414 	frameservers.ref[dst_i] = NULL;
415 	frameservers.used--;
416 
417 	if (fsrv == frameservers.focus){
418 		TRACE_MARK_ONESHOT("conductor", "frameserver",
419 			TRACE_SYS_DEFAULT, fsrv->vid, 0, "lost-focus");
420 
421 		frameservers.focus = NULL;
422 	}
423 
424 	TRACE_MARK_ONESHOT("conductor", "frameserver",
425 		TRACE_SYS_DEFAULT, fsrv->vid, 0, "deregister");
426 
427 /* the real work here comes when we do multithreaded processing */
428 }
429 
430 extern struct arcan_luactx* main_lua_context;
431 static size_t event_count;
process_event(arcan_event * ev,int drain)432 static bool process_event(arcan_event* ev, int drain)
433 {
434 /*
435  * The event_counter here is used to determine if we should forward an
436  * end-of-stream marker in order to let the lua scripts also have some buffer
437  * window. It will trigger the '_input_end" event handler as a flush trigger.
438  *
439  * The cases where that is interesting and relevant is expensive actions where
440  * there is buffer backpressure on something (mouse motion in drag-rz)
441  * (buffer-bloat mitigation) and scenarios like:
442  *
443  *  (local event -> lua event -> displayhint -> [client resizes quickly] ->
444  *  resize event -> next local event)
445  *
446  * to instead allow:
447  *
448  *  (local event -> lua event -> [accumulator])
449  *  (end marker -> lua event end -> apply accumulator)
450  */
451 	if (!ev){
452 		if (event_count){
453 			event_count = 0;
454 			arcan_lua_pushevent(main_lua_context, NULL);
455 		}
456 
457 		return true;
458 	}
459 
460 	event_count++;
461 	arcan_lua_pushevent(main_lua_context, ev);
462 	return true;
463 }
464 
465 /*
466  * The case for this one is slightly different to process_event (which is used
467  * for flushing the master queue).
468  *
469  * Processing frameservers might cause a queue transfer that might block
470  * if the master event queue is saturated above a safety threshold.
471  *
472  * It is possible to opt-in allow certain frameservers to exceed this
473  * threshold, as well as process 'direct to drain' without being multiplexed on
474  * the master queue, saving a copy/queue. This breaks ordering guarantees so
475  * can only safely be enabled at the preroll synchpoint.
476  *
477  * This can also be used to force inject events when the video pipeline is
478  * blocked due to scanout through arcan_event_denqueue. The main engine itself
479  * has no problems with manipulating the video pipeline during scanout, but the
480  * driver stack and agp layer (particularly GL) may be of an entirely different
481  * opinion.
482  *
483  * Therefore there is a soft contract:
484  *
485  * (lua:input_raw) that will only be triggered if an input event sample is
486  * received as a drain-enqueue while we are also locked to scanout. If the
487  * entry point is implemented, the script 'promises' to not perform actions
488  * that would manipulate GPU state (readbacks, rendertarget updates, ...).
489  *
490  * input_raw can also _defer_ input sample processing:
491  *           denqueue:
492  *             overflow_drain:
493  *               lua:input_raw:
494  *                 <- false (nak)
495  *             <- false (nak)
496  *           attempt normal enqueue <- this is problematic then as that
497  *
498  *
499  */
overflow_drain(arcan_event * ev,int drain)500 static bool overflow_drain(arcan_event* ev, int drain)
501 {
502 	if (arcan_lua_pushevent(main_lua_context, ev)){
503 		event_count++;
504 		return true;
505 	}
506 
507 	return false;
508 }
509 
estimate_frame_cost()510 static int estimate_frame_cost()
511 {
512 	return conductor.render_cost + conductor.transfer_cost + conductor.timestep;
513 }
514 
preframe_synch(int next,int elapsed)515 static bool preframe_synch(int next, int elapsed)
516 {
517 	switch(synchopt){
518 	case SYNCH_ADAPTIVE:{
519 		ssize_t margin = next - estimate_frame_cost();
520 		if (elapsed > margin){
521 			internal_yield();
522 			return false;
523 		}
524 
525 		TRACE_MARK_ONESHOT("conductor", "synchronization",
526 			TRACE_SYS_DEFAULT, 0, elapsed - margin, "adaptive-deadline");
527 		return true;
528 	}
529 	break;
530 /* this is more complex, we behave like "ADAPTIVE" until half the deadline has passed,
531  * then we release the herd and wait until the last safe moment and go with that */
532 	case SYNCH_TIGHT:{
533 		ssize_t deadline = (next >> 1) - estimate_frame_cost();
534 		ssize_t margin = next - estimate_frame_cost();
535 
536 		if (elapsed < deadline){
537 			internal_yield();
538 			return false;
539 		}
540 		else if (elapsed < next - estimate_frame_cost()){
541 			if (!conductor.in_frame){
542 				conductor.in_frame = true;
543 				unlock_herd();
544 				internal_yield();
545 				return false;
546 			}
547 			internal_yield();
548 			return false;
549 		}
550 
551 		TRACE_MARK_ONESHOT("conductor", "synchronization",
552 			TRACE_SYS_DEFAULT, 0, elapsed - margin, "tight-deadline");
553 		return true;
554 	}
555 	case SYNCH_VSYNCH:
556 	case SYNCH_PROCESSING:
557 	case SYNCH_IMMEDIATE:
558 	case SYNCH_POWERSAVE:
559 	break;
560 	}
561 	return true;
562 }
563 
postframe_synch(uint64_t next)564 static uint64_t postframe_synch(uint64_t next)
565 {
566 	switch(synchopt){
567 	case SYNCH_VSYNCH:
568 	case SYNCH_ADAPTIVE:
569 	case SYNCH_POWERSAVE:
570 		unlock_herd();
571 	break;
572 	case SYNCH_PROCESSING:
573 	case SYNCH_IMMEDIATE:
574 	break;
575 	}
576 	conductor.in_frame = false;
577 	return next;
578 }
579 
580 /* Reset when starting _run, and set to true after we have managed to pass a
581  * full event-context flush and display scanout. This is to catch errors that
582  * persist and re-introduce min the main event handler during warmup and make
583  * sure we don't get stuck in an endless recover->main->flush->recover loop */
584 static bool valid_cycle;
arcan_conductor_valid_cycle()585 bool arcan_conductor_valid_cycle()
586 {
587 	return valid_cycle;
588 }
589 
trigger_video_synch(float frag)590 static int trigger_video_synch(float frag)
591 {
592 	conductor.set_deadline = -1;
593 
594 	TRACE_MARK_ENTER("conductor", "platform-frame", TRACE_SYS_DEFAULT, conductor.tick_count, frag, "");
595 		arcan_lua_callvoidfun(main_lua_context, "preframe_pulse", false, NULL);
596 			platform_video_synch(conductor.tick_count, frag, NULL, NULL);
597 		arcan_lua_callvoidfun(main_lua_context, "postframe_pulse", false, NULL);
598 	TRACE_MARK_EXIT("conductor", "platform-frame", TRACE_SYS_DEFAULT, conductor.tick_count, frag, "");
599 
600 	arcan_bench_register_frame();
601 	arcan_benchdata* stats = arcan_bench_data();
602 
603 /* exponential moving average */
604 	conductor.render_cost =
605 		0.8 * (double)stats->framecost[(uint8_t)stats->costofs] +
606 		0.2 * conductor.render_cost;
607 
608 	TRACE_MARK_ONESHOT("conductor", "frame-over", TRACE_SYS_DEFAULT, 0, conductor.set_deadline, "");
609 
610 	valid_cycle = true;
611 /* if the platform wants us to wait, it'll provide a new deadline at synch */
612 	return conductor.set_deadline > 0 ? conductor.set_deadline : 0;
613 }
614 
615 static arcan_tick_cb outcb;
616 static void conductor_cycle();
arcan_conductor_run(arcan_tick_cb tick)617 int arcan_conductor_run(arcan_tick_cb tick)
618 {
619 	arcan_evctx* evctx = arcan_event_defaultctx();
620 	outcb = tick;
621 	int exit_code = EXIT_FAILURE;
622 	alloc_frameserver_struct();
623 	uint64_t last_tickcount;
624 	uint64_t last_synch = arcan_timemillis();
625 	uint64_t next_synch = 0;
626 	int sstate = -1;
627 	valid_cycle = false;
628 
629 	arcan_event_setdrain(evctx, overflow_drain);
630 
631 	for(;;){
632 /*
633  * So this is not good enough to do attribution, and it is likely that the
634  * cause of the context reset will simply repeat itself. Possible options for
635  * deriving this is to trigger bufferfail for all external frameservers (or
636  * pick them at random until figured out the sinner), and have an eval pass for
637  * custom shaders (other likely suspect) - and after some 'n fails' trigger the
638  * script kind of reset/rebuild.
639  */
640 		if (!agp_status_ok(NULL)){
641 			TRACE_MARK_ONESHOT("conductor", "platform",
642 				TRACE_SYS_ERROR, 0, 0, "accelerated graphics failed");
643 			platform_video_reset(-1, false);
644 		}
645 
646 /*
647  * specific note here, we'd like to know about frameservers that have resized
648  * and then actually dispatch / process these twice so that their old buffers
649  * might get to be updated before we synch to display.
650  */
651 		arcan_video_pollfeed();
652 		arcan_audio_refresh();
653 		arcan_event_poll_sources(evctx, 0);
654 
655 		last_tickcount = conductor.tick_count;
656 
657 		TRACE_MARK_ENTER("conductor", "event",
658 			TRACE_SYS_DEFAULT, 0, last_tickcount, "process");
659 
660 		float frag = arcan_event_process(evctx, conductor_cycle);
661 		uint64_t elapsed = arcan_timemillis() - last_synch;
662 
663 		TRACE_MARK_EXIT("conductor", "event",
664 			TRACE_SYS_DEFAULT, 0, last_tickcount, "process");
665 
666 /* This fails when the event recipient has queued a SHUTDOWN event */
667 		if (!arcan_event_feed(evctx, process_event, &exit_code))
668 			break;
669 		process_event(NULL, 0);
670 
671 /* Chunk the time left until the next batch and yield in small steps. This
672  * puts us about 25fps, could probably go a little lower than that, say 12 */
673 		if (synchopt == SYNCH_POWERSAVE && last_tickcount == conductor.tick_count){
674 			internal_yield();
675 			continue;
676 		}
677 
678 /* Other processing modes deal with their poll/sleep synch inside video-synch
679  * or based on another evaluation function */
680 		else if (next_synch <= 0 || preframe_synch(next_synch - last_synch, elapsed)){
681 /* A stall or other action caused us to miss the tight deadline and the herd
682  * didn't get unlocked this pass, so perform one now to not block the clients
683  * indefinitely */
684 			if (synchopt == SYNCH_TIGHT && !conductor.in_frame){
685 				conductor.in_frame = true;
686 				unlock_herd();
687 			}
688 
689 			next_synch = postframe_synch( trigger_video_synch(frag) );
690 			last_synch = arcan_timemillis();
691 		}
692 	}
693 
694 	outcb = NULL;
695 	return exit_code;
696 }
697 
698 /* We arrive here when the video platform decides that the system should behave
699  * like there is a synchronization period to respect, but leaves it up to us
700  * to decide how to clock and interleave. Left sets the number of milliseconds
701  * that should be 'burnt' for the synch signal to be complete. */
arcan_conductor_fakesynch(uint8_t left)702 void arcan_conductor_fakesynch(uint8_t left)
703 {
704 	int real_left = left;
705 	int step;
706 	TRACE_MARK_ENTER("conductor", "synchronization",
707 		TRACE_SYS_SLOW, 0, left, "fake synch");
708 
709 /* Some platforms have a high cost for sleep / yield operations and the overhead
710  * alone might eat up what is actually left in the timing buffer */
711 	struct platform_timing timing = platform_hardware_clockcfg();
712 	size_t sleep_cost = !timing.tickless * (timing.cost_us / 1000);
713 
714 	while ((step = arcan_conductor_yield(NULL, 0)) != -1 && left > step + sleep_cost){
715 		arcan_event_poll_sources(arcan_event_defaultctx(), step);
716 		left -= step;
717 	}
718 
719 	TRACE_MARK_EXIT("conductor", "synchronization",
720 		TRACE_SYS_SLOW, 0, left, "fake synch");
721 }
722 
arcan_conductor_deadline(uint8_t deadline)723 void arcan_conductor_deadline(uint8_t deadline)
724 {
725 	if (conductor.set_deadline == -1 || deadline < conductor.set_deadline){
726 		conductor.set_deadline = arcan_timemillis() + deadline;
727 		TRACE_MARK_ONESHOT("conductor", "synchronization",
728 			TRACE_SYS_DEFAULT, 0, deadline, "deadline");
729 	}
730 }
731 
conductor_cycle(int nticks)732 static void conductor_cycle(int nticks)
733 {
734 	conductor.tick_count += nticks;
735 /* priority is always in maintaining logical clock and event processing */
736 	unsigned njobs;
737 
738 	arcan_video_tick(nticks, &njobs);
739 	arcan_audio_tick(nticks);
740 
741 /* the lua VM last after a/v pipe is to allow 1- tick schedulers, otherwise
742  * you'd get the problem of:
743  *
744  * function clock_pulse() nudge_image(a, 10, 10, 1); end
745  *
746  * and tag transforms handlers being one tick off
747  */
748 	if (arcan_watchdog_ping)
749 		atomic_store(arcan_watchdog_ping, arcan_timemillis());
750 
751 	arcan_lua_tick(main_lua_context, nticks, conductor.tick_count);
752 	outcb(nticks);
753 
754 	while(nticks--)
755 		arcan_mem_tick();
756 }
757