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