try to emulate borders properly
authornotaz <notasas@gmail.com>
Wed, 16 Aug 2023 22:55:11 +0000 (01:55 +0300)
committernotaz <notasas@gmail.com>
Sat, 19 Aug 2023 22:37:53 +0000 (01:37 +0300)
frontend/cspace.c
frontend/libretro.c
frontend/libretro_core_options.h
frontend/menu.c
frontend/plugin_lib.c
frontend/plugin_lib.h
plugins/gpu_neon/psx_gpu_if.c
plugins/gpulib/gpu.c
plugins/gpulib/gpu.h
plugins/gpulib/vout_pl.c

index 785b3d1..a3e3301 100644 (file)
@@ -215,7 +215,7 @@ void bgr555_to_uyvy(void *d, const void *s, int pixels)
   int r0, g0, b0, r1, g1, b1;
   int y0, y1, u, v;
 
-  for (; pixels > 0; src += 2, dst++, pixels -= 2)
+  for (; pixels > 1; src += 2, dst++, pixels -= 2)
   {
     b0 = (src[0] >> 10) & 0x1f;
     g0 = (src[0] >> 5) & 0x1f;
index 4d29e16..6a3a97c 100644 (file)
@@ -80,7 +80,7 @@ static unsigned msg_interface_version = 0;
 static void *vout_buf;
 static void *vout_buf_ptr;
 static int vout_width, vout_height;
-static int vout_doffs_old, vout_fb_dirty;
+static int vout_fb_dirty;
 static bool vout_can_dupe;
 static bool duping_enable;
 static bool found_bios;
@@ -267,29 +267,22 @@ static void convert(void *buf, size_t bytes)
 }
 #endif
 
-static void vout_flip(const void *vram, int stride, int bgr24, int w, int h)
+static void vout_flip(const void *vram, int stride, int bgr24,
+      int x, int y, int w, int h, int dims_changed)
 {
    unsigned short *dest = vout_buf_ptr;
    const unsigned short *src = vram;
    int dstride = vout_width, h1 = h;
-   int doffs;
 
-   if (vram == NULL)
+   if (vram == NULL || dims_changed)
    {
+      memset(vout_buf_ptr, 0, dstride * vout_height * 2);
       // blanking
-      memset(vout_buf_ptr, 0, dstride * h * 2);
-      goto out;
+      if (vram == NULL)
+         goto out;
    }
 
-   doffs = (vout_height - h) * dstride;
-   doffs += (dstride - w) / 2 & ~1;
-   if (doffs != vout_doffs_old)
-   {
-      // clear borders
-      memset(vout_buf_ptr, 0, dstride * h * 2);
-      vout_doffs_old = doffs;
-   }
-   dest += doffs;
+   dest += x + y * dstride;
 
    if (bgr24)
    {
@@ -2152,11 +2145,37 @@ static void update_variables(bool in_flight)
    if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
    {
       if (strcmp(var.value, "disabled") == 0)
-        Config.GpuListWalking = 0;
+         Config.GpuListWalking = 0;
       else if (strcmp(var.value, "enabled") == 0)
-        Config.GpuListWalking = 1;
+         Config.GpuListWalking = 1;
       else // auto
-        Config.GpuListWalking = -1;
+         Config.GpuListWalking = -1;
+   }
+
+   var.value = NULL;
+   var.key = "pcsx_rearmed_screen_centering";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      if (strcmp(var.value, "game") == 0)
+         pl_rearmed_cbs.screen_centering_type = 1;
+      else if (strcmp(var.value, "manual") == 0)
+         pl_rearmed_cbs.screen_centering_type = 2;
+      else // auto
+         pl_rearmed_cbs.screen_centering_type = 0;
+   }
+
+   var.value = NULL;
+   var.key = "pcsx_rearmed_screen_centering_x";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      pl_rearmed_cbs.screen_centering_x = atoi(var.value);
+   }
+
+   var.value = NULL;
+   var.key = "pcsx_rearmed_screen_centering_y";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      pl_rearmed_cbs.screen_centering_y = atoi(var.value);
    }
 
 #ifdef THREAD_RENDERING
index e20503e..ef25f7b 100644 (file)
@@ -435,6 +435,50 @@ struct retro_core_option_v2_definition option_defs_us[] = {
       },
       "auto",
    },
+   {
+      "pcsx_rearmed_screen_centering",
+      "(GPU) Screen centering",
+      NULL,
+      "The PSX has a feature allowing it to shift the image position on screen. Some (mostly PAL) games used this feature in a strange way making the image miscentered and causing borders to appear. With 'Auto' the emulator tries to correct this miscentering automatically. 'Game-controlled' uses the settings supplied by the game. 'Manual' allows to override those values with the settings below.",
+      NULL,
+      "video",
+      {
+         { "auto", "Auto" },
+         { "game", "Game-controlled" },
+         { "manual", "Manual" },
+         { NULL, NULL },
+      },
+      "auto",
+   },
+#define V(x) { #x, NULL }
+   {
+      "pcsx_rearmed_screen_centering_x",
+      "(GPU) Manual screen centering X",
+      NULL,
+      "X offset of the frame buffer. Only effective when 'Screen centering' is set to 'Manual'.",
+      NULL,
+      "video",
+      {
+         V(-16), V(-14), V(-12), V(-10), V(-8), V(-6), V(-4), V(-2), V(0), V(2), V(4), V(6), V(8), V(10), V(12), V(14), V(16),
+         { NULL, NULL },
+      },
+      "0",
+   },
+   {
+      "pcsx_rearmed_screen_centering_y",
+      "(GPU) Manual screen centering Y",
+      NULL,
+      "Y offset of the frame buffer. Only effective when 'Screen centering' is set to 'Manual'.",
+      NULL,
+      "video",
+      {
+         V(-16), V(-15), V(-14), V(-13), V(-12), V(-11), V(-10), V(-9), V(-8), V(-7), V(-6), V(-5), V(-4), V(-3), V(-2), V(-1),
+        V(0), V(1), V(2), V(3), V(4), V(5), V(6), V(7), V(8), V(9), V(10), V(11), V(12), V(13), V(14), V(15), V(16),
+         { NULL, NULL },
+      },
+      "0",
+   },
+#undef V
 #ifdef GPU_NEON
    {
       "pcsx_rearmed_neon_interlace_enable_v2",
index 9586bae..fb71224 100644 (file)
@@ -87,6 +87,7 @@ typedef enum
        MA_OPT_VOUT_MODE,
        MA_OPT_SCANLINES,
        MA_OPT_SCANLINE_LEVEL,
+       MA_OPT_CENTERING,
 } menu_id;
 
 static int last_vout_w, last_vout_h, last_vout_bpp;
@@ -450,6 +451,9 @@ static const struct {
        CE_INTVAL_P(gpu_peopsgl.iVRamSize),
        CE_INTVAL_P(gpu_peopsgl.iTexGarbageCollection),
        CE_INTVAL_P(gpu_peopsgl.dwActFixes),
+       CE_INTVAL_P(screen_centering_type),
+       CE_INTVAL_P(screen_centering_x),
+       CE_INTVAL_P(screen_centering_y),
        CE_INTVAL(spu_config.iUseReverb),
        CE_INTVAL(spu_config.iXAPitch),
        CE_INTVAL(spu_config.iUseInterpolation),
@@ -1252,6 +1256,7 @@ static const char *men_soft_filter[] = { "None",
 #endif
        NULL };
 static const char *men_dummy[] = { NULL };
+static const char *men_centering[] = { "Auto", "Ingame", "Force", NULL };
 static const char h_scaler[]    = "int. 2x  - scales w. or h. 2x if it fits on screen\n"
                                  "int. 4:3 - uses integer if possible, else fractional";
 static const char h_cscaler[]   = "Displays the scaler layer, you can resize it\n"
@@ -1316,6 +1321,7 @@ static int menu_loop_cscaler(int id, int keys)
 
 static menu_entry e_menu_gfx_options[] =
 {
+       mee_enum      ("Screen centering",         MA_OPT_CENTERING, pl_rearmed_cbs.screen_centering_type, men_centering),
        mee_enum_h    ("Scaler",                   MA_OPT_VARSCALER, g_scaler, men_scaler, h_scaler),
        mee_enum      ("Video output mode",        MA_OPT_VOUT_MODE, plat_target.vout_method, men_dummy),
        mee_onoff     ("Software Scaling",         MA_OPT_SCALER2, soft_scaling, 1),
@@ -2581,7 +2587,7 @@ void menu_init(void)
 
        i = plat_target.cpu_clock_set != NULL
                && plat_target.cpu_clock_get != NULL && cpu_clock_st > 0;
-       me_enable(e_menu_gfx_options, MA_OPT_CPU_CLOCKS, i);
+       me_enable(e_menu_options, MA_OPT_CPU_CLOCKS, i);
 
        i = me_id2offset(e_menu_gfx_options, MA_OPT_VOUT_MODE);
        e_menu_gfx_options[i].data = plat_target.vout_methods;
index d5cec76..bdf09c7 100644 (file)
@@ -16,6 +16,7 @@
 #include <fcntl.h>
 #include <unistd.h>
 #include <pthread.h>
+#include <assert.h>
 
 #include "libpicofe/fonts.h"
 #include "libpicofe/input.h"
@@ -118,9 +119,9 @@ static void print_fps(int h, int border)
                pl_rearmed_cbs.vsps_cur);
 }
 
-static void print_cpu_usage(int w, int h, int border)
+static void print_cpu_usage(int x, int h)
 {
-       hud_printf(pl_vout_buf, pl_vout_w, pl_vout_w - border - 28,
+       hud_printf(pl_vout_buf, pl_vout_w, x - 28,
                h - HUD_HEIGHT, "%3d", pl_rearmed_cbs.cpu_usage);
 }
 
@@ -154,13 +155,11 @@ static __attribute__((noinline)) void draw_active_chans(int vout_w, int vout_h)
        }
 }
 
-static void print_hud(int w, int h, int xborder)
+static void print_hud(int x, int w, int h)
 {
-       if (h < 16)
+       if (h < 192)
                return;
 
-       if (w < pl_vout_w)
-               xborder += (pl_vout_w - w) / 2;
        if (h > pl_vout_h)
                h = pl_vout_h;
 
@@ -168,12 +167,12 @@ static void print_hud(int w, int h, int xborder)
                draw_active_chans(w, h);
 
        if (hud_msg[0] != 0)
-               print_msg(h, xborder);
+               print_msg(h, x);
        else if (g_opts & OPT_SHOWFPS)
-               print_fps(h, xborder);
+               print_fps(h, x);
 
        if (g_opts & OPT_SHOWCPU)
-               print_cpu_usage(w, h, xborder);
+               print_cpu_usage(x + w, h);
 }
 
 /* update scaler target size according to user settings */
@@ -262,11 +261,7 @@ static void pl_vout_set_mode(int w, int h, int raw_w, int raw_h, int bpp)
        if (pl_rearmed_cbs.only_16bpp)
                vout_bpp = 16;
 
-       // don't use very low heights
-       if (vout_h < 192) {
-               buf_yoffset = (192 - vout_h) / 2;
-               vout_h = 192;
-       }
+       assert(vout_h >= 192);
 
        pl_vout_scale_w = pl_vout_scale_h = 1;
 #ifdef __ARM_NEON__
@@ -307,14 +302,15 @@ static void pl_vout_set_mode(int w, int h, int raw_w, int raw_h, int 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 void pl_vout_flip(const void *vram, int stride, int bgr24,
+       int x, int y, int w, int h, int dims_changed)
 {
-       static int doffs_old, clear_counter;
+       static int clear_counter;
        unsigned char *dest = pl_vout_buf;
        const unsigned short *src = vram;
        int dstride = pl_vout_w, h1 = h;
        int h_full = pl_vout_h - pl_vout_yoffset;
-       int doffs;
+       int xoffs = 0, doffs;
 
        pcnt_start(PCNT_BLIT);
 
@@ -328,12 +324,15 @@ static void pl_vout_flip(const void *vram, int stride, int bgr24, int w, int h)
                goto out_hud;
        }
 
-       // borders
-       doffs = (dstride - w * pl_vout_scale_w) / 2 & ~1;
+       assert(x + w <= pl_vout_w);
+       assert(y + h <= pl_vout_h);
+
+       // offset
+       xoffs = x * pl_vout_scale_w;
+       doffs = xoffs + y * dstride;
 
-       if (doffs > doffs_old)
+       if (dims_changed)
                clear_counter = 2;
-       doffs_old = doffs;
 
        if (clear_counter > 0) {
                if (pl_plat_clear)
@@ -409,7 +408,7 @@ static void pl_vout_flip(const void *vram, int stride, int bgr24, int w, int h)
        }
 
 out_hud:
-       print_hud(w * pl_vout_scale_w, h * pl_vout_scale_h, 0);
+       print_hud(xoffs, w * pl_vout_scale_w, (y + h) * pl_vout_scale_h);
 
 out:
        pcnt_end(PCNT_BLIT);
index 3f8b5c4..4984d30 100644 (file)
@@ -54,7 +54,7 @@ struct rearmed_cbs {
        int   (*pl_vout_open)(void);
        void  (*pl_vout_set_mode)(int w, int h, int raw_w, int raw_h, int bpp);
        void  (*pl_vout_flip)(const void *vram, int stride, int bgr24,
-                             int w, int h);
+                             int x, int y, int w, int h, int dims_changed);
        void  (*pl_vout_close)(void);
        void *(*mmap)(unsigned int size);
        void  (*munmap)(void *ptr, unsigned int size);
@@ -107,6 +107,9 @@ struct rearmed_cbs {
        } gpu_peopsgl;
        // misc
        int gpu_caps;
+       int screen_centering_type; // 0 - auto, 1 - game conrolled, 2 - manual
+       int screen_centering_x;
+       int screen_centering_y;
 };
 
 extern struct rearmed_cbs pl_rearmed_cbs;
index 353b603..30faee2 100644 (file)
@@ -52,7 +52,7 @@ int do_cmd_list(uint32_t *list, int count, int *last_cmd)
 
 #define ENHANCEMENT_BUF_SIZE (1024 * 1024 * 2 * 4 + 4096 * 2)
 
-static uint16_t *get_enhancement_bufer(int *x, int *y, int *w, int *h,
+static void *get_enhancement_bufer(int *x, int *y, int *w, int *h,
  int *vram_h)
 {
   uint16_t *ret = select_enhancement_buf_ptr(&egpu, *x);
index e9714e4..931583f 100644 (file)
@@ -9,6 +9,7 @@
  */
 
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include "gpu.h"
 
@@ -61,28 +62,90 @@ static noinline void do_reset(void)
   gpu.regs[3] = 1;
   gpu.screen.hres = gpu.screen.w = 256;
   gpu.screen.vres = gpu.screen.h = 240;
+  gpu.screen.x = gpu.screen.y = 0;
 }
 
 static noinline void update_width(void)
 {
+  static const short hres_all[8] = { 256, 368, 320, 368, 512, 368, 640, 368 };
+  static const uint8_t hdivs[8] = { 10, 7, 8, 7, 5, 7, 4, 7 };
+  uint8_t hdiv = hdivs[(gpu.status >> 16) & 7];
+  int hres = hres_all[(gpu.status >> 16) & 7];
+  int pal = gpu.status & PSX_GPU_STATUS_PAL;
   int sw = gpu.screen.x2 - gpu.screen.x1;
-  if (sw <= 0 || sw >= 2560)
-    // full width
-    gpu.screen.w = gpu.screen.hres;
-  else
-    gpu.screen.w = sw * gpu.screen.hres / 2560;
+  int x = 0, x_auto;
+  if (sw <= 0)
+    /* nothing displayed? */;
+  else {
+    int s = pal ? 656 : 608; // or 600? pal is just a guess
+    x = (gpu.screen.x1 - s) / hdiv;
+    x = (x + 1) & ~1;   // blitter limitation
+    sw /= hdiv;
+    sw = (sw + 2) & ~3; // according to nocash
+    switch (gpu.state.screen_centering_type) {
+    case 1:
+      break;
+    case 2:
+      x = gpu.state.screen_centering_x;
+      break;
+    default:
+      // correct if slightly miscentered
+      x_auto = (hres - sw) / 2 & ~3;
+      if ((uint32_t)x_auto <= 8u && abs(x) < 24)
+        x = x_auto;
+    }
+    if (x + sw > hres)
+      sw = hres - x;
+    // .x range check is done in vout_update()
+  }
+  // reduce the unpleasant right border that a few games have
+  if (gpu.state.screen_centering_type == 0
+      && x <= 4 && hres - (x + sw) >= 4)
+    hres -= 4;
+  gpu.screen.x = x;
+  gpu.screen.w = sw;
+  gpu.screen.hres = hres;
+  gpu.state.dims_changed = 1;
+  //printf("xx %d %d -> %2d, %d / %d\n",
+  //  gpu.screen.x1, gpu.screen.x2, x, sw, hres);
 }
 
 static noinline void update_height(void)
 {
-  // TODO: emulate this properly..
+  int pal = gpu.status & PSX_GPU_STATUS_PAL;
+  int dheight = gpu.status & PSX_GPU_STATUS_DHEIGHT;
+  int y = gpu.screen.y1 - (pal ? 39 : 16); // 39 for spyro
   int sh = gpu.screen.y2 - gpu.screen.y1;
-  if (gpu.status & PSX_GPU_STATUS_DHEIGHT)
-    sh *= 2;
-  if (sh <= 0 || sh > gpu.screen.vres)
-    sh = gpu.screen.vres;
-
+  int center_tol = 16;
+  int vres = 240;
+
+  if (pal && (sh > 240 || gpu.screen.vres == 256))
+    vres = 256;
+  if (dheight)
+    y *= 2, sh *= 2, vres *= 2, center_tol *= 2;
+  if (sh <= 0)
+    /* nothing displayed? */;
+  else {
+    switch (gpu.state.screen_centering_type) {
+    case 1:
+      break;
+    case 2:
+      y = gpu.state.screen_centering_y;
+      break;
+    default:
+      // correct if slightly miscentered
+      if ((uint32_t)(vres - sh) <= 1 && abs(y) <= center_tol)
+        y = 0;
+    }
+    if (y + sh > vres)
+      sh = vres - y;
+  }
+  gpu.screen.y = y;
   gpu.screen.h = sh;
+  gpu.screen.vres = vres;
+  gpu.state.dims_changed = 1;
+  //printf("yy %d %d -> %d, %d / %d\n",
+  //  gpu.screen.y1, gpu.screen.y2, y, sh, vres);
 }
 
 static noinline void decide_frameskip(void)
@@ -115,8 +178,8 @@ static noinline int decide_frameskip_allow(uint32_t cmd_e3)
   uint32_t x = cmd_e3 & 0x3ff;
   uint32_t y = (cmd_e3 >> 10) & 0x3ff;
   gpu.frameskip.allow = (gpu.status & PSX_GPU_STATUS_INTERLACE) ||
-    (uint32_t)(x - gpu.screen.x) >= (uint32_t)gpu.screen.w ||
-    (uint32_t)(y - gpu.screen.y) >= (uint32_t)gpu.screen.h;
+    (uint32_t)(x - gpu.screen.src_x) >= (uint32_t)gpu.screen.w ||
+    (uint32_t)(y - gpu.screen.src_y) >= (uint32_t)gpu.screen.h;
   return gpu.frameskip.allow;
 }
 
@@ -192,8 +255,6 @@ long GPUshutdown(void)
 
 void GPUwriteStatus(uint32_t data)
 {
-  static const short hres[8] = { 256, 368, 320, 384, 512, 512, 640, 640 };
-  static const short vres[4] = { 240, 480, 256, 480 };
   uint32_t cmd = data >> 24;
 
   if (cmd < ARRAY_SIZE(gpu.regs)) {
@@ -212,8 +273,10 @@ void GPUwriteStatus(uint32_t data)
       do_cmd_reset();
       break;
     case 0x03:
-      if (data & 1)
+      if (data & 1) {
         gpu.status |= PSX_GPU_STATUS_BLANKING;
+        gpu.state.dims_changed = 1; // for hud clearing
+      }
       else
         gpu.status &= ~PSX_GPU_STATUS_BLANKING;
       break;
@@ -222,8 +285,8 @@ void GPUwriteStatus(uint32_t data)
       gpu.status |= PSX_GPU_STATUS_DMA(data & 3);
       break;
     case 0x05:
-      gpu.screen.x = data & 0x3ff;
-      gpu.screen.y = (data >> 10) & 0x1ff;
+      gpu.screen.src_x = data & 0x3ff;
+      gpu.screen.src_y = (data >> 10) & 0x1ff;
       if (gpu.frameskip.set) {
         decide_frameskip_allow(gpu.ex_regs[3]);
         if (gpu.frameskip.last_flip_frame != *gpu.state.frame_count) {
@@ -244,8 +307,6 @@ void GPUwriteStatus(uint32_t data)
       break;
     case 0x08:
       gpu.status = (gpu.status & ~0x7f0000) | ((data & 0x3F) << 17) | ((data & 0x40) << 10);
-      gpu.screen.hres = hres[(gpu.status >> 16) & 7];
-      gpu.screen.vres = vres[(gpu.status >> 19) & 3];
       update_width();
       update_height();
       renderer_notify_res_change();
@@ -752,6 +813,15 @@ void GPUrearmedCallbacks(const struct rearmed_cbs *cbs)
   gpu.state.frame_count = cbs->gpu_frame_count;
   gpu.state.allow_interlace = cbs->gpu_neon.allow_interlace;
   gpu.state.enhancement_enable = cbs->gpu_neon.enhancement_enable;
+  if (gpu.state.screen_centering_type != cbs->screen_centering_type
+      || gpu.state.screen_centering_x != cbs->screen_centering_x
+      || gpu.state.screen_centering_y != cbs->screen_centering_y) {
+    gpu.state.screen_centering_type = cbs->screen_centering_type;
+    gpu.state.screen_centering_x = cbs->screen_centering_x;
+    gpu.state.screen_centering_y = cbs->screen_centering_y;
+    update_width();
+    update_height();
+  }
 
   gpu.mmap = cbs->mmap;
   gpu.munmap = cbs->munmap;
index 4637a71..446a023 100644 (file)
@@ -34,6 +34,7 @@ extern "C" {
 #define BIT(x) (1 << (x))
 
 #define PSX_GPU_STATUS_DHEIGHT         BIT(19)
+#define PSX_GPU_STATUS_PAL             BIT(20)
 #define PSX_GPU_STATUS_RGB24           BIT(21)
 #define PSX_GPU_STATUS_INTERLACE       BIT(22)
 #define PSX_GPU_STATUS_BLANKING                BIT(23)
@@ -53,6 +54,7 @@ struct psx_gpu {
     int x, y, w, h;
     int x1, x2;
     int y1, y2;
+    int src_x, src_y;
   } screen;
   struct {
     int x, y, w, h;
@@ -67,6 +69,7 @@ struct psx_gpu {
     uint32_t blanked:1;
     uint32_t enhancement_enable:1;
     uint32_t enhancement_active:1;
+    uint32_t dims_changed:1;
     uint32_t *frame_count;
     uint32_t *hcnt; /* hsync count */
     struct {
@@ -77,6 +80,9 @@ struct psx_gpu {
     } last_list;
     uint32_t last_vram_read_frame;
     uint32_t w_out_old, h_out_old, status_vo_old;
+    int screen_centering_type; // 0 - auto, 1 - game conrolled, 2 - manual
+    int screen_centering_x;
+    int screen_centering_y;
   } state;
   struct {
     int32_t set:3; /* -1 auto, 0 off, 1-3 fixed */
@@ -88,7 +94,7 @@ struct psx_gpu {
     uint32_t last_flip_frame;
     uint32_t pending_fill[3];
   } frameskip;
-  uint16_t *(*get_enhancement_bufer)
+  void *(*get_enhancement_bufer)
     (int *x, int *y, int *w, int *h, int *vram_h);
   void *(*mmap)(unsigned int size);
   void  (*munmap)(void *ptr, unsigned int size);
index a6a3f63..26827d0 100644 (file)
@@ -28,7 +28,7 @@ int vout_finish(void)
 static void check_mode_change(int force)
 {
   int w = gpu.screen.hres;
-  int h = gpu.screen.h;
+  int h = gpu.screen.vres;
   int w_out = w;
   int h_out = h;
 
@@ -56,47 +56,59 @@ static void check_mode_change(int force)
 
 void vout_update(void)
 {
+  int bpp = (gpu.status & PSX_GPU_STATUS_RGB24) ? 24 : 16;
+  uint8_t *vram = (uint8_t *)gpu.vram;
+  int src_x = gpu.screen.src_x;
+  int src_y = gpu.screen.src_y;
   int x = gpu.screen.x;
   int y = gpu.screen.y;
   int w = gpu.screen.w;
   int h = gpu.screen.h;
-  uint16_t *vram = gpu.vram;
   int vram_h = 512;
+  int src_x2 = 0;
+
+  if (x < 0) { w += x; src_x2 = -x; x = 0; }
+  if (y < 0) { h += y; src_y -=  y; y = 0; }
 
-  if (w == 0 || h == 0)
+  if (w <= 0 || h <= 0)
     return;
 
   check_mode_change(0);
-  if (gpu.state.enhancement_active)
-    vram = gpu.get_enhancement_bufer(&x, &y, &w, &h, &vram_h);
+  if (gpu.state.enhancement_active) {
+    vram = gpu.get_enhancement_bufer(&src_x, &src_y, &w, &h, &vram_h);
+    x *= 2; y *= 2;
+  }
 
-  if (y + h > vram_h) {
-    if (y + h - vram_h > h / 2) {
+  if (src_y + h > vram_h) {
+    if (src_y + h - vram_h > h / 2) {
       // wrap
-      h -= vram_h - y;
-      y = 0;
+      h -= vram_h - src_y;
+      src_y = 0;
     }
     else
       // clip
-      h = vram_h - y;
+      h = vram_h - src_y;
   }
 
-  vram += y * 1024 + x;
+  vram += (src_y * 1024 + src_x) * 2;
+  vram += src_x2 * bpp / 8;
 
-  cbs->pl_vout_flip(vram, 1024, !!(gpu.status & PSX_GPU_STATUS_RGB24), w, h);
+  cbs->pl_vout_flip(vram, 1024, !!(gpu.status & PSX_GPU_STATUS_RGB24),
+      x, y, w, h, gpu.state.dims_changed);
+  gpu.state.dims_changed = 0;
 }
 
 void vout_blank(void)
 {
   int w = gpu.screen.hres;
-  int h = gpu.screen.h;
+  int h = gpu.screen.vres;
 
   check_mode_change(0);
   if (gpu.state.enhancement_active) {
     w *= 2;
     h *= 2;
   }
-  cbs->pl_vout_flip(NULL, 1024, !!(gpu.status & PSX_GPU_STATUS_RGB24), w, h);
+  cbs->pl_vout_flip(NULL, 1024, !!(gpu.status & PSX_GPU_STATUS_RGB24), 0, 0, w, h, 0);
 }
 
 long GPUopen(void **unused)