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