+// platform hooks
+void (*pl_plat_clear)(void);
+void (*pl_plat_blit)(int doffs, const void *src, int w, int h,
+ int sstride, int bgr24);
+void (*pl_plat_hud_print)(int x, int y, const char *str, int bpp);
+
+
+static __attribute__((noinline)) int get_cpu_ticks(void)
+{
+ static unsigned long last_utime;
+ static int fd;
+ unsigned long utime, ret;
+ char buf[128];
+
+ if (fd == 0)
+ fd = open("/proc/self/stat", O_RDONLY);
+ lseek(fd, 0, SEEK_SET);
+ buf[0] = 0;
+ read(fd, buf, sizeof(buf));
+ buf[sizeof(buf) - 1] = 0;
+
+ sscanf(buf, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu", &utime);
+ ret = utime - last_utime;
+ if (ret > 200)
+ ret = 0;
+ last_utime = utime;
+ return ret;
+}
+
+static void hud_print(void *fb, int w, int x, int y, const char *text)
+{
+ if (pl_plat_hud_print)
+ pl_plat_hud_print(x, y, text, pl_vout_bpp);
+ else if (pl_vout_bpp == 16)
+ basic_text_out16_nf(fb, w, x, y, text);
+}
+
+static void hud_printf(void *fb, int w, int x, int y, const char *texto, ...)
+{
+ va_list args;
+ char buffer[256];
+
+ va_start(args, texto);
+ vsnprintf(buffer, sizeof(buffer), texto, args);
+ va_end(args);
+
+ hud_print(fb, w, x, y, buffer);
+}
+
+static void print_msg(int h, int border)
+{
+ hud_print(pl_vout_buf, pl_vout_w, border + 2, h - 10, hud_msg);
+}
+
+static void print_fps(int h, int border)
+{
+ hud_printf(pl_vout_buf, pl_vout_w, border + 2, h - 10,
+ "%2d %4.1f", pl_rearmed_cbs.flips_per_sec,
+ pl_rearmed_cbs.vsps_cur);
+}
+
+static void print_cpu_usage(int w, int h, int border)
+{
+ hud_printf(pl_vout_buf, pl_vout_w, pl_vout_w - border - 28, h - 10,
+ "%3d", pl_rearmed_cbs.cpu_usage);
+}
+
+// draw 192x8 status of 24 sound channels
+static __attribute__((noinline)) void draw_active_chans(int vout_w, int vout_h)
+{
+ extern void spu_get_debug_info(int *chans_out, int *run_chans,
+ int *fmod_chans_out, int *noise_chans_out); // hack
+ int live_chans, run_chans, fmod_chans, noise_chans;
+
+ static const unsigned short colors[2] = { 0x1fe3, 0x0700 };
+ unsigned short *dest = (unsigned short *)pl_vout_buf +
+ vout_w * (vout_h - 10) + vout_w / 2 - 192/2;
+ unsigned short *d, p;
+ int c, x, y;
+
+ if (dest == NULL || pl_vout_bpp != 16)
+ return;
+
+ spu_get_debug_info(&live_chans, &run_chans, &fmod_chans, &noise_chans);
+
+ for (c = 0; c < 24; c++) {
+ d = dest + c * 8;
+ p = !(live_chans & (1<<c)) ? (run_chans & (1<<c) ? 0x01c0 : 0) :
+ (fmod_chans & (1<<c)) ? 0xf000 :
+ (noise_chans & (1<<c)) ? 0x001f :
+ colors[c & 1];
+ for (y = 0; y < 8; y++, d += vout_w)
+ for (x = 0; x < 8; x++)
+ d[x] = p;
+ }
+}
+
+void pl_print_hud(int w, int h, int xborder)
+{
+ if (h < 16)
+ return;
+
+ xborder += (pl_vout_w - w) / 2;
+
+ if (g_opts & OPT_SHOWSPU)
+ draw_active_chans(w, h);
+
+ if (hud_msg[0] != 0)
+ print_msg(h, xborder);
+ else if (g_opts & OPT_SHOWFPS)
+ print_fps(h, xborder);
+
+ if (g_opts & OPT_SHOWCPU)
+ print_cpu_usage(w, h, xborder);
+}
+
+/* update scaler target size according to user settings */
+static void update_layer_size(int w, int h)
+{
+ float mult;
+ int imult;
+
+ switch (g_scaler) {
+ case SCALE_1_1:
+ g_layer_w = w; g_layer_h = h;
+ break;
+
+ case SCALE_4_3v2:
+ if (h > g_menuscreen_h || (240 < h && h <= 360))
+ goto fractional_4_3;
+
+ // 4:3 that prefers integer scaling
+ imult = g_menuscreen_h / h;
+ g_layer_w = w * imult;
+ g_layer_h = h * imult;
+ mult = (float)g_layer_w / (float)g_layer_h;
+ if (mult < 1.25f || mult > 1.666f)
+ g_layer_w = 4.0f/3.0f * (float)g_layer_h;
+ printf(" -> %dx%d %.1f\n", g_layer_w, g_layer_h, mult);
+ break;
+
+ fractional_4_3:
+ case SCALE_4_3:
+ mult = 240.0f / (float)h * 4.0f / 3.0f;
+ if (h > 256)
+ mult *= 2.0f;
+ g_layer_w = mult * (float)g_menuscreen_h;
+ g_layer_h = g_menuscreen_h;
+ printf(" -> %dx%d %.1f\n", g_layer_w, g_layer_h, mult);
+ break;
+
+ case SCALE_FULLSCREEN:
+ g_layer_w = g_menuscreen_w;
+ g_layer_h = g_menuscreen_h;
+ break;
+
+ default:
+ break;
+ }
+
+ g_layer_x = g_menuscreen_w / 2 - g_layer_w / 2;
+ g_layer_y = g_menuscreen_h / 2 - g_layer_h / 2;
+ if (g_layer_x < 0) g_layer_x = 0;
+ if (g_layer_y < 0) g_layer_y = 0;
+ if (g_layer_w > g_menuscreen_w) g_layer_w = g_menuscreen_w;
+ if (g_layer_h > g_menuscreen_h) g_layer_h = g_menuscreen_h;
+}
+
+// XXX: this is platform specific really
+static int resolution_ok(int w, int h)
+{
+ return w <= 1024 && h <= 512;
+}
+
+static void pl_vout_set_mode(int w, int h, int raw_w, int raw_h, int bpp)
+{
+ int vout_w, vout_h, vout_bpp;
+
+ // special h handling, Wipeout likes to change it by 1-6
+ static int vsync_cnt_ms_prev;
+ if ((unsigned int)(vsync_cnt - vsync_cnt_ms_prev) < 5*60)
+ h = (h + 7) & ~7;
+ vsync_cnt_ms_prev = vsync_cnt;
+
+ psx_w = raw_w;
+ psx_h = raw_h;
+ vout_w = w;
+ vout_h = h;
+ vout_bpp = psx_bpp = bpp;
+
+ pl_vout_scale = 1;
+#ifdef __ARM_NEON__
+ if (soft_filter) {
+ if (resolution_ok(w * 2, h * 2) && bpp == 16) {
+ vout_w *= 2;
+ vout_h *= 2;
+ pl_vout_scale = 2;
+ }
+ else {
+ // filter unavailable
+ hud_msg[0] = 0;
+ }
+ }
+#endif
+
+ if (pl_vout_buf != NULL && vout_w == pl_vout_w && vout_h == pl_vout_h
+ && vout_bpp == pl_vout_bpp)
+ return;
+
+ update_layer_size(vout_w, vout_h);
+
+ pl_vout_buf = plat_gvideo_set_mode(&vout_w, &vout_h, &vout_bpp);
+ if (pl_vout_buf == NULL && pl_plat_blit == NULL)
+ fprintf(stderr, "failed to set mode %dx%d@%d\n",
+ vout_w, vout_h, psx_bpp);
+ else {
+ pl_vout_w = vout_w;
+ pl_vout_h = vout_h;
+ pl_vout_bpp = vout_bpp;
+ }
+
+ menu_notify_mode_change(pl_vout_w, pl_vout_h, pl_vout_bpp);
+}
+
+static void pl_vout_flip(const void *vram, int stride, int bgr24, int w, int h)
+{
+ static int doffs_old, clear_counter;
+ unsigned char *dest = pl_vout_buf;
+ const unsigned short *src = vram;
+ int dstride = pl_vout_w, h1 = h;
+ int doffs;
+
+ pcnt_start(PCNT_BLIT);
+
+ if (vram == NULL) {
+ // blanking
+ if (pl_plat_clear)
+ pl_plat_clear();
+ else
+ memset(pl_vout_buf, 0,
+ dstride * pl_vout_h * pl_vout_bpp / 8);
+ goto out_hud;
+ }
+
+ // borders
+ doffs = (dstride - w * pl_vout_scale) / 2 & ~1;
+
+ if (doffs > doffs_old)
+ clear_counter = 2;
+ doffs_old = doffs;
+
+ if (clear_counter > 0) {
+ if (pl_plat_clear)
+ pl_plat_clear();
+ else
+ memset(pl_vout_buf, 0,
+ dstride * pl_vout_h * pl_vout_bpp / 8);
+ clear_counter--;
+ }
+
+ if (pl_plat_blit)
+ {
+ pl_plat_blit(doffs, src, w, h, stride, bgr24);
+ goto out_hud;
+ }
+
+ if (dest == NULL)
+ goto out;
+
+ dest += doffs * 2;
+
+ if (bgr24)
+ {
+ if (pl_rearmed_cbs.only_16bpp) {
+ for (; h1-- > 0; dest += dstride * 2, src += stride)
+ {
+ bgr888_to_rgb565(dest, src, w * 3);
+ }
+ }
+ else {
+ dest -= doffs * 2;
+ dest += (doffs / 8) * 24;
+
+ for (; h1-- > 0; dest += dstride * 3, src += stride)
+ {
+ bgr888_to_rgb888(dest, src, w * 3);
+ }
+ }
+ }
+#ifdef __ARM_NEON__
+ else if (soft_filter == SOFT_FILTER_SCALE2X && pl_vout_scale == 2)
+ {
+ neon_scale2x_16_16(src, (void *)dest, w,
+ stride * 2, dstride * 2, h);
+ }
+ else if (soft_filter == SOFT_FILTER_EAGLE2X && pl_vout_scale == 2)
+ {
+ neon_eagle2x_16_16(src, (void *)dest, w,
+ stride * 2, dstride * 2, h);
+ }
+#endif
+ else
+ {
+ for (; h1-- > 0; dest += dstride * 2, src += stride)
+ {
+ bgr555_to_rgb565(dest, src, w * 2);
+ }
+ }
+
+out_hud:
+ pl_print_hud(w * pl_vout_scale, h * pl_vout_scale, 0);
+
+out:
+ pcnt_end(PCNT_BLIT);
+
+ // let's flip now
+ pl_vout_buf = plat_gvideo_flip();
+ pl_rearmed_cbs.flip_cnt++;
+}
+
+static int pl_vout_open(void)
+{
+ struct timeval now;
+
+ // force mode update on pl_vout_set_mode() call from gpulib/vout_pl
+ pl_vout_buf = NULL;
+
+ plat_gvideo_open(is_pal);
+
+ gettimeofday(&now, 0);
+ vsync_usec_time = now.tv_usec;
+ while (vsync_usec_time >= frame_interval)
+ vsync_usec_time -= frame_interval;
+
+ return 0;
+}
+
+static void pl_vout_close(void)
+{
+ plat_gvideo_close();
+}
+
+static void pl_set_gpu_caps(int caps)