1 /*
2  * Copyright 2012-2016, Björn Ståhl
3  * License: GPLv2, see COPYING file in arcan source repository.
4  * Reference: http://www.libretro.com
5  */
6 
7 #define AGP_ENABLE_UNPURE 1
8 
9 #include <math.h>
10 #include <stdlib.h>
11 #include <assert.h>
12 #include <stdio.h>
13 #include <stdint.h>
14 #include <stdbool.h>
15 #include <unistd.h>
16 #include <string.h>
17 #include <strings.h>
18 #include <pthread.h>
19 #include <errno.h>
20 #include <dlfcn.h>
21 #include <fcntl.h>
22 #include <inttypes.h>
23 
24 #ifdef FRAMESERVER_LIBRETRO_3D
25 #ifdef ENABLE_RETEXTURE
26 #include "retexture.h"
27 #endif
28 #include "video_platform.h"
29 #include "platform.h"
30 #define WANT_ARCAN_SHMIF_HELPER
31 #endif
32 #include <arcan_shmif.h>
33 
34 #include <sys/stat.h>
35 #include <sys/types.h>
36 
37 #include "frameserver.h"
38 #include "ntsc/snes_ntsc.h"
39 #include "sync_plot.h"
40 #include "libretro.h"
41 
42 #include "font_8x8.h"
43 
44 #ifndef MAX_PORTS
45 #define MAX_PORTS 4
46 #endif
47 
48 #ifndef MAX_AXES
49 #define MAX_AXES 32
50 #endif
51 
52 #ifndef MAX_BUTTONS
53 #define MAX_BUTTONS 16
54 #endif
55 
56 #undef BADID
57 
58 #define COUNT_OF(x) \
59 	((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
60 
61 /* note on synchronization:
62  * the async and esync mechanisms will buffer locally and have that buffer
63  * flushed by the main application whenever appropriate. For audio, this is
64  * likely limited by the buffering capacity of the sound device / pipeline
65  * whileas the event queue might be a bit more bursty.
66  *
67  * however, we will lock to video, meaning that it is the framerate of the
68  * frameserver that will decide the actual framerate, that may be locked
69  * to VREFRESH (or lower, higher / variable). Thus we also need frameskipping
70  * heuristics here.
71  */
72 struct input_port {
73 	bool buttons[MAX_BUTTONS];
74 	int16_t axes[MAX_AXES];
75 
76 /* special offsets into buttons / axes based on device */
77 	unsigned cursor_x, cursor_y, cursor_btns[5];
78 };
79 
80 struct core_variable {
81 	const char* key;
82 	const char* value;
83 	bool updated;
84 };
85 
86 typedef void(*pixconv_fun)(const void* data, shmif_pixel* outp,
87 	unsigned width, unsigned height, size_t pitch, bool postfilter);
88 
89 static struct {
90 	struct synch_graphing* sync_data; /* overlaying statistics */
91 
92 /* flag for rendering callbacks, should the frame be processed or not */
93 	bool skipframe_a, skipframe_v, empty_v;
94 	bool in_3d;
95 
96 /* miliseconds per frame, 1/fps */
97 	double mspf;
98 
99 /* when did we last seed gameplay timing */
100 	long long int basetime;
101 
102 /* for debugging / testing, added extra jitter to
103  * the cost for the different stages */
104 	int jitterstep, jitterxfer;
105 
106 /* add 'n' frames pseudoskipping to populate audiobuffers */
107 	int preaudiogen;
108 /* user changeable variable, how synching should be treated */
109 	int skipmode;
110 /* for frameskip auto to compensate for jitter in transfer etc. */
111 	int prewake;
112 
113 /* statistics / timing */
114 	unsigned long long aframecount, vframecount;
115 	struct retro_system_av_info avinfo; /* timing according to libretro */
116 
117 	int rebasecount, frameskips, transfercost, framecost;
118 	const char* colorspace;
119 
120 /* colour conversion / filtering */
121 	pixconv_fun converter;
122 	uint16_t* ntsc_imb;
123 	bool ntscconv;
124 	snes_ntsc_t* ntscctx;
125 	int ntscvals[4];
126 	snes_ntsc_setup_t ntsc_opts;
127 
128 /* SHM- API input /output */
129 	struct arcan_shmif_cont shmcont;
130 	int graphmode;
131 
132 /* libretro states / function pointers */
133 	struct retro_system_info sysinfo;
134 	struct retro_game_info gameinfo;
135 
136 /* for core options support:
137  * 1. on SET_VARIABLES, expose each as an event to parent.
138  *    populate a separate table that acts as a cache.
139  *
140  * 2. on GET_VARIABLE, lookup against the args and fallback
141  *    on the cache. Dynamic switching isn't supported currently. */
142 	struct arg_arr* inargs;
143 	struct core_variable* varset;
144 	struct arcan_event ident;
145 	bool optdirty;
146 
147 /* for skipmode = TARGET_SKIP_ROLLBACK,
148  * then we maintain a statebuffer (requires savestate support)
149  * and when input is "dirty" roll back one frame ignoring output,
150  * apply, then fast forward one frame */
151  	bool dirty_input;
152 	float aframesz;
153 	int rollback_window;
154 	unsigned rollback_front;
155 	char* rollback_state;
156 	size_t state_sz;
157 	char* syspath;
158 	bool res_empty;
159 
160 /*
161  * performance trim-values for video/audio buffer synchronization
162  */
163 	uint16_t def_abuf_sz;
164 	uint8_t abuf_cnt, vbuf_cnt;
165 
166 #ifdef FRAMESERVER_LIBRETRO_3D
167 	struct retro_hw_render_callback hwctx;
168 	bool got_3dframe;
169 #endif
170 
171 /* parent uses an event->push model for input, libretro uses a poll one, so
172  * prepare a lookup table that events gets pushed into and libretro can poll */
173 	struct input_port input_ports[MAX_PORTS];
174 	char kbdtbl[RETROK_LAST];
175 
176 	void (*run)();
177 	void (*reset)();
178 	bool (*load_game)(const struct retro_game_info* game);
179 	size_t (*serialize_size)();
180 	bool (*serialize)(void*, size_t);
181 	bool (*deserialize)(const void*, size_t);
182 	void (*set_ioport)(unsigned, unsigned);
183 } retro = {
184 	.abuf_cnt = 12,
185 	.def_abuf_sz = 1,
186 	.vbuf_cnt = 3,
187 	.prewake = 10,
188 	.preaudiogen = 1,
189 	.skipmode = TARGET_SKIP_AUTO
190 };
191 
192 /* render statistics unto *vidp, at the very end of this .c file */
193 static void update_ntsc();
194 static void push_stats();
195 
196 #ifdef FRAMESERVER_LIBRETRO_3D
197 static void setup_3dcore(struct retro_hw_render_callback*);
198 #endif
199 
200 static void* lastlib, (* globallib);
libretro_requirefun(const char * sym)201 retro_proc_address_t libretro_requirefun(const char* sym)
202 {
203 /* not very relevant here, but proper form is dlerror() */
204 	if (!sym)
205 		return NULL;
206 
207 /*
208   if (module){
209 		if (lastlib)
210 			return dlsym(lastlib, sym);
211 		else
212 			return NULL;
213 	}
214  */
215 
216 	return dlsym(lastlib, sym);
217 }
218 
write_handle(const void * const data,size_t sz_data,file_handle dst,bool finalize)219 static bool write_handle(const void* const data,
220 	size_t sz_data, file_handle dst, bool finalize)
221 {
222 	bool rv = false;
223 
224 	if (dst != BADFD)
225 	{
226 		off_t ofs = 0;
227 		ssize_t nw;
228 
229 		while ( ofs != sz_data){
230 			nw = write(dst, ((char*) data) + ofs, sz_data - ofs);
231 			if (-1 == nw)
232 				switch (errno){
233 				case EAGAIN: continue;
234 				case EINTR: continue;
235 				default:
236 					LOG("write_handle(dumprawfile) -- write failed (%d),"
237 					"	reason: %s\n", errno, strerror(errno));
238 					goto out;
239 			}
240 
241 			ofs += nw;
242 		}
243 		rv = true;
244 
245 		out:
246 		if (finalize)
247 			close(dst);
248 	}
249 	 else
250 		 LOG("write_handle(dumprawfile) -- request to dump to invalid "
251 			"file handle ignored, no output set by parent.\n");
252 
253 	return rv;
254 }
255 
resize_shmpage(int neww,int newh,bool first)256 static void resize_shmpage(int neww, int newh, bool first)
257 {
258 	if (retro.shmcont.abufpos){
259 		LOG("resize(), force flush %zu samples\n", (size_t)retro.shmcont.abufpos);
260 		arcan_shmif_signal(&retro.shmcont, SHMIF_SIGAUD | SHMIF_SIGBLK_NONE);
261 	}
262 
263 #ifdef FRAMESERVER_LIBRETRO_3D
264 	if (retro.in_3d)
265 		retro.shmcont.hints = SHMIF_RHINT_ORIGO_LL;
266 #endif
267 
268 	if (!arcan_shmif_resize_ext(&retro.shmcont, neww, newh,
269 		(struct shmif_resize_ext){
270 			.abuf_sz = 1024,
271 			.samplerate = retro.avinfo.timing.sample_rate,
272 			.abuf_cnt = retro.abuf_cnt,
273 			.vbuf_cnt = retro.vbuf_cnt})){
274 		LOG("resizing shared memory page failed\n");
275 		exit(1);
276 	}
277 	else {
278 		LOG("requested resize to (%d * %d), %d 1k audio buffers, "
279 				"%d video buffers\n", neww, newh, retro.abuf_cnt, retro.vbuf_cnt);
280 	}
281 
282 #ifdef FRAMESERVER_LIBRETRO_3D
283 	if (retro.in_3d)
284 		arcan_shmifext_make_current(&retro.shmcont);
285 
286 	if (!getenv("GAME_NORESET") && retro.hwctx.context_reset)
287 		retro.hwctx.context_reset();
288 #endif
289 
290 	if (retro.sync_data)
291 		retro.sync_data->cont_switch(retro.sync_data, &retro.shmcont);
292 
293 /* will be reallocated if needed and not set so just free and unset */
294 	if (retro.ntsc_imb){
295 		free(retro.ntsc_imb);
296 		retro.ntsc_imb = NULL;
297 	}
298 }
299 
300 /* overrv / overra are needed for handling rollbacks etc.
301  * while still making sure the other frameskipping options are working */
process_frames(int nframes,bool overrv,bool overra)302 static void process_frames(int nframes, bool overrv, bool overra)
303 {
304 	bool cv = retro.skipframe_v;
305 	bool ca = retro.skipframe_a;
306 
307 	if (overrv)
308 		retro.skipframe_v = true;
309 
310 	if (overra)
311 		retro.skipframe_a = true;
312 
313 	while(nframes--)
314 		retro.run();
315 
316 	if (retro.skipmode <= TARGET_SKIP_ROLLBACK){
317 		retro.serialize(retro.rollback_state +
318 			(retro.rollback_front * retro.state_sz), retro.state_sz);
319 		retro.rollback_front = (retro.rollback_front + 1)
320 			% retro.rollback_window;
321 	}
322 
323 	retro.skipframe_v = cv;
324 	retro.skipframe_a = ca;
325 }
326 
327 #define RGB565(b, g, r) ((uint16_t)(((uint8_t)(r) >> 3) << 11) | \
328 								(((uint8_t)(g) >> 2) << 5) | ((uint8_t)(b) >> 3))
329 
push_ntsc(unsigned width,unsigned height,const uint16_t * ntsc_imb,shmif_pixel * outp)330 static void push_ntsc(unsigned width, unsigned height,
331 	const uint16_t* ntsc_imb, shmif_pixel* outp)
332 {
333 	size_t linew = SNES_NTSC_OUT_WIDTH(width) * 4;
334 
335 /* only draw on every other line, so we can easily mix or
336  * blend interleaved (or just duplicate) */
337 	snes_ntsc_blit(retro.ntscctx, ntsc_imb, width, 0,
338 		width, height, outp, linew * 2);
339 
340 /* this might be a possible test-case for running two shmif
341  * connections and let the compositor do interlacing management */
342 	assert(ARCAN_SHMPAGE_VCHANNELS == 4);
343 	for (int row = 1; row < height * 2; row += 2)
344 		memcpy(& ((char*) retro.shmcont.vidp)[row * linew],
345 			&((char*) retro.shmcont.vidp)[(row-1) * linew], linew);
346 }
347 
348 /* better distribution for conversion (white is white ..) */
349 static const uint8_t rgb565_lut5[] = {
350   0,   8,  16,  25,  33,  41,  49,  58,  66,   74,  82,  90,  99, 107, 115,123,
351 132, 140, 148, 156, 165, 173, 181, 189,  197, 206, 214, 222, 230, 239, 247,255
352 };
353 
354 static const uint8_t rgb565_lut6[] = {
355   0,   4,   8,  12,  16,  20,  24,  28,  32,  36,  40,  45,  49,  53,  57, 61,
356  65,  69,  73,  77,  81,  85,  89,  93,  97, 101, 105, 109, 113, 117, 121, 125,
357 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190,
358 194, 198, 202, 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255
359 };
360 
libretro_rgb565_rgba(const uint16_t * data,shmif_pixel * outp,unsigned width,unsigned height,size_t pitch)361 static void libretro_rgb565_rgba(const uint16_t* data, shmif_pixel* outp,
362 	unsigned width, unsigned height, size_t pitch)
363 {
364 	uint16_t* interm = retro.ntsc_imb;
365 	retro.colorspace = "RGB565->RGBA";
366 
367 /* with NTSC on, the input format is already correct */
368 	for (int y = 0; y < height; y++){
369 		for (int x = 0; x < width; x++){
370 			uint16_t val = data[x];
371 			uint8_t r = rgb565_lut5[ (val & 0xf800) >> 11 ];
372 			uint8_t g = rgb565_lut6[ (val & 0x07e0) >> 5  ];
373 			uint8_t b = rgb565_lut5[ (val & 0x001f)       ];
374 
375 			if (retro.ntscconv)
376 				*interm++ = RGB565(r, g, b);
377 			else
378 				*outp++ = RGBA(r, g, b, 0xff);
379 		}
380 		data += pitch >> 1;
381 	}
382 
383 	if (retro.ntscconv)
384 		push_ntsc(width, height, retro.ntsc_imb, outp);
385 
386 	return;
387 }
388 
libretro_xrgb888_rgba(const uint32_t * data,uint32_t * outp,unsigned width,unsigned height,size_t pitch)389 static void libretro_xrgb888_rgba(const uint32_t* data, uint32_t* outp,
390 	unsigned width, unsigned height, size_t pitch)
391 {
392 	assert( (uintptr_t)data % 4 == 0 );
393 	retro.colorspace = "XRGB888->RGBA";
394 
395 	uint16_t* interm = retro.ntsc_imb;
396 
397 	for (int y = 0; y < height; y++){
398 		for (int x = 0; x < width; x++){
399 			uint8_t* quad = (uint8_t*) (data + x);
400 			if (retro.ntscconv)
401 				*interm++ = RGB565(quad[2], quad[1], quad[0]);
402 			else
403 				*outp++ = RGBA(quad[2], quad[1], quad[0], 0xff);
404 		}
405 
406 		data += pitch >> 2;
407 	}
408 
409 	if (retro.ntscconv)
410 		push_ntsc(width, height, retro.ntsc_imb, outp);
411 }
412 
libretro_rgb1555_rgba(const uint16_t * data,uint32_t * outp,unsigned width,unsigned height,size_t pitch,bool postfilter)413 static void libretro_rgb1555_rgba(const uint16_t* data, uint32_t* outp,
414 	unsigned width, unsigned height, size_t pitch, bool postfilter)
415 {
416 	uint16_t* interm = retro.ntsc_imb;
417 	retro.colorspace = "RGB1555->RGBA";
418 
419 	unsigned dh = height >= ARCAN_SHMPAGE_MAXH ? ARCAN_SHMPAGE_MAXH : height;
420 	unsigned dw =  width >= ARCAN_SHMPAGE_MAXW ? ARCAN_SHMPAGE_MAXW : width;
421 
422 	for (int y = 0; y < dh; y++){
423 		for (int x = 0; x < dw; x++){
424 			uint16_t val = data[x];
425 			uint8_t r = ((val & 0x7c00) >> 10) << 3;
426 			uint8_t g = ((val & 0x03e0) >>  5) << 3;
427 			uint8_t b = ( val & 0x001f) <<  3;
428 
429 			if (postfilter)
430 				*interm++ = RGB565(r, g, b);
431 			else
432 				*outp++ = RGBA(r, g, b, 0xff);
433 		}
434 
435 		data += pitch >> 1;
436 	}
437 
438 	if (postfilter)
439 		push_ntsc(width, height, retro.ntsc_imb, outp);
440 }
441 
442 
443 static int testcounter;
libretro_vidcb(const void * data,unsigned width,unsigned height,size_t pitch)444 static void libretro_vidcb(const void* data, unsigned width,
445 	unsigned height, size_t pitch)
446 {
447 	testcounter++;
448 
449 	if (retro.in_3d && !data)
450 		;
451 	else if (!data || retro.skipframe_v){
452 		retro.empty_v = true;
453 		return;
454 	}
455 	else
456 		retro.empty_v = false;
457 
458 /* width / height can be changed without notice, so we have to be ready for the
459  * fact that the cost of conversion can suddenly move outside the allowed
460  * boundaries, then NTSC is ignored (or if we have 3d/hw source) */
461 	unsigned outw = width;
462 	unsigned outh = height;
463 	bool ntscconv = retro.ntscconv && data != RETRO_HW_FRAME_BUFFER_VALID;
464 
465 	if (ntscconv && SNES_NTSC_OUT_WIDTH(width)<= ARCAN_SHMPAGE_MAXW
466 		&& height * 2 <= ARCAN_SHMPAGE_MAXH){
467 		outh = outh << 1;
468 		outw = SNES_NTSC_OUT_WIDTH( width );
469 	}
470 	else {
471 		outw = width;
472 		outh = height;
473 		ntscconv = false;
474 	}
475 
476 /* the shmpage size will be larger than the possible values for width / height,
477  * so if we have a mismatch, just change the shared dimensions and toggle
478  * resize flag */
479 	if (outw != retro.shmcont.addr->w || outh != retro.shmcont.addr->h){
480 		resize_shmpage(outw, outh, false);
481 	}
482 
483 	if (ntscconv && !retro.ntsc_imb){
484 		retro.ntsc_imb = malloc(sizeof(uint16_t) * outw * outh);
485 	}
486 
487 #ifdef FRAMESERVER_LIBRETRO_3D
488 /* method one, just read color attachment */
489 	if (retro.in_3d){
490 /* it seems like tons of cores doesn't actually set this correctly */
491 		retro.got_3dframe = 1 || data == RETRO_HW_FRAME_BUFFER_VALID;
492 		return;
493 	}
494 	else
495 #endif
496 
497 /* lastly, convert / blit, this will possibly clip */
498 	if (retro.converter)
499 		retro.converter(data, retro.shmcont.vidp, width,
500 			height, pitch, ntscconv);
501 }
502 
do_preaudio()503 static void do_preaudio()
504 {
505 	if (retro.preaudiogen == 0)
506 		return;
507 
508 	retro.skipframe_v = true;
509 	retro.skipframe_a = false;
510 
511 	int afc = retro.aframecount;
512 	int vfc = retro.vframecount;
513 
514 	for (int i = 0; i < retro.preaudiogen; i++)
515 		retro.run();
516 
517 	retro.skipframe_v = false;
518 	retro.aframecount = afc;
519 	retro.vframecount = vfc;
520 }
521 
libretro_skipnframes(unsigned count,bool fastfwd)522 static void libretro_skipnframes(unsigned count, bool fastfwd)
523 {
524 	retro.skipframe_v = true;
525 	retro.skipframe_a = fastfwd;
526 
527 	long long afc = retro.aframecount;
528 
529 	for (int i = 0; i < count; i++)
530 		retro.run();
531 
532 	if (fastfwd){
533 		retro.aframecount = afc;
534 		retro.frameskips += count;
535 	}
536 	else
537 		retro.vframecount += count;
538 
539 	retro.skipframe_a = false;
540 	retro.skipframe_v = false;
541 }
542 
reset_timing(bool newstate)543 static void reset_timing(bool newstate)
544 {
545 	arcan_shmif_enqueue(&retro.shmcont, &(arcan_event){
546 		.ext.kind = ARCAN_EVENT(FLUSHAUD)
547 	});
548 	retro.basetime = arcan_timemillis();
549 	do_preaudio();
550 	retro.vframecount = 1;
551 	retro.aframecount = 1;
552 	retro.frameskips  = 0;
553 	if (!newstate){
554 		retro.rebasecount++;
555 	}
556 
557 /* since we can't be certain about our current vantage point...*/
558 	if (newstate && retro.skipmode <= TARGET_SKIP_ROLLBACK &&
559 		retro.state_sz > 0){
560 		retro.rollback_window = (TARGET_SKIP_ROLLBACK - retro.skipmode) + 1;
561 		if (retro.rollback_window > 10)
562 			retro.rollback_window = 10;
563 
564 		free(retro.rollback_state);
565 		retro.rollback_state = malloc(retro.state_sz * retro.rollback_window);
566 		retro.rollback_front = 0;
567 
568 		retro.serialize(retro.rollback_state, retro.state_sz);
569 		for (int i=1; i < retro.rollback_window - 1; i++)
570 			memcpy(retro.rollback_state + (i*retro.state_sz),
571 				retro.rollback_state, retro.state_sz);
572 
573 		LOG("setting input rollback (%d)\n", retro.rollback_window);
574 	}
575 }
576 
libretro_audscb(int16_t left,int16_t right)577 static void libretro_audscb(int16_t left, int16_t right)
578 {
579 	if (retro.skipframe_a)
580 		return;
581 
582 	retro.aframecount++;
583 	retro.shmcont.audp[retro.shmcont.abufpos++] = SHMIF_AINT16(left);
584 	retro.shmcont.audp[retro.shmcont.abufpos++] = SHMIF_AINT16(right);
585 
586 	if (retro.shmcont.abufpos >= retro.shmcont.abufcount){
587 		long long elapsed = arcan_shmif_signal(&retro.shmcont, SHMIF_SIGAUD | SHMIF_SIGBLK_NONE);
588 		LOG("audio buffer synch cost (%lld) ms\n", elapsed);
589 	}
590 }
591 
libretro_audcb(const int16_t * data,size_t nframes)592 static size_t libretro_audcb(const int16_t* data, size_t nframes)
593 {
594 	if (retro.skipframe_a)
595 		return nframes;
596 
597 /* from FRAMES to SAMPLES */
598 	size_t left = nframes * 2;
599 
600 	while (left){
601 		size_t bfree = retro.shmcont.abufcount - retro.shmcont.abufpos;
602 		bool flush = false;
603 		size_t ntw;
604 
605 		if (left > bfree){
606 			ntw = bfree;
607 			flush = true;
608 		}
609 		else {
610 			ntw = left;
611 			flush = false;
612 		}
613 
614 /* this is in BYTES not SAMPLES or FRAMES */
615 		memcpy(&retro.shmcont.audp[retro.shmcont.abufpos], data, ntw * 2);
616 		left -= ntw;
617 		data += ntw;
618 		retro.shmcont.abufpos += ntw;
619 		if (flush){
620 			long long elapsed = arcan_shmif_signal(&retro.shmcont, SHMIF_SIGAUD | SHMIF_SIGBLK_NONE);
621 			LOG("audio buffer synch cost (%lld) ms\n", elapsed);
622 		}
623 	}
624 
625 	retro.aframecount += nframes;
626 	return nframes;
627 }
628 
629 /* we ignore these since before pushing for a frame,
630  * we've already processed the queue */
libretro_pollcb()631 static void libretro_pollcb(){}
632 
lookup_varset(const char * key)633 static const char* lookup_varset( const char* key )
634 {
635 	struct core_variable* var = retro.varset;
636 	char buf[ strlen(key) + sizeof("core_") + 1];
637 	snprintf(buf, sizeof(buf), "core_%s", key);
638 	const char* val = NULL;
639 
640 /* we have an initial preset, only update if dirty,
641  * note: this might not be necessary anymore, test and drop */
642 	if (arg_lookup(retro.inargs, buf, 0, &val)){
643 		if (var)
644 			while(var->key){
645 				if (var->updated && strcmp(var->key, key) == 0){
646 					return var->value;
647 				}
648 
649 				var++;
650 			}
651 
652 	}
653 /* no preset, just return the first match */
654 	else if (var) {
655 		while (var->key){
656 			if (strcmp(var->key, key) == 0)
657 				return var->value;
658 
659 			var++;
660 		}
661 	}
662 
663 	return val;
664 }
665 
666 /* from parent, not all cores support dynamic arguments
667  * so this is just a complement to launch arguments */
update_corearg(int code,const char * value)668 static void update_corearg(int code, const char* value)
669 {
670 	struct core_variable* var = retro.varset;
671 	while (var && var->key && code--)
672 		var++;
673 
674 	if (code <= 0){
675 		free((char*)var->value);
676 		var->value = strdup(value);
677 		var->updated = true;
678 	}
679 }
680 
update_varset(struct retro_variable * data)681 static void update_varset( struct retro_variable* data )
682 {
683 	int count = 0;
684 	arcan_event outev = {
685 		.category = EVENT_EXTERNAL,
686 		.ext.kind = ARCAN_EVENT(COREOPT)
687 	};
688 
689 	size_t msgsz = COUNT_OF(outev.ext.coreopt.data);
690 
691 /* reset current varset */
692 	if (retro.varset){
693 		while (retro.varset[count].key){
694 			free((char*)retro.varset[count].key);
695 			free((char*)retro.varset[count].value);
696 			count++;
697 		}
698 
699 		free(retro.varset);
700 		retro.varset = NULL;
701 		count = 0;
702 	}
703 
704 /* allocate a new set */
705 	while ( data[count].key )
706 		count++;
707 
708 	if (count == 0)
709 		return;
710 
711 	count++;
712 	retro.varset = malloc( sizeof(struct core_variable) * count);
713 	memset(retro.varset, '\0', sizeof(struct core_variable) * count);
714 
715 	count = 0;
716 	while ( data[count].key ){
717 		retro.varset[count].key = strdup(data[count].key);
718 		outev.ext.coreopt.index = count;
719 
720 /* parse, grab the first argument and keep in table,
721  * queue the argument as a series of event to the parent */
722 		if (data[count].value){
723 			bool gotval = false;
724 			char* msg = strdup(data[count].value);
725 			char* workbeg = msg;
726 			char* workend = msg;
727 
728 /* message */
729 			while (*workend && *workend != ';') workend++;
730 
731 			if (*workend != ';'){
732 				LOG("malformed core argument (%s:%s)\n", data[count].key,
733 					data[count].value);
734 				goto step;
735 			}
736 			*workend++ = '\0';
737 
738 			if (msgsz < strlen(workbeg)){
739 				LOG("suspiciously long description (%s:%s), %d\n", data[count].key,
740 					workbeg, (int)msgsz);
741 				goto step;
742 			}
743 
744 /* skip leading whitespace */
745 		while(*workend && *workend == ' ') workend++;
746 
747 /* key */
748 			outev.ext.coreopt.type = 0;
749 			snprintf((char*)outev.ext.coreopt.data, msgsz, "%s", data[count].key);
750 			arcan_shmif_enqueue(&retro.shmcont, &outev);
751 /* description */
752 			outev.ext.coreopt.type = 1;
753 			snprintf((char*)outev.ext.coreopt.data, msgsz, "%s", workbeg);
754 			arcan_shmif_enqueue(&retro.shmcont, &outev);
755 
756 /* each option */
757 startarg:
758 			workbeg = workend;
759 			while (*workend && *workend != '|') workend++;
760 
761 /* treats || as erroneous */
762 			if (strlen(workbeg) > 0){
763 				if (*workend == '|')
764 					*workend++ = '\0';
765 
766 				if (!gotval && (gotval = true))
767 					retro.varset[count].value = strdup(workbeg);
768 
769 				outev.ext.coreopt.type = 2;
770 				snprintf((char*)outev.ext.coreopt.data, msgsz, "%s", workbeg);
771 				arcan_shmif_enqueue(&retro.shmcont, &outev);
772 
773 				goto startarg;
774 			}
775 
776 			const char* curv = lookup_varset(data[count].key);
777 			if (curv){
778 				outev.ext.coreopt.type = 3;
779 				snprintf((char*)outev.ext.coreopt.data, msgsz, "%s", curv);
780 				arcan_shmif_enqueue(&retro.shmcont, &outev);
781 			}
782 
783 step:
784 			free(msg);
785 		}
786 
787 		count++;
788 	}
789 }
790 
libretro_log(enum retro_log_level level,const char * fmt,...)791 static void libretro_log(enum retro_log_level level, const char* fmt, ...)
792 {
793 }
794 
795 static struct retro_log_callback log_cb = {
796 	.log = libretro_log
797 };
798 
libretro_setenv(unsigned cmd,void * data)799 static bool libretro_setenv(unsigned cmd, void* data){
800 	bool rv = true;
801 
802 	if (!retro.shmcont.addr)
803 		return false;
804 
805 	switch (cmd){
806 	case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
807 
808 		switch ( *(enum retro_pixel_format*) data ){
809 		case RETRO_PIXEL_FORMAT_0RGB1555:
810 			LOG("pixel format set to RGB1555\n");
811 			retro.converter = (pixconv_fun) libretro_rgb1555_rgba;
812 		break;
813 
814 		case RETRO_PIXEL_FORMAT_RGB565:
815 			LOG("pixel format set to RGB565\n");
816 			retro.converter = (pixconv_fun) libretro_rgb565_rgba;
817 		break;
818 
819 		case RETRO_PIXEL_FORMAT_XRGB8888:
820 			LOG("pixel format set to XRGB8888\n");
821 			retro.converter = (pixconv_fun) libretro_xrgb888_rgba;
822 		break;
823 
824 		default:
825 			LOG("unknown pixelformat encountered (%d).\n", *(unsigned*)data);
826 			retro.converter = NULL;
827 		}
828 	break;
829 
830 	case RETRO_ENVIRONMENT_GET_CAN_DUPE:
831 		*((bool*) data) = true;
832 	break;
833 
834 	case RETRO_ENVIRONMENT_SHUTDOWN:
835 		arcan_shmif_drop(&retro.shmcont);
836 		exit(EXIT_SUCCESS);
837 	break;
838 
839 	case RETRO_ENVIRONMENT_SET_VARIABLES:
840 		update_varset( (struct retro_variable*) data );
841 	break;
842 
843 	case RETRO_ENVIRONMENT_GET_VARIABLE:
844 		{
845 			struct retro_variable* arg = (struct retro_variable*) data;
846 			const char* val = lookup_varset(arg->key);
847 			if (val){
848 				arg->value = val;
849 				LOG("core requested (%s), got (%s)\n", arg->key, arg->value);
850 				rv = true;
851 			}
852 		}
853 	break;
854 
855 	case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL:
856 /* don't care */
857 	break;
858 
859 	case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
860 		rv = retro.optdirty;
861 		if (data)
862 			*(bool*)data = rv;
863 		retro.optdirty = false;
864 	break;
865 
866 	case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
867 		*((const char**) data) = retro.syspath;
868 		rv = retro.syspath != NULL;
869 		LOG("system directory set to (%s).\n",
870 			retro.syspath ? retro.syspath : "MISSING");
871 	break;
872 
873 	case RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK:
874 		LOG("frame-time callback unsupported.\n");
875 		rv = false;
876 	break;
877 
878 	case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE:
879 		LOG("rumble- interfaces unsupported.\n");
880 		rv = false;
881 	break;
882 
883 	case RETRO_ENVIRONMENT_GET_PERF_INTERFACE:
884 		LOG("performance- interfaces unsupported.\n");
885 		rv = false;
886 	break;
887 
888 	case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
889 		retro.res_empty = true;
890 	break;
891 
892 	case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK:
893 		LOG("retro- keyboard callback unsupported.\n");
894 		rv = false;
895 	break;
896 
897 	case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
898 		*((struct retro_log_callback*) data) = log_cb;
899 	break;
900 
901 	case RETRO_ENVIRONMENT_GET_USERNAME:
902 		*((const char**) data) = strdup("defusr");
903 	break;
904 
905 	case RETRO_ENVIRONMENT_GET_LANGUAGE:
906 		*((unsigned*) data) = RETRO_LANGUAGE_ENGLISH;
907 	break;
908 
909 #ifdef FRAMESERVER_LIBRETRO_3D
910 	case RETRO_ENVIRONMENT_SET_HW_RENDER | RETRO_ENVIRONMENT_EXPERIMENTAL:
911 	case RETRO_ENVIRONMENT_SET_HW_RENDER:
912 	{
913 /* this should be matched with AGP model rather than statically
914  * set that we only care about GL, doesn't look like any core rely
915  * on this behavior though */
916 		struct retro_hw_render_callback* hwrend = data;
917 		if (hwrend->context_type == RETRO_HW_CONTEXT_OPENGL ||
918 			hwrend->context_type == RETRO_HW_CONTEXT_OPENGL_CORE){
919 			setup_3dcore( hwrend );
920 		}
921 		else
922 			LOG("unsupported hw context requested.\n");
923 	}
924 	break;
925 #else
926 	case RETRO_ENVIRONMENT_SET_HW_RENDER | RETRO_ENVIRONMENT_EXPERIMENTAL:
927 	case RETRO_ENVIRONMENT_SET_HW_RENDER:
928 		LOG("trying to load a GL/3D enabled core, but "
929 			"frameserver was built without 3D support.\n");
930 		rv = false;
931 	break;
932 #endif
933 
934 	default:
935 		rv = false;
936 #ifdef _DEBUG
937 		LOG("unhandled retro request (%d)\n", cmd);
938 #endif
939 	}
940 
941 	return rv;
942 }
943 
944 /*
945  * this is quite sensitive to changes in libretro.h
946  */
map_analog_axis(unsigned port,unsigned ind,unsigned id)947 static inline int16_t map_analog_axis(unsigned port, unsigned ind, unsigned id)
948 {
949 	ind *= 2;
950 	ind += id;
951 	assert(ind < MAX_AXES);
952 
953 	return (int16_t) retro.input_ports[port].axes[ind];
954 }
955 
956 /* use the context-tables from retro in combination with dev / ind / ... to
957  * figure out what to return, this table is populated in flush_eventq(). */
libretro_inputmain(unsigned port,unsigned dev,unsigned ind,unsigned id)958 static inline int16_t libretro_inputmain(unsigned port, unsigned dev,
959 	unsigned ind, unsigned id){
960 	static bool butn_warning = false;
961 	static bool port_warning = false;
962 
963 	if (id > MAX_BUTTONS){
964 		if (butn_warning == false)
965 			LOG("unexpectedly high button index (dev:%u)(%u:%%) "
966 				"requested, ignoring.\n", ind, id);
967 
968 		butn_warning = true;
969 		return 0;
970 	}
971 
972 	if (port >= MAX_PORTS){
973 		if (port_warning == false)
974 			LOG("core requested unknown port (%u:%u:%u), ignored.\n", dev, ind, id);
975 
976 		port_warning = true;
977 		return 0;
978 	}
979 
980 	int16_t rv = 0;
981 	struct input_port* inp;
982 
983 	switch (dev){
984 		case RETRO_DEVICE_JOYPAD:
985 			return (int16_t) retro.input_ports[port].buttons[id];
986 		break;
987 
988 		case RETRO_DEVICE_KEYBOARD:
989 			if (id < RETROK_LAST)
990 				rv |= retro.kbdtbl[id];
991 		break;
992 
993 		case RETRO_DEVICE_MOUSE:
994 			if (port == 1) port = 0;
995 			inp = &retro.input_ports[port];
996 			switch (id){
997 				case RETRO_DEVICE_ID_MOUSE_LEFT:
998 					return inp->buttons[ inp->cursor_btns[0] ];
999 
1000 				case RETRO_DEVICE_ID_MOUSE_RIGHT:
1001 					return inp->buttons[ inp->cursor_btns[2] ];
1002 
1003 				case RETRO_DEVICE_ID_MOUSE_X:
1004 					rv = inp->axes[ inp->cursor_x ];
1005 					inp->axes[ inp->cursor_x ] = 0;
1006 					return rv;
1007 
1008 				case RETRO_DEVICE_ID_MOUSE_Y:
1009 					rv = inp->axes[ inp->cursor_y ];
1010 					inp->axes[ inp->cursor_y ] = 0;
1011 					return rv;
1012 			}
1013 		break;
1014 
1015 		case RETRO_DEVICE_LIGHTGUN:
1016 			switch (id){
1017 				case RETRO_DEVICE_ID_LIGHTGUN_X:
1018 					return (int16_t) retro.input_ports[port].axes[
1019 						retro.input_ports[port].cursor_x
1020 					];
1021 
1022 				case RETRO_DEVICE_ID_LIGHTGUN_Y:
1023 					return (int16_t) retro.input_ports[port].axes[
1024 						retro.input_ports[port].cursor_y
1025 					];
1026 
1027 				case RETRO_DEVICE_ID_LIGHTGUN_TRIGGER:
1028 					return (int16_t) retro.input_ports[port].buttons[
1029 						retro.input_ports[port].cursor_btns[0]
1030 					];
1031 
1032 				case RETRO_DEVICE_ID_LIGHTGUN_CURSOR:
1033 					return (int16_t) retro.input_ports[port].buttons[
1034 						retro.input_ports[port].cursor_btns[1]
1035 					];
1036 
1037 				case RETRO_DEVICE_ID_LIGHTGUN_START:
1038 					return (int16_t) retro.input_ports[port].buttons[
1039 					retro.input_ports[port].cursor_btns[2]
1040 				];
1041 
1042 					case RETRO_DEVICE_ID_LIGHTGUN_TURBO:
1043 					return (int16_t) retro.input_ports[port].buttons[
1044 					retro.input_ports[port].cursor_btns[3]
1045 				];
1046 
1047 				case RETRO_DEVICE_ID_LIGHTGUN_PAUSE:
1048 					return (int16_t) retro.input_ports[port].buttons[
1049 					retro.input_ports[port].cursor_btns[4]
1050 				];
1051 		}
1052 		break;
1053 
1054 		case RETRO_DEVICE_ANALOG:
1055 			return map_analog_axis(port, ind, id);
1056 
1057 		break;
1058 
1059 		default:
1060 			LOG("Unknown device ID specified (%d), video will be disabled.\n", dev);
1061 	}
1062 
1063 	return 0;
1064 }
1065 
enable_graphseg()1066 static void enable_graphseg()
1067 {
1068 	struct arcan_shmif_cont cont =
1069 		arcan_shmif_acquire(&retro.shmcont,
1070 			NULL, SEGID_DEBUG, SHMIF_DISABLE_GUARD);
1071 
1072 	if (!cont.addr){
1073 		LOG("segment transfer failed, investigate.\n");
1074 		return;
1075 	}
1076 
1077 	if (!arcan_shmif_resize(&cont, 640, 180)){
1078 		LOG("resize failed on debug graph context\n");
1079 		return;
1080 	}
1081 
1082 	struct arcan_shmif_cont* pcont = malloc(sizeof(struct arcan_shmif_cont));
1083 
1084 	if (retro.sync_data)
1085 		retro.sync_data->free(&retro.sync_data);
1086 
1087 	*pcont = cont;
1088 	retro.sync_data = setup_synch_graph(pcont, false);
1089 }
1090 
libretro_inputstate(unsigned port,unsigned dev,unsigned ind,unsigned id)1091 static int16_t libretro_inputstate(unsigned port, unsigned dev,
1092 	unsigned ind, unsigned id)
1093 {
1094 	int16_t rv = libretro_inputmain(port, dev, ind, id);
1095 /* indirection to be used for debug graphing what inputs
1096  * the core actually requested */
1097 	return rv;
1098 }
1099 
1100 static int remaptbl[] = {
1101 	RETRO_DEVICE_ID_JOYPAD_A,
1102 	RETRO_DEVICE_ID_JOYPAD_B,
1103 	RETRO_DEVICE_ID_JOYPAD_X,
1104 	RETRO_DEVICE_ID_JOYPAD_Y,
1105 	RETRO_DEVICE_ID_JOYPAD_L,
1106 	RETRO_DEVICE_ID_JOYPAD_R,
1107 	RETRO_DEVICE_ID_JOYPAD_L2,
1108 	RETRO_DEVICE_ID_JOYPAD_R2,
1109 	RETRO_DEVICE_ID_JOYPAD_L3,
1110 	RETRO_DEVICE_ID_JOYPAD_R3
1111 };
1112 
1113 /*
1114  * A static default input layout for apps that provide no
1115  * higher-level semantic translation on its own.
1116  */
default_map(arcan_ioevent * ioev)1117 static void default_map(arcan_ioevent* ioev)
1118 {
1119 	if (ioev->datatype == EVENT_IDATATYPE_TRANSLATED){
1120 		int button = -1;
1121 		int port = -1;
1122 
1123 /* happy coincidence, keysyms here match retro_key (as they both
1124  * originate from SDL) */
1125 		switch(ioev->input.translated.keysym){
1126 		case RETROK_x:
1127 			port = 0;
1128 			button = RETRO_DEVICE_ID_JOYPAD_A;
1129 		break;
1130 		case RETROK_z:
1131 			port = 0;
1132 			button = RETRO_DEVICE_ID_JOYPAD_B;
1133 		break;
1134 		case RETROK_a:
1135 			port = 0;
1136 			button = RETRO_DEVICE_ID_JOYPAD_Y;
1137 		break;
1138 		case RETROK_s:
1139 			port = 0;
1140 			button = RETRO_DEVICE_ID_JOYPAD_X;
1141 		break;
1142 		case RETROK_RETURN:
1143 			port = 0;
1144 			button = RETRO_DEVICE_ID_JOYPAD_START;
1145 		break;
1146 		case RETROK_RSHIFT:
1147 			port = 0;
1148 			button = RETRO_DEVICE_ID_JOYPAD_SELECT;
1149 		break;
1150 		case RETROK_LEFT:
1151 			port = 0;
1152 			button = RETRO_DEVICE_ID_JOYPAD_LEFT;
1153 		break;
1154 		case RETROK_RIGHT:
1155 			port = 0;
1156 			button = RETRO_DEVICE_ID_JOYPAD_RIGHT;
1157 		break;
1158 		case RETROK_UP:
1159 			port = 0;
1160 			button = RETRO_DEVICE_ID_JOYPAD_UP;
1161 		break;
1162 		case RETROK_DOWN:
1163 			port = 0;
1164 			button = RETRO_DEVICE_ID_JOYPAD_DOWN;
1165 		break;
1166 		}
1167 
1168 		if (-1 != button && -1 != port){
1169 			retro.input_ports[
1170 				port].buttons[button] = ioev->input.translated.active;
1171 		}
1172 	}
1173 	else if (ioev->devkind == EVENT_IDEVKIND_GAMEDEV){
1174 		int port_number = ioev->devid % MAX_PORTS;
1175 		int button_number = ioev->subid % MAX_BUTTONS;
1176 		int button = remaptbl[button_number];
1177 		retro.input_ports[
1178 			port_number].buttons[button] = ioev->input.digital.active;
1179 	}
1180 }
1181 
ioev_ctxtbl(arcan_ioevent * ioev,const char * label)1182 static void ioev_ctxtbl(arcan_ioevent* ioev, const char* label)
1183 {
1184 	size_t remaptbl_sz = sizeof(remaptbl) / sizeof(remaptbl[0]) - 1;
1185 	int ind, button = -1, axis;
1186 	char* subtype;
1187 
1188 /*
1189  * if the calling script does no translation of its own
1190  */
1191 	if (label[0] == '\0'){
1192 		return default_map(ioev);
1193 	}
1194 
1195 	if (!retro.dirty_input && retro.sync_data)
1196 		retro.sync_data->mark_input(retro.sync_data, arcan_timemillis());
1197 
1198 	retro.dirty_input = true;
1199 
1200 	signed value = ioev->datatype == EVENT_IDATATYPE_TRANSLATED ?
1201 		ioev->input.translated.active : ioev->input.digital.active;
1202 
1203 	if (1 == sscanf(label, "PLAYER%d_", &ind) && ind > 0 &&
1204 		ind <= MAX_PORTS && (subtype = strchr(label, '_')) ){
1205 		subtype++;
1206 
1207 		if (1 == sscanf(subtype, "BUTTON%d", &button) && button > 0 &&
1208 			button <= MAX_BUTTONS - remaptbl_sz){
1209 			button--;
1210 			button = remaptbl[button];
1211 		}
1212 		else if (1 == sscanf(subtype, "AXIS%d", &axis) && axis > 0
1213 			&& axis <= MAX_AXES){
1214 			retro.input_ports[ind-1].axes[ axis - 1 ] =
1215 				ioev->input.analog.gotrel ?
1216 				ioev->input.analog.axisval[0] :
1217 				ioev->input.analog.axisval[1];
1218 		}
1219 		else if ( strcmp(subtype, "UP") == 0 )
1220 			button = RETRO_DEVICE_ID_JOYPAD_UP;
1221 		else if ( strcmp(subtype, "DOWN") == 0 )
1222 			button = RETRO_DEVICE_ID_JOYPAD_DOWN;
1223 		else if ( strcmp(subtype, "LEFT") == 0 )
1224 			button = RETRO_DEVICE_ID_JOYPAD_LEFT;
1225 		else if ( strcmp(subtype, "RIGHT") == 0 )
1226 			button = RETRO_DEVICE_ID_JOYPAD_RIGHT;
1227 		else if ( strcmp(subtype, "SELECT") == 0 )
1228 			button = RETRO_DEVICE_ID_JOYPAD_SELECT;
1229 		else if ( strcmp(subtype, "START") == 0 )
1230 			button = RETRO_DEVICE_ID_JOYPAD_START;
1231 		else;
1232 		if (button >= 0)
1233 			retro.input_ports[ind-1].buttons[button] = value;
1234 	}
1235 	else if (ioev->datatype == EVENT_IDATATYPE_TRANSLATED){
1236 		if (ioev->input.translated.keysym < RETROK_LAST)
1237 			retro.kbdtbl[ioev->input.translated.keysym] = value;
1238 	}
1239 }
1240 
toggle_ntscfilter(int toggle)1241 static void toggle_ntscfilter(int toggle)
1242 {
1243 	if (retro.ntscconv && toggle == 0){
1244 		free(retro.ntsc_imb);
1245 		retro.ntsc_imb = NULL;
1246 		retro.ntscconv = false;
1247 	}
1248 	else if (!retro.ntscconv && toggle == 1) {
1249 /* malloc etc. happens in resize */
1250 		retro.ntscconv = true;
1251 	}
1252 }
1253 
targetev(arcan_event * ev)1254 static inline void targetev(arcan_event* ev)
1255 {
1256 	arcan_tgtevent* tgt = &ev->tgt;
1257 	switch (tgt->kind){
1258 		case TARGET_COMMAND_RESET:
1259 		switch(tgt->ioevs[0].iv){
1260 		case 0:
1261 		case 1:
1262 			retro.reset();
1263 		break;
1264 		case 2:
1265 		case 3:
1266 /* send coreargs too */
1267 			if (retro.sync_data){
1268 				retro.sync_data->free(&retro.sync_data);
1269 				retro.sync_data = NULL;
1270 			}
1271 			arcan_shmif_enqueue(&retro.shmcont, &retro.ident);
1272 			reset_timing(true);
1273 		break;
1274 		}
1275 		break;
1276 
1277 		case TARGET_COMMAND_GRAPHMODE:
1278 			if (tgt->ioevs[0].iv == 1 || tgt->ioevs[1].iv == 2){
1279 				toggle_ntscfilter(tgt->ioevs[1].iv - 1);
1280 			}
1281 			else if (tgt->ioevs[0].iv == 3){
1282 				retro.ntscvals[0] = tgt->ioevs[1].fv;
1283 				retro.ntscvals[1] = tgt->ioevs[2].fv;
1284 				retro.ntscvals[2] = tgt->ioevs[3].fv;
1285 				retro.ntscvals[3] = tgt->ioevs[4].fv;
1286 				update_ntsc();
1287 			}
1288 		break;
1289 
1290 /* 0 : auto, -1 : single-step, > 0 render every n frames.
1291  * with 0, the second ioev defines pre-wake. -1 (last frame cost),
1292  * 0 (whatever), 1..mspf-1
1293  * ioev[2] audio preemu- frames, whenever the counter is reset,
1294  * perform n extra run()
1295  * passes to populate audiobuffer -- increases latency but reduces pops..
1296  * ioev[3] debugging
1297  * options -- added emulation cost ms (0 default, +n constant n ms,
1298  * -n 1..abs(n) random)
1299  */
1300 		case TARGET_COMMAND_FRAMESKIP:
1301 			retro.skipmode    = tgt->ioevs[0].iv;
1302 			retro.prewake     = tgt->ioevs[1].iv;
1303 			retro.preaudiogen = tgt->ioevs[2].iv;
1304 			retro.jitterstep  = tgt->ioevs[3].iv;
1305 			retro.jitterxfer  = tgt->ioevs[4].iv;
1306 			reset_timing(true);
1307 		break;
1308 
1309 /*
1310  * multiple possible receivers, e.g.
1311  * retexture transfer page, debugwindow or secondary etc. screens
1312  */
1313 		case TARGET_COMMAND_NEWSEGMENT:
1314 			if (tgt->ioevs[2].iv == SEGID_DEBUG)
1315 				enable_graphseg();
1316 		break;
1317 
1318 /* can safely assume there are no other events in the queue after this one,
1319  * more important for encode etc. that need to flush codecs */
1320 		case TARGET_COMMAND_EXIT:
1321 			arcan_shmif_drop(&retro.shmcont);
1322 			exit(EXIT_SUCCESS);
1323 		break;
1324 
1325 		case TARGET_COMMAND_DISPLAYHINT:
1326 /* don't do anything about these, scaling is implemented arcan - side */
1327 		break;
1328 
1329 		case TARGET_COMMAND_COREOPT:
1330 			retro.optdirty = true;
1331 			update_corearg(tgt->code, tgt->message);
1332 		break;
1333 
1334 		case TARGET_COMMAND_SETIODEV:
1335 			retro.set_ioport(tgt->ioevs[0].iv, tgt->ioevs[1].iv);
1336 		break;
1337 
1338 /* should also emit a corresponding event back with the current framenumber */
1339 		case TARGET_COMMAND_STEPFRAME:
1340 			if (tgt->ioevs[0].iv < 0);
1341 				else
1342 					while(tgt->ioevs[0].iv--)
1343 						retro.run();
1344 		break;
1345 
1346 /* store / rewind operate on the last FD set through FDtransfer */
1347 		case TARGET_COMMAND_STORE:
1348 		{
1349 			size_t dstsize = retro.serialize_size();
1350 			void* buf;
1351 			if (dstsize && ( buf = malloc( dstsize ) )){
1352 
1353 				if ( retro.serialize(buf, dstsize) ){
1354 					write_handle( buf, dstsize, ev->tgt.ioevs[0].iv, true );
1355 				} else
1356 					LOG("serialization failed.\n");
1357 
1358 				free(buf);
1359 			}
1360 			else
1361 				LOG("snapshot store requested without	any viable target.\n");
1362 		}
1363 		break;
1364 
1365 		case TARGET_COMMAND_RESTORE:
1366 		{
1367 			ssize_t dstsize = retro.serialize_size();
1368 			size_t ntc = dstsize;
1369 			void* buf;
1370 
1371 		if (dstsize && (buf = malloc(dstsize))){
1372 			char* dst = buf;
1373 			while (ntc){
1374 				ssize_t nr = read(ev->tgt.ioevs[0].iv, dst, ntc);
1375 				if (nr == -1){
1376 					if (errno != EINTR && errno != EAGAIN)
1377 						break;
1378 					else
1379 						continue;
1380 				}
1381 
1382 				dst += nr;
1383 				ntc -= nr;
1384 			}
1385 
1386 			if (ntc == 0){
1387 				retro.deserialize( buf, dstsize );
1388 				reset_timing(true);
1389 			}
1390 			else
1391 				LOG("failed restoring from snapshot (%s)\n", strerror(errno));
1392 
1393 			free(buf);
1394 		}
1395 		else
1396 			LOG("restore requested but core does not support savestates\n");
1397 		}
1398 		break;
1399 
1400 		default:
1401 			LOG("unknown target event (%s), ignored.\n",
1402 				arcan_shmif_eventstr(ev, NULL, 0));
1403 	}
1404 }
1405 
1406 /* use labels etc. for trying to populate the context table we also process
1407  * requests to save state, shutdown, reset, plug/unplug input, here */
flush_eventq()1408 static inline int flush_eventq(){
1409 	arcan_event ev;
1410 	int ps;
1411 
1412 	while ((ps = arcan_shmif_poll(&retro.shmcont, &ev)) > 0){
1413 		switch (ev.category){
1414 			case EVENT_IO:
1415 				ioev_ctxtbl(&(ev.io), ev.io.label);
1416 			break;
1417 
1418 			case EVENT_TARGET:
1419 				targetev(&ev);
1420 
1421 			default:
1422 			break;
1423 		}
1424 	}
1425 
1426 	return ps;
1427 }
1428 
update_ntsc()1429 void update_ntsc()
1430 {
1431 	static bool init;
1432 	if (!init){
1433 		retro.ntsc_opts = snes_ntsc_rgb;
1434 		retro.ntscctx = malloc(sizeof(snes_ntsc_t));
1435 		snes_ntsc_init(retro.ntscctx, &retro.ntsc_opts);
1436 		init = true;
1437 	}
1438 
1439 	snes_ntsc_update_setup(
1440 		retro.ntscctx, &retro.ntsc_opts,
1441 		retro.ntscvals[0], retro.ntscvals[1],
1442 		retro.ntscvals[2], retro.ntscvals[3]);
1443 }
1444 
1445 /* return true if we're in synch (may sleep),
1446  * return false if we're lagging behind */
retro_sync()1447 static inline bool retro_sync()
1448 {
1449 	long long int timestamp = arcan_timemillis();
1450 	retro.vframecount++;
1451 
1452 /* only skip (at most) 1 frame */
1453 	if (retro.skipframe_v || retro.empty_v)
1454 		return true;
1455 
1456 	long long int now  = timestamp - retro.basetime;
1457 	long long int next = floor( (double)retro.vframecount * retro.mspf );
1458 	int left = next - now;
1459 
1460 /* ntpd, settimeofday, wonky OS etc. or some massive stall, disqualify
1461  * DEBUGSTALL for the normal timing thing, or even switching 3d settings */
1462 	static int checked;
1463 
1464 /*
1465  * a jump this large (+- 10 frames) indicate some kind of problem with timing
1466  * or insanely slow emulation/rendering - nothing we can do about that except
1467  * try and resynch
1468  */
1469 	if (retro.skipmode == TARGET_SKIP_AUTO){
1470 		if (left < -200 || left > 200){
1471 			if (checked == 0){
1472 				checked = getenv("ARCAN_FRAMESERVER_DEBUGSTALL") ? -1 : 1;
1473 			}
1474 			else if (checked == 1){
1475 				LOG("frameskip stall (%d ms deviation) - detected, resetting timers.\n", left);
1476 				reset_timing(false);
1477 			}
1478 			return true;
1479 		}
1480 
1481 		if (left < -0.5 * retro.mspf){
1482 			if (retro.sync_data)
1483 				retro.sync_data->mark_drop(retro.sync_data, timestamp);
1484 			LOG("frameskip: at(%lld), next: (%lld), "
1485 				"deviation: (%d)\n", now, next, left);
1486 			retro.frameskips++;
1487 			return false;
1488 		}
1489 	}
1490 
1491 /* since we have to align the transfer with the parent, and it's better to
1492  * under- than overshoot- a deadline in that respect, prewake tries to
1493  * compensate lightly for scheduling jitter etc. */
1494 	if (left > retro.prewake){
1495 		LOG("sleep %d ms\n", left - retro.prewake);
1496 		arcan_timesleep( left - retro.prewake );
1497 	}
1498 
1499 	return true;
1500 }
1501 
1502 /*
1503  * used for debugging / testing synchronization during various levels of harsh
1504  * synchronization costs
1505  */
add_jitter(int num)1506 static inline long long add_jitter(int num)
1507 {
1508 	long long start = arcan_timemillis();
1509 	if (num < 0)
1510 		arcan_timesleep( rand() % abs(num) );
1511 	else if (num > 0)
1512 		arcan_timesleep( num );
1513 	long long stop = arcan_timemillis();
1514 	return stop - start;
1515 }
1516 
1517 #ifdef FRAMESERVER_LIBRETRO_3D
1518 /*
1519  * legacy from agp_* functions, mostly just to make symbols resolve
1520  */
platform_video_gfxsym(const char * sym)1521 void* platform_video_gfxsym(const char* sym)
1522 {
1523 	return arcan_shmifext_lookup(&retro.shmcont, sym);
1524 }
1525 
get_framebuffer()1526 static uintptr_t get_framebuffer()
1527 {
1528 	uintptr_t tgt = 0;
1529 	arcan_shmifext_gl_handles(&retro.shmcont, &tgt, NULL, NULL);
1530 	return tgt;
1531 }
1532 
setup_3dcore(struct retro_hw_render_callback * ctx)1533 static void setup_3dcore(struct retro_hw_render_callback* ctx)
1534 {
1535 /*
1536  * cheat with some envvars as the agp_ interface because it was not designed
1537  * to handle these sort of 'someone else decides which version to use'
1538  */
1539 	struct arcan_shmifext_setup setup = arcan_shmifext_defaults(&retro.shmcont);
1540 	if (ctx->context_type == RETRO_HW_CONTEXT_OPENGL_CORE){
1541 		setup.major = ctx->version_major;
1542 		setup.minor = ctx->version_minor;
1543 	}
1544 	enum shmifext_setup_status status;
1545 	setup.depth = 1;
1546 	if ((status = arcan_shmifext_setup(&retro.shmcont, setup)) != SHMIFEXT_OK){
1547 		LOG("couldn't setup 3D context, code: %d, giving up.\n", status);
1548 		arcan_shmif_drop(&retro.shmcont);
1549 		exit(EXIT_FAILURE);
1550 	}
1551 
1552 	retro.in_3d = true;
1553 
1554 	ctx->get_current_framebuffer = get_framebuffer;
1555 	ctx->get_proc_address = (retro_hw_get_proc_address_t) platform_video_gfxsym;
1556 
1557 	memcpy(&retro.hwctx, ctx,
1558 		sizeof(struct retro_hw_render_callback));
1559 }
1560 #endif
1561 
map_lretrofun()1562 static void map_lretrofun()
1563 {
1564 /* map normal functions that will be called repeatedly */
1565 	retro.run = (void(*)()) libretro_requirefun("retro_run");
1566 	retro.reset = (void(*)()) libretro_requirefun("retro_reset");
1567 	retro.load_game = (bool(*)(const struct retro_game_info* game))
1568 		libretro_requirefun("retro_load_game");
1569 	retro.serialize = (bool(*)(void*, size_t))
1570 		libretro_requirefun("retro_serialize");
1571 	retro.set_ioport = (void(*)(unsigned,unsigned))
1572 		libretro_requirefun("retro_set_controller_port_device");
1573 	retro.deserialize = (bool(*)(const void*, size_t))
1574 		libretro_requirefun("retro_unserialize");
1575 	retro.serialize_size = (size_t(*)())
1576 		libretro_requirefun("retro_serialize_size");
1577 
1578 /* setup callbacks */
1579 	( (void(*)(retro_video_refresh_t) )
1580 		libretro_requirefun("retro_set_video_refresh"))(libretro_vidcb);
1581 	( (size_t(*)(retro_audio_sample_batch_t))
1582 		libretro_requirefun("retro_set_audio_sample_batch"))(libretro_audcb);
1583 	( (void(*)(retro_audio_sample_t))
1584 		libretro_requirefun("retro_set_audio_sample"))(libretro_audscb);
1585 	( (void(*)(retro_input_poll_t))
1586 		libretro_requirefun("retro_set_input_poll"))(libretro_pollcb);
1587 	( (void(*)(retro_input_state_t))
1588 		libretro_requirefun("retro_set_input_state") )(libretro_inputstate);
1589 }
1590 
1591 /* might need to add another subgrammar here to handle multiple file-
1592  * images (another ??, why not just populate an array with images and a
1593  * swap- function.. */
load_resource(const char * resname)1594 static bool load_resource(const char* resname)
1595 {
1596 /* rather ugly -- core actually requires file-path */
1597 	if (retro.sysinfo.need_fullpath){
1598 		LOG("core(%s), core requires fullpath, resolved to (%s).\n",
1599 			retro.sysinfo.library_name, resname );
1600 
1601 		retro.gameinfo.data = NULL;
1602 		retro.gameinfo.path = strdup( resname );
1603 		retro.gameinfo.size = 0;
1604 	}
1605 	else {
1606 		retro.gameinfo.path = strdup( resname );
1607 		data_source res = arcan_open_resource(resname);
1608 		map_region map = arcan_map_resource(&res, true);
1609 		if (!map.ptr){
1610 			arcan_shmif_last_words(&retro.shmcont, "Couldn't open/map resource");
1611 			LOG("couldn't map (%s)\n", resname ? resname : "");
1612 //			return false;
1613 		}
1614 		retro.gameinfo.data = map.ptr;
1615 		retro.gameinfo.size = map.sz;
1616 	}
1617 
1618 	bool res = retro.load_game(&retro.gameinfo);
1619 	if (!res){
1620 		arcan_shmif_last_words(&retro.shmcont, "Core couldn't load resource");
1621 		LOG("libretro core rejected the resource\n");
1622 		return false;
1623 	}
1624 	return true;
1625 }
1626 
setup_av()1627 static void setup_av()
1628 {
1629 /* load the game, and if that fails, give up */
1630 #ifdef FRAMESERVER_LIBRETRO_3D
1631 	if (retro.hwctx.context_reset)
1632 		retro.hwctx.context_reset();
1633 #endif
1634 
1635 	( (void(*)(struct retro_system_av_info*))
1636 		libretro_requirefun("retro_get_system_av_info"))(&retro.avinfo);
1637 
1638 /* setup frameserver, synchronization etc. */
1639 	assert(retro.avinfo.timing.fps > 1);
1640 	assert(retro.avinfo.timing.sample_rate > 1);
1641 	retro.mspf = ( 1000.0 * (1.0 / retro.avinfo.timing.fps) );
1642 
1643 	retro.ntscconv = false;
1644 
1645 	LOG("video timing: %f fps (%f ms), audio samplerate: %f Hz\n",
1646 		(float)retro.avinfo.timing.fps, (float)retro.mspf,
1647 		(float)retro.avinfo.timing.sample_rate);
1648 }
1649 
setup_input()1650 static void setup_input()
1651 {
1652 /* setup standard device remapping tables, these can be changed
1653  * by the calling process with a corresponding target event. */
1654 	for (int i = 0; i < MAX_PORTS; i++){
1655 		retro.input_ports[i].cursor_x = 0;
1656 		retro.input_ports[i].cursor_y = 1;
1657 		retro.input_ports[i].cursor_btns[0] = 0;
1658 		retro.input_ports[i].cursor_btns[1] = 1;
1659 		retro.input_ports[i].cursor_btns[2] = 2;
1660 		retro.input_ports[i].cursor_btns[3] = 3;
1661 		retro.input_ports[i].cursor_btns[4] = 4;
1662 	}
1663 
1664 	retro.state_sz = retro.serialize_size();
1665 	arcan_shmif_enqueue(&retro.shmcont, &(struct arcan_event){
1666 		.category = EVENT_EXTERNAL,
1667 		.ext.kind = ARCAN_EVENT(STATESIZE),
1668 		.ext.stateinf.size = retro.state_sz
1669 	});
1670 }
1671 
dump_help()1672 static void dump_help()
1673 {
1674 	fprintf(stdout, "ARCAN_ARG (environment variable, "
1675 		"key1=value:key2:key3=value), arguments:\n"
1676 		"   key   \t   value   \t   description\n"
1677 		"---------\t-----------\t-----------------\n"
1678 		" core    \t filename  \t relative path to libretro core (req)\n"
1679 		" info    \t           \t load core, print information and quit\n"
1680 		" syspath \t path      \t set core system path\n"
1681 		" resource\t filename  \t resource file to load with core\n"
1682 		" vbufc   \t num       \t (1) 1..4 - number of video buffers\n"
1683 		" abufc   \t num       \t (8) 1..16 - number of audio buffers\n"
1684 		" abufsz  \t num       \t audio buffer size in bytes (default = probe)\n"
1685     " noreset \t           \t (3D) disable context reset calls\n"
1686     "---------\t-----------\t-----------------\n"
1687 	);
1688 }
1689 
1690 /* map up a libretro compatible library resident at fullpath:game,
1691  * if resource is /info, no loading will occur but a dump of the capabilities
1692  * of the core will be sent to stdout. */
afsrv_game(struct arcan_shmif_cont * cont,struct arg_arr * args)1693 int	afsrv_game(struct arcan_shmif_cont* cont, struct arg_arr* args)
1694 {
1695 	if (!cont || !args){
1696 		dump_help();
1697 		return EXIT_FAILURE;
1698 	}
1699 
1700 	retro.converter = (pixconv_fun) libretro_rgb1555_rgba;
1701 	retro.inargs = args;
1702 	retro.shmcont = *cont;
1703 
1704 	const char* libname = NULL;
1705 	const char* resname = NULL;
1706 	const char* val;
1707 
1708 	if (arg_lookup(args, "core", 0, &val))
1709 		libname = strdup(val);
1710 
1711 	if (arg_lookup(args, "resource", 0, &val))
1712 		resname = strdup(val);
1713 
1714 	if (arg_lookup(args, "abufc", 0, &val)){
1715 		uint8_t bufc = strtoul(val, NULL, 10);
1716 		retro.abuf_cnt = bufc > 0 && bufc < 16 ? bufc : 8;
1717 	}
1718 
1719 	if (arg_lookup(args, "vbufc", 0, &val)){
1720 		uint8_t bufc = strtoul(val, NULL, 10);
1721 		retro.vbuf_cnt = bufc > 0 && bufc <= 4 ? bufc : 1;
1722 	}
1723 
1724 	if (arg_lookup(args, "abufsz", 0, &val)){
1725 		retro.def_abuf_sz = strtoul(val, NULL, 10);
1726 	}
1727 
1728 /* system directory doesn't really match any of arcan namespaces,
1729  * provide some kind of global-  user overridable way */
1730 	const char* spath = getenv("ARCAN_LIBRETRO_SYSPATH");
1731 	if (!spath)
1732 		spath = "./";
1733 
1734 	if (arg_lookup(args, "syspath", 0, &val))
1735 		spath = val;
1736 
1737 /* some cores (mednafen-psx, ..) currently breaks on relative paths,
1738  * so resolve to absolute one for the time being */
1739 	retro.syspath = realpath(spath, NULL);
1740 
1741 /* set if we only want to dump status about the core, info etc.  (which
1742  * incidentally was then moved to yet another format to parse and manage as
1743  * a separate file, not an embedded string in core.. */
1744 	bool info_only = arg_lookup(args, "info", 0, NULL) || cont->addr == NULL;
1745 
1746 	if (!libname || *libname == 0){
1747 		LOG("error > No core specified.\n");
1748 		dump_help();
1749 
1750 		return EXIT_FAILURE;
1751 	}
1752 
1753 	if (!info_only)
1754 		LOG("Loading core (%s) with resource (%s)\n", libname ?
1755 			libname : "missing arg.", resname ? resname : "missing resarg.");
1756 
1757 /* map up functions and test version */
1758 	lastlib = dlopen(libname, RTLD_LAZY);
1759 	if (!globallib)
1760 		globallib = dlopen(NULL, RTLD_LAZY);
1761 
1762 	if (!lastlib){
1763 		arcan_shmif_last_words(&retro.shmcont, "Couldn't load/open core");
1764 		LOG("couldn't open library (%s), giving up.\n", libname);
1765 		exit(EXIT_FAILURE);
1766 	}
1767 
1768 	void (*initf)() = libretro_requirefun("retro_init");
1769 	unsigned (*apiver)() = (unsigned(*)())
1770 		libretro_requirefun("retro_api_version");
1771 
1772 	( (void(*)(retro_environment_t))
1773 		libretro_requirefun("retro_set_environment"))(libretro_setenv);
1774 
1775 /* get the lib up and running, ensure that the version matches
1776  * the one we got from the header */
1777 	if (!( (initf(), true) && apiver() == RETRO_API_VERSION) )
1778 		return EXIT_FAILURE;
1779 
1780 	((void(*)(struct retro_system_info*))
1781 	 libretro_requirefun("retro_get_system_info"))(&retro.sysinfo);
1782 
1783 	if (info_only){
1784 		fprintf(stdout, "arcan_frameserver(info)\nlibrary:%s\n"
1785 			"version:%s\nextensions:%s\n/arcan_frameserver(info)",
1786 			retro.sysinfo.library_name, retro.sysinfo.library_version,
1787 			retro.sysinfo.valid_extensions);
1788 		return EXIT_FAILURE;
1789 	}
1790 
1791 	LOG("libretro(%s), version %s loaded. Accepted extensions: %s\n",
1792 		retro.sysinfo.library_name, retro.sysinfo.library_version,
1793 		retro.sysinfo.valid_extensions);
1794 
1795 	resize_shmpage(retro.avinfo.geometry.base_width,
1796 		retro.avinfo.geometry.base_height, true);
1797 
1798 /* map functions to context structure */
1799    unsigned base_height;   /* Nominal video height of game. */
1800 
1801 /* send some information on what core is actually loaded etc. */
1802 	retro.ident.ext.kind = ARCAN_EVENT(IDENT);
1803 	size_t msgsz = COUNT_OF(retro.ident.ext.message.data);
1804 	snprintf((char*)retro.ident.ext.message.data, msgsz, "%s %s",
1805 		retro.sysinfo.library_name, retro.sysinfo.library_version);
1806 	arcan_shmif_enqueue(&retro.shmcont, &retro.ident);
1807 
1808 /* map the functions we need during runtime */
1809 	map_lretrofun();
1810 
1811 /* load / start */
1812 	if (!resname && retro.res_empty)
1813 		;
1814 	else if (!load_resource(resname ? resname : ""))
1815 		return EXIT_FAILURE;
1816 
1817 /* remixing, conversion functions for color formats... */
1818 	setup_av();
1819 
1820 /* default input tables, state management */
1821 	setup_input();
1822 
1823 /* since we're 'guaranteed' to get at least one input callback each run(),
1824  * call, we multiplex parent event processing as well */
1825 	arcan_event outev = {.ext.framestatus.framenumber = 0};
1826 
1827 /* some cores die on this kind of reset, retro.reset() e.g. NXengine
1828  * retro_reset() */
1829 
1830 	if (retro.state_sz > 0)
1831 		retro.rollback_state = malloc(retro.state_sz);
1832 
1833 /* basetime is used as epoch for all other timing calculations, run
1834  * an initial frame because sometimes first run can introduce a large stall */
1835 	retro.skipframe_v = retro.skipframe_a = true;
1836 	retro.run();
1837 	retro.skipframe_v = retro.skipframe_a = false;
1838 	retro.basetime = arcan_timemillis();
1839 
1840 /* pre-audio is a last- resort to work around buffering size issues
1841  * in audio layers -- run one or more frames of emulation, ignoring
1842  * timing and input, and just keep the audioframes */
1843 	do_preaudio();
1844 	long long int start, stop;
1845 
1846 /* don't want the UI to draw a mouse cursor in this window */
1847 	arcan_shmif_enqueue(&retro.shmcont, &(struct arcan_event){
1848 		.category = EVENT_EXTERNAL,
1849 		.ext.kind = ARCAN_EVENT(CURSORHINT),
1850 		.ext.message = "hidden"
1851 	});
1852 
1853 	while (flush_eventq() >= 0){
1854 		if (retro.skipmode >= TARGET_SKIP_FASTFWD)
1855 			libretro_skipnframes(retro.skipmode -
1856 				TARGET_SKIP_FASTFWD + 1, true);
1857 
1858 		else if (retro.skipmode >= TARGET_SKIP_STEP)
1859 			libretro_skipnframes(retro.skipmode -
1860 				TARGET_SKIP_STEP + 1, false);
1861 
1862 		else if (retro.skipmode <= TARGET_SKIP_ROLLBACK &&
1863 			retro.dirty_input){
1864 /* last entry will always be the current front */
1865 			retro.deserialize(retro.rollback_state +
1866 				retro.state_sz * retro.rollback_front, retro.state_sz);
1867 
1868 /* rollback to desired "point", run frame (which will consume input)
1869  * then roll forward to next video frame */
1870 			process_frames(retro.rollback_window - 1, true, true);
1871 			retro.dirty_input = false;
1872 		}
1873 
1874 		testcounter = 0;
1875 
1876 /* add jitter, jitterstep, framecost etc. are used for debugging /
1877  * testing by adding delays at various key synchronization points */
1878 		start = arcan_timemillis();
1879 			add_jitter(retro.jitterstep);
1880 			process_frames(1, false, false);
1881 		stop = arcan_timemillis();
1882 		retro.framecost = stop - start;
1883 		if (retro.sync_data){
1884 			retro.sync_data->mark_start(retro.sync_data, start);
1885 			retro.sync_data->mark_stop(retro.sync_data, stop);
1886 		}
1887 
1888 #ifdef _DEBUG
1889 		if (testcounter != 1){
1890 			static bool countwarn = 0;
1891 			if (!countwarn && (countwarn = true))
1892 				LOG("inconsistent core behavior, "
1893 					"expected 1 video frame / run(), got %d\n", testcounter);
1894 		}
1895 #endif
1896 
1897 /* begin with synching video, as it is the one with the biggest deadline
1898  * penalties and the cost for resampling can be enough if we are close */
1899 		if (!retro.empty_v){
1900 			long long elapsed = add_jitter(retro.jitterstep);
1901 #ifdef FRAMESERVER_LIBRETRO_3D
1902 			if (retro.got_3dframe){
1903 				int handlestatus = arcan_shmifext_signal(&retro.shmcont,
1904 					0, SHMIF_SIGVID, SHMIFEXT_BUILTIN);
1905 				if (handlestatus >= 0)
1906 					elapsed += handlestatus;
1907 				retro.got_3dframe = false;
1908 				LOG("3d-video transfer cost (%lld)\n", elapsed);
1909 			}
1910 /* note the dangling else */
1911 			else
1912 #endif
1913 			elapsed += arcan_shmif_signal(&retro.shmcont, SHMIF_SIGVID | SHMIF_SIGBLK_NONE);
1914 
1915 			retro.transfercost = elapsed;
1916 			LOG("video transfer cost (%lld)\n", elapsed);
1917 			if (retro.sync_data)
1918 				retro.sync_data->mark_transfer(retro.sync_data,
1919 					stop, retro.transfercost);
1920 		}
1921 
1922 /* sleep / synch / skipframes */
1923 		retro.skipframe_a = false;
1924 		retro.skipframe_v = !retro_sync();
1925 
1926 		if (retro.sync_data)
1927 				push_stats();
1928 	}
1929 	return EXIT_SUCCESS;
1930 }
1931 
push_stats()1932 static void push_stats()
1933 {
1934 	char scratch[512];
1935 	long long int timestamp = arcan_timemillis();
1936 
1937 	snprintf(scratch, 512, "%s, %s\n"
1938 		"%s, %f fps, %f Hz\n"
1939 		"Mode: %d, Preaudio: %d\n Jitter: %d/%d\n"
1940 		"(A,V - A/V) %lld, %lld - %lld\n"
1941 		"Real (Hz): %f\n"
1942 		"cost,wake,xfer: %d, %d, %d ms \n",
1943 		(char*)retro.sysinfo.library_name,
1944 		(char*)retro.sysinfo.library_version,
1945 		(char*)retro.colorspace,
1946 		(float)retro.avinfo.timing.fps,
1947 		(float)retro.avinfo.timing.sample_rate,
1948 		retro.skipmode, retro.preaudiogen,
1949 		retro.jitterstep, retro.jitterxfer,
1950 		retro.aframecount, retro.vframecount,
1951 		retro.aframecount / retro.vframecount,
1952 		1000.0f * (float)retro.aframecount /
1953 			(float)(timestamp - retro.basetime),
1954 		retro.framecost, retro.prewake, retro.transfercost
1955 	);
1956 
1957 	if (!retro.sync_data->update(
1958 		retro.sync_data, retro.mspf, scratch)){
1959 		retro.sync_data->free(&retro.sync_data);
1960 	}
1961 }
1962