mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
fix(ps3): overlap core rendering with RSX flips while preserving VSync (#20003)
* fix(ps3): overlap core rendering with RSX flips while preserving VSync Wait for the pending flip before frontend composition so the core can render while the flip is in progress. Keep flip completion synchronized when menu or overlay uploads are active. With VSync enabled, present the cached image for duplicate frames even when no UI is visible. This prevents emulation from running too fast when audio sync is disabled. Cover flip timing and software and hardware frame duplicates with and without visible UI in the RSX regression tests. * rsx: keep the core behind the flip with two swapchain images Max Swapchain Images now chooses the RSX queue depth: two keep the CPU behind each vsync flip for the lowest input latency, three let the core run into the pending flip. The setting is shown for RSX. A vsync dupe redraws the last frame that loaded; a failed upload no longer becomes the cached frame, and with nothing cached the dupe leaves the screen as it is instead of presenting a cleared buffer. rsx_alloc_test models the GCM flip status, so a wait on a flip that was never reset or queued shows up, and gains flip_latency and dupe_keeps_image lanes. * PS3: default to two swapchain images RSX keeps the core behind each vsync flip with two images, the lowest input latency; three, letting the core run into the pending flip, stays available as a setting. --------- Co-authored-by: LibretroAdmin <[email protected]>
This commit is contained in:
1 parent
01a14c020d
commit
eb699ab842
3 files changed
+403
-29
No files matched your search
@@ -418,7 +418,12 @@
|
|||||||
#define MAXIMUM_SCANLINE_SYNC_OFFSET 3000
|
#define MAXIMUM_SCANLINE_SYNC_OFFSET 3000
|
||||||
|
|
||||||
/* Vulkan specific */
|
/* Vulkan specific */
|
||||||
|
#if defined(__PSL1GHT__) || defined(__PS3__)
|
||||||
|
/* RSX keeps the core behind each flip with two: lowest input latency */
|
||||||
|
#define DEFAULT_MAX_SWAPCHAIN_IMAGES 2
|
||||||
|
#else
|
||||||
#define DEFAULT_MAX_SWAPCHAIN_IMAGES 3
|
#define DEFAULT_MAX_SWAPCHAIN_IMAGES 3
|
||||||
|
#endif
|
||||||
#define MINIMUM_MAX_SWAPCHAIN_IMAGES 2
|
#define MINIMUM_MAX_SWAPCHAIN_IMAGES 2
|
||||||
#define MAXIMUM_MAX_SWAPCHAIN_IMAGES 4
|
#define MAXIMUM_MAX_SWAPCHAIN_IMAGES 4
|
||||||
|
|
||||||
|
|||||||
+53
-24
@@ -37,6 +37,7 @@
|
|||||||
#include "../../menu/menu_driver.h"
|
#include "../../menu/menu_driver.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "../gfx_display.h"
|
||||||
#ifdef HAVE_GFX_WIDGETS
|
#ifdef HAVE_GFX_WIDGETS
|
||||||
#include "../gfx_widgets.h"
|
#include "../gfx_widgets.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -52,8 +53,8 @@
|
|||||||
#include <libretro_rsx.h>
|
#include <libretro_rsx.h>
|
||||||
#ifdef HAVE_THREADS
|
#ifdef HAVE_THREADS
|
||||||
#include "../video_thread_wrapper.h"
|
#include "../video_thread_wrapper.h"
|
||||||
#include "../display_servers/dispserv_ps3.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
#include "../display_servers/dispserv_ps3.h"
|
||||||
|
|
||||||
#define RSX_MAX_BUFFERS 2
|
#define RSX_MAX_BUFFERS 2
|
||||||
#define RSX_MAX_MENU_BUFFERS 2
|
#define RSX_MAX_MENU_BUFFERS 2
|
||||||
@@ -1480,11 +1481,10 @@ error:
|
|||||||
|
|
||||||
static int rsx_flip(gcmContextData *context, s32 buffer)
|
static int rsx_flip(gcmContextData *context, s32 buffer)
|
||||||
{
|
{
|
||||||
|
gcmResetFlipStatus();
|
||||||
if (gcmSetFlip(context, buffer) == 0)
|
if (gcmSetFlip(context, buffer) == 0)
|
||||||
{
|
{
|
||||||
rsxFlushBuffer (context);
|
rsxFlushBuffer (context);
|
||||||
/* Prevent the RSX from continuing until the flip has finished. */
|
|
||||||
gcmSetWaitFlip (context);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1520,7 +1520,6 @@ static void rsx_wait_flip(void)
|
|||||||
{
|
{
|
||||||
while(gcmGetFlipStatus() != 0)
|
while(gcmGetFlipStatus() != 0)
|
||||||
usleep(200); /* Sleep, to not stress the cpu. */
|
usleep(200); /* Sleep, to not stress the cpu. */
|
||||||
gcmResetFlipStatus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gcmContextData *rsx_init_screen(rsx_t* gcm)
|
static gcmContextData *rsx_init_screen(rsx_t* gcm)
|
||||||
@@ -2651,8 +2650,6 @@ static void rsx_update_screen(rsx_t* gcm)
|
|||||||
}
|
}
|
||||||
|
|
||||||
rsx_flip(gcm->context, buffer->id);
|
rsx_flip(gcm->context, buffer->id);
|
||||||
if (gcm->vsync)
|
|
||||||
rsx_wait_flip();
|
|
||||||
rsxSetSurface(gcm->context, &gcm->surface[gcm->nextBuffer]);
|
rsxSetSurface(gcm->context, &gcm->surface[gcm->nextBuffer]);
|
||||||
#ifdef HAVE_SYSUTILS
|
#ifdef HAVE_SYSUTILS
|
||||||
cellSysutilCheckCallback();
|
cellSysutilCheckCallback();
|
||||||
@@ -2715,16 +2712,29 @@ static bool rsx_frame(void* data, const void* frame,
|
|||||||
|| gcm->overlay_enable
|
|| gcm->overlay_enable
|
||||||
#endif
|
#endif
|
||||||
|| video_info->widgets_visible;
|
|| video_info->widgets_visible;
|
||||||
bool draw_ui = ui_now || gcm->ui_presented;
|
bool draw_cached = ui_now || gcm->ui_presented || gcm->vsync;
|
||||||
/* The core shares the FIFO and leaves its own state behind.
|
bool sync_ui = false
|
||||||
* Restore the frontend's state before anything of ours is drawn:
|
#ifdef HAVE_MENU
|
||||||
* a core frame (hardware or CPU fallback), or a duped frame that
|
|| menu_is_alive || gcm->menu_frame_enable
|
||||||
* still gets the menu, overlay, OSD or widgets drawn on top. A
|
#endif
|
||||||
* bare dupe draws nothing and skips it. */
|
#ifdef HAVE_OVERLAY
|
||||||
bool restore = gcm->hw_render && (frame || draw_ui);
|
|| gcm->overlay_enable
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
/* Two images keep the core behind the flip: lowest input latency */
|
||||||
|
bool overlap = video_info->max_swapchain_images > 2;
|
||||||
|
/* The core shares the FIFO and leaves its own state behind. */
|
||||||
|
bool restore = gcm->hw_render && (frame || draw_cached);
|
||||||
|
|
||||||
rsx_collect_retired_memory();
|
rsx_collect_retired_memory();
|
||||||
rsx_fence_retired_memory(gcm->context);
|
rsx_fence_retired_memory(gcm->context);
|
||||||
|
/* Core work overlaps the previous flip; UI buffers cannot. */
|
||||||
|
if (frame || draw_cached)
|
||||||
|
{
|
||||||
|
if (gcm->vsync)
|
||||||
|
rsx_wait_flip();
|
||||||
|
gcmSetWaitFlip(gcm->context);
|
||||||
|
}
|
||||||
if (restore)
|
if (restore)
|
||||||
{
|
{
|
||||||
unsigned attrib;
|
unsigned attrib;
|
||||||
@@ -2753,6 +2763,9 @@ static bool rsx_frame(void* data, const void* frame,
|
|||||||
if (gcm->should_resize)
|
if (gcm->should_resize)
|
||||||
rsx_update_viewport(gcm);
|
rsx_update_viewport(gcm);
|
||||||
|
|
||||||
|
if (frame || draw_cached)
|
||||||
|
rsx_clear_surface(gcm);
|
||||||
|
|
||||||
vp.min = 0.0f;
|
vp.min = 0.0f;
|
||||||
vp.max = 1.0f;
|
vp.max = 1.0f;
|
||||||
vp.pos = VIDEO_POS_PACK(VIDEO_POS_X(gcm->vp.pos),
|
vp.pos = VIDEO_POS_PACK(VIDEO_POS_X(gcm->vp.pos),
|
||||||
@@ -2784,14 +2797,16 @@ static bool rsx_frame(void* data, const void* frame,
|
|||||||
}
|
}
|
||||||
else if (frame && width && height)
|
else if (frame && width && height)
|
||||||
{
|
{
|
||||||
gcm->tex_index = ((gcm->tex_index + 1) % RSX_MAX_TEXTURES);
|
/* A failed load leaves the last good frame the cached one */
|
||||||
|
unsigned next = (gcm->tex_index + 1) % RSX_MAX_TEXTURES;
|
||||||
if (rsx_load_texture_data(gcm,
|
if (rsx_load_texture_data(gcm,
|
||||||
&gcm->texture[gcm->tex_index],
|
&gcm->texture[next],
|
||||||
frame, width, height, pitch, gcm->rgb32, false,
|
frame, width, height, pitch, gcm->rgb32, false,
|
||||||
gcm->smooth
|
gcm->smooth
|
||||||
? TEXTURE_FILTER_LINEAR
|
? TEXTURE_FILTER_LINEAR
|
||||||
: TEXTURE_FILTER_NEAREST))
|
: TEXTURE_FILTER_NEAREST))
|
||||||
{
|
{
|
||||||
|
gcm->tex_index = next;
|
||||||
/* TODO/FIXME - pipeline ID being used here is RSX_SHADER_MENU,
|
/* TODO/FIXME - pipeline ID being used here is RSX_SHADER_MENU,
|
||||||
* shouldn't this be RSX_SHADER_STOCK_BLEND instead? */
|
* shouldn't this be RSX_SHADER_STOCK_BLEND instead? */
|
||||||
rsx_set_texture(gcm, &gcm->texture[gcm->tex_index]);
|
rsx_set_texture(gcm, &gcm->texture[gcm->tex_index]);
|
||||||
@@ -2800,17 +2815,24 @@ static bool rsx_frame(void* data, const void* frame,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Rebuild the background before any frontend layers. */
|
/* Core duplicates still need a synchronized present. */
|
||||||
if (!draw && draw_ui)
|
if (!draw && draw_cached)
|
||||||
{
|
{
|
||||||
if (gcm->hw_render && gcm->hw_image_valid)
|
if (gcm->hw_render && gcm->hw_image_valid)
|
||||||
|
{
|
||||||
rsx_draw_hw_image(gcm);
|
rsx_draw_hw_image(gcm);
|
||||||
|
draw = true;
|
||||||
|
}
|
||||||
else if (gcm->texture[gcm->tex_index].data)
|
else if (gcm->texture[gcm->tex_index].data)
|
||||||
{
|
{
|
||||||
rsx_set_texture(gcm, &gcm->texture[gcm->tex_index]);
|
rsx_set_texture(gcm, &gcm->texture[gcm->tex_index]);
|
||||||
rsx_draw_vertices(gcm);
|
rsx_draw_vertices(gcm);
|
||||||
|
draw = true;
|
||||||
}
|
}
|
||||||
draw = true;
|
/* Nothing cached: a cleared buffer goes out only to put up or
|
||||||
|
* take down UI, never in place of the image on screen */
|
||||||
|
else if (ui_now || gcm->ui_presented)
|
||||||
|
draw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_MENU
|
#ifdef HAVE_MENU
|
||||||
@@ -2842,9 +2864,9 @@ static bool rsx_frame(void* data, const void* frame,
|
|||||||
|
|
||||||
#ifdef HAVE_GFX_WIDGETS
|
#ifdef HAVE_GFX_WIDGETS
|
||||||
/* Called on bare dupes too, for the widgets' own per-frame work.
|
/* Called on bare dupes too, for the widgets' own per-frame work.
|
||||||
* Those go out without the state restore and are not presented,
|
* Without vsync those go out without the state restore and are not
|
||||||
* so this relies on gfx_widgets_frame drawing nothing whenever
|
* presented, so this relies on gfx_widgets_frame drawing nothing
|
||||||
* the frame's widgets_visible is false. */
|
* whenever the frame's widgets_visible is false. */
|
||||||
if (widgets_active)
|
if (widgets_active)
|
||||||
gfx_widgets_frame(video_info);
|
gfx_widgets_frame(video_info);
|
||||||
#endif
|
#endif
|
||||||
@@ -2857,9 +2879,11 @@ static bool rsx_frame(void* data, const void* frame,
|
|||||||
#endif
|
#endif
|
||||||
if (draw)
|
if (draw)
|
||||||
{
|
{
|
||||||
/* Only update when we draw to prevent flickering when core frame duping is enabled */
|
/* Undrawn frames are not presented: the last image stays up */
|
||||||
rsx_update_screen(gcm);
|
rsx_update_screen(gcm);
|
||||||
rsx_clear_surface(gcm);
|
/* Menu and overlay uploads happen between frame calls. */
|
||||||
|
if (gcm->vsync && (sync_ui || !overlap))
|
||||||
|
rsx_wait_flip();
|
||||||
gcm->ui_presented = ui_now;
|
gcm->ui_presented = ui_now;
|
||||||
}
|
}
|
||||||
gcm->vert_idx = 0;
|
gcm->vert_idx = 0;
|
||||||
@@ -2998,7 +3022,12 @@ static void rsx_set_osd_msg(void *data,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static uint32_t rsx_get_flags(void *data) { return 0; }
|
static uint32_t rsx_get_flags(void *data)
|
||||||
|
{
|
||||||
|
uint32_t flags = 0;
|
||||||
|
BIT32_SET(flags, GFX_CTX_FLAGS_CUSTOMIZABLE_SWAPCHAIN_IMAGES);
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- GPU-native BCn compressed-texture upload (PoC) --- */
|
/* --- GPU-native BCn compressed-texture upload (PoC) --- */
|
||||||
/* RSX (NV47/G70) natively samples DXT1/DXT23/DXT45 == BC1/BC2/BC3.
|
/* RSX (NV47/G70) natively samples DXT1/DXT23/DXT45 == BC1/BC2/BC3.
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
* cursors and GPU fences.
|
* cursors and GPU fences.
|
||||||
* - native_menu_frame, menu_dupe_frame: a menu with no new core
|
* - native_menu_frame, menu_dupe_frame: a menu with no new core
|
||||||
* frame is presented, over the last core frame if there is one.
|
* frame is presented, over the last core frame if there is one.
|
||||||
|
* - flip_latency: two swapchain images keep the core behind the
|
||||||
|
* flip, three let it run into the pending one.
|
||||||
|
* - dupe_keeps_image: a dupe redraws the last frame that loaded,
|
||||||
|
* and with none presents nothing.
|
||||||
*
|
*
|
||||||
* Plain C and the C library only, so it builds and runs the same with
|
* Plain C and the C library only, so it builds and runs the same with
|
||||||
* mingw on Windows. */
|
* mingw on Windows. */
|
||||||
@@ -156,15 +160,83 @@ void rsxSetWaitForIdle(gcmContextData *context) { }
|
|||||||
void rsxSetWriteBackendLabel(gcmContextData *context, u8 index, u32 value)
|
void rsxSetWriteBackendLabel(gcmContextData *context, u8 index, u32 value)
|
||||||
{ if (!fake_defer_backend) fake_labels[index] = value; }
|
{ if (!fake_defer_backend) fake_labels[index] = value; }
|
||||||
u32 *gcmGetLabelAddress(const u8 index) { return &fake_labels[index]; }
|
u32 *gcmGetLabelAddress(const u8 index) { return &fake_labels[index]; }
|
||||||
u32 gcmGetFlipStatus(void) { return 0; }
|
static unsigned rsx_flip_waits;
|
||||||
void gcmResetFlipStatus(void) { }
|
static retro_time_t fake_time;
|
||||||
|
static retro_time_t fake_flip_period;
|
||||||
|
static retro_time_t fake_flip_due;
|
||||||
|
static retro_time_t fake_gpu_time;
|
||||||
|
static unsigned fake_flip_overlap;
|
||||||
|
static unsigned fake_clear_hazards;
|
||||||
|
static unsigned fake_time_reads;
|
||||||
|
static retro_time_t fake_last_flip_due;
|
||||||
|
/* The flip status as GCM keeps it: 1 from a reset until the flip
|
||||||
|
* queued after it has been shown, 0 from then on */
|
||||||
|
static u32 fake_flip_status;
|
||||||
|
static bool fake_flip_queued;
|
||||||
|
static unsigned fake_flip_hangs;
|
||||||
|
/* rsx_wait_flip spins on this; a pending flip is shown at its due
|
||||||
|
* time, a reset with no flip behind it would spin forever */
|
||||||
|
u32 gcmGetFlipStatus(void)
|
||||||
|
{
|
||||||
|
rsx_flip_waits++;
|
||||||
|
if (fake_flip_status)
|
||||||
|
{
|
||||||
|
if (!fake_flip_queued)
|
||||||
|
fake_flip_hangs++;
|
||||||
|
else if (fake_flip_period && fake_time < fake_flip_due)
|
||||||
|
fake_time = fake_flip_due;
|
||||||
|
fake_flip_status = 0;
|
||||||
|
fake_flip_due = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void gcmResetFlipStatus(void)
|
||||||
|
{
|
||||||
|
fake_flip_status = 1;
|
||||||
|
fake_flip_queued = false;
|
||||||
|
}
|
||||||
s32 gcmSetDisplayBuffer(const u8 bufferId, const u32 offset,
|
s32 gcmSetDisplayBuffer(const u8 bufferId, const u32 offset,
|
||||||
const u32 pitch, const u32 width, const u32 height) { return 0; }
|
const u32 pitch, const u32 width, const u32 height) { return 0; }
|
||||||
static unsigned rsx_flip_calls;
|
static unsigned rsx_flip_calls;
|
||||||
|
retro_time_t cpu_features_get_time_usec(void)
|
||||||
|
{
|
||||||
|
fake_time_reads++;
|
||||||
|
return fake_time;
|
||||||
|
}
|
||||||
s32 gcmSetFlip(gcmContextData *context, const u8 bufferId)
|
s32 gcmSetFlip(gcmContextData *context, const u8 bufferId)
|
||||||
{ rsx_flip_calls++; return 0; }
|
{
|
||||||
|
rsx_flip_calls++;
|
||||||
|
fake_flip_queued = true;
|
||||||
|
if (fake_flip_period)
|
||||||
|
{
|
||||||
|
if (fake_flip_due > fake_time)
|
||||||
|
fake_flip_overlap++;
|
||||||
|
if (fake_gpu_time < fake_time)
|
||||||
|
fake_gpu_time = fake_time;
|
||||||
|
fake_flip_due = (fake_gpu_time / fake_flip_period + 1) * fake_flip_period;
|
||||||
|
fake_last_flip_due = fake_flip_due;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A flip queued one period out, as a test's first frame finds it */
|
||||||
|
static void fake_flip_pending(void)
|
||||||
|
{
|
||||||
|
fake_flip_status = 1;
|
||||||
|
fake_flip_queued = true;
|
||||||
|
fake_flip_due = fake_time + fake_flip_period;
|
||||||
|
}
|
||||||
void gcmSetFlipMode(const u32 mode) { }
|
void gcmSetFlipMode(const u32 mode) { }
|
||||||
void gcmSetWaitFlip(gcmContextData *context) { }
|
void gcmSetWaitFlip(gcmContextData *context)
|
||||||
|
{
|
||||||
|
if (fake_flip_period)
|
||||||
|
{
|
||||||
|
if (fake_gpu_time < fake_time)
|
||||||
|
fake_gpu_time = fake_time;
|
||||||
|
if (fake_gpu_time < fake_flip_due)
|
||||||
|
fake_gpu_time = fake_flip_due;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void rsxVertexProgramGetUCode(const rsxVertexProgram *vp, void **ucode,
|
void rsxVertexProgramGetUCode(const rsxVertexProgram *vp, void **ucode,
|
||||||
u32 *size) { *ucode = fake_ucode; *size = sizeof(fake_ucode); }
|
u32 *size) { *ucode = fake_ucode; *size = sizeof(fake_ucode); }
|
||||||
@@ -181,7 +253,11 @@ rsxProgramAttrib *rsxFragmentProgramGetAttrib(const rsxFragmentProgram *fp,
|
|||||||
|
|
||||||
void rsxBindVertexArrayAttrib(gcmContextData *c, u8 attr, u16 frequency,
|
void rsxBindVertexArrayAttrib(gcmContextData *c, u8 attr, u16 frequency,
|
||||||
u32 offset, u8 stride, u8 elems, u8 dtype, u8 location) { }
|
u32 offset, u8 stride, u8 elems, u8 dtype, u8 location) { }
|
||||||
void rsxClearSurface(gcmContextData *c, u32 clear_mask) { }
|
void rsxClearSurface(gcmContextData *c, u32 clear_mask)
|
||||||
|
{
|
||||||
|
if (fake_flip_period && fake_gpu_time < fake_flip_due)
|
||||||
|
fake_clear_hazards++;
|
||||||
|
}
|
||||||
static unsigned rsx_draw_calls;
|
static unsigned rsx_draw_calls;
|
||||||
#define FAKE_DRAW_OSD UINT32_MAX
|
#define FAKE_DRAW_OSD UINT32_MAX
|
||||||
#define FAKE_DRAW_WIDGET (UINT32_MAX - 1)
|
#define FAKE_DRAW_WIDGET (UINT32_MAX - 1)
|
||||||
@@ -298,6 +374,7 @@ void input_driver_video_window(enum input_window_kind window,
|
|||||||
void menu_driver_frame(bool menu_is_alive, video_frame_info_t *video_info) { }
|
void menu_driver_frame(bool menu_is_alive, video_frame_info_t *video_info) { }
|
||||||
static bool fake_widgets_visible = true;
|
static bool fake_widgets_visible = true;
|
||||||
static unsigned fake_widget_calls;
|
static unsigned fake_widget_calls;
|
||||||
|
static bool fake_widget_restored;
|
||||||
bool gfx_widgets_visible(void *data)
|
bool gfx_widgets_visible(void *data)
|
||||||
{
|
{
|
||||||
video_frame_info_t *info = (video_frame_info_t*)data;
|
video_frame_info_t *info = (video_frame_info_t*)data;
|
||||||
@@ -308,7 +385,10 @@ void gfx_widgets_frame(void *data)
|
|||||||
{
|
{
|
||||||
fake_widget_calls++;
|
fake_widget_calls++;
|
||||||
if (gfx_widgets_visible(data))
|
if (gfx_widgets_visible(data))
|
||||||
|
{
|
||||||
|
fake_widget_restored = rsx_depth_test_enable == GCM_FALSE;
|
||||||
fake_record_draw(FAKE_DRAW_WIDGET);
|
fake_record_draw(FAKE_DRAW_WIDGET);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int32_t sysUtilCheckCallback(void) { return 0; }
|
int32_t sysUtilCheckCallback(void) { return 0; }
|
||||||
uintptr_t video_thread_texture_handle(void *data,
|
uintptr_t video_thread_texture_handle(void *data,
|
||||||
@@ -1214,6 +1294,262 @@ static void test_widget_visibility(void)
|
|||||||
printf("widget_visibility: ok\n");
|
printf("widget_visibility: ok\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_ui_dupe_pacing(void)
|
||||||
|
{
|
||||||
|
static uint16_t pixels[4 * 4];
|
||||||
|
video_frame_info_t info;
|
||||||
|
rsx_t *rsx;
|
||||||
|
unsigned mode, i, flips, waits, calls, reads, overlaps, hazards;
|
||||||
|
retro_time_t start;
|
||||||
|
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
fake_hwr.context_type = RETRO_HW_CONTEXT_RSX;
|
||||||
|
rsx = driver_up();
|
||||||
|
info.refresh_rate = 60.0f;
|
||||||
|
info.max_swapchain_images = 3;
|
||||||
|
info.widgets_active = true;
|
||||||
|
rsx_set_nonblock_state(rsx, false, false, 1);
|
||||||
|
fake_flip_period = 16667;
|
||||||
|
for (mode = 0; mode < 5; mode++)
|
||||||
|
{
|
||||||
|
flips = rsx_flip_calls;
|
||||||
|
waits = rsx_flip_waits;
|
||||||
|
calls = fake_widget_calls;
|
||||||
|
reads = fake_time_reads;
|
||||||
|
overlaps = fake_flip_overlap;
|
||||||
|
hazards = fake_clear_hazards;
|
||||||
|
fake_time = start = 10 * fake_flip_period;
|
||||||
|
fake_gpu_time = fake_time;
|
||||||
|
fake_flip_pending();
|
||||||
|
for (i = 0; i < 120; i++)
|
||||||
|
{
|
||||||
|
const void *frame = mode == 0 || mode >= 3 ? (i & 1 ? NULL : pixels)
|
||||||
|
: mode == 1 ? (i & 1 ? NULL : RETRO_HW_FRAME_BUFFER_VALID)
|
||||||
|
: NULL;
|
||||||
|
fake_time += mode == 3 && !(i & 1) ? 20000 : 1000;
|
||||||
|
if (mode == 4)
|
||||||
|
{
|
||||||
|
if (fake_gpu_time < fake_time)
|
||||||
|
fake_gpu_time = fake_time;
|
||||||
|
fake_gpu_time += (i & 1) ? 1000 : 20000;
|
||||||
|
}
|
||||||
|
rsx_depth_test_enable = GCM_TRUE;
|
||||||
|
fake_widget_restored = false;
|
||||||
|
frame_out(rsx, frame, VIDEO_SCALE_PACK(4, 4), i, 8, NULL, &info);
|
||||||
|
CHECK(fake_widget_restored,
|
||||||
|
"ui_dupe_pacing: visible widgets missing or drawn without state restore");
|
||||||
|
}
|
||||||
|
CHECK(rsx_flip_calls == flips + 120 && rsx_flip_waits == waits + 120,
|
||||||
|
"ui_dupe_pacing: core duplicates lost synchronized presents");
|
||||||
|
CHECK(fake_time - start == 120 * fake_flip_period,
|
||||||
|
"ui_dupe_pacing: lost display cadence (%ld us, expected %ld us)",
|
||||||
|
(long)(fake_time - start), (long)(120 * fake_flip_period));
|
||||||
|
CHECK(fake_widget_calls == calls + 120 && !info.notifications_hidden,
|
||||||
|
"ui_dupe_pacing: widget frames suppressed");
|
||||||
|
CHECK(fake_time_reads == reads,
|
||||||
|
"ui_dupe_pacing: frame path reads wall clock");
|
||||||
|
CHECK(fake_flip_overlap == overlaps,
|
||||||
|
"ui_dupe_pacing: new flip submitted before previous one completes");
|
||||||
|
CHECK(fake_clear_hazards == hazards,
|
||||||
|
"ui_dupe_pacing: framebuffer cleared before previous flip completes");
|
||||||
|
}
|
||||||
|
|
||||||
|
flips = rsx_flip_calls;
|
||||||
|
waits = rsx_flip_waits;
|
||||||
|
rsx->menu_frame_enable = true;
|
||||||
|
info.menu_st_flags = MENU_ST_FLAG_ALIVE;
|
||||||
|
frame_out(rsx, NULL, 0, 120, 0, NULL, &info);
|
||||||
|
frame_out(rsx, NULL, 0, 121, 0, NULL, &info);
|
||||||
|
CHECK(rsx_flip_calls == flips + 2 && !fake_flip_due,
|
||||||
|
"ui_dupe_pacing: menu returned with uploads still in use");
|
||||||
|
rsx->menu_frame_enable = false;
|
||||||
|
info.menu_st_flags = 0;
|
||||||
|
rsx->should_resize = true;
|
||||||
|
frame_out(rsx, NULL, 0, 122, 0, NULL, &info);
|
||||||
|
CHECK(rsx_flip_calls == flips + 3 && !rsx->should_resize,
|
||||||
|
"ui_dupe_pacing: viewport resize deferred");
|
||||||
|
|
||||||
|
info.widgets_active = false;
|
||||||
|
frame_out(rsx, NULL, 0, 123, 0, NULL, &info);
|
||||||
|
frame_out(rsx, NULL, 0, 124, 0, NULL, &info);
|
||||||
|
CHECK(rsx_flip_calls == flips + 5 && !rsx->ui_presented,
|
||||||
|
"ui_dupe_pacing: cleanup lost display cadence or stayed pending");
|
||||||
|
|
||||||
|
info.widgets_active = true;
|
||||||
|
flips = rsx_flip_calls;
|
||||||
|
waits = rsx_flip_waits;
|
||||||
|
rsx_set_nonblock_state(rsx, true, false, 1);
|
||||||
|
frame_out(rsx, NULL, 0, 125, 0, NULL, &info);
|
||||||
|
CHECK(rsx_flip_calls == flips + 1 && rsx_flip_waits == waits,
|
||||||
|
"ui_dupe_pacing: nonblocking frame waited or deferred");
|
||||||
|
rsx_set_nonblock_state(rsx, false, false, 1);
|
||||||
|
info.frame_repeat = true;
|
||||||
|
frame_out(rsx, NULL, 0, 126, 0, NULL, &info);
|
||||||
|
CHECK(rsx_flip_calls == flips + 2 && rsx_flip_waits == waits + 1,
|
||||||
|
"ui_dupe_pacing: cached replay deferred");
|
||||||
|
rsx->overlay_enable = true;
|
||||||
|
flips = rsx_flip_calls;
|
||||||
|
frame_out(rsx, NULL, 0, 127, 0, NULL, &info);
|
||||||
|
CHECK(rsx_flip_calls == flips + 1 && !fake_flip_due,
|
||||||
|
"ui_dupe_pacing: overlay returned with vertices still in use");
|
||||||
|
fake_flip_period = 0;
|
||||||
|
fake_flip_due = 0;
|
||||||
|
driver_down(rsx);
|
||||||
|
fake_hwr.context_type = RETRO_HW_CONTEXT_NONE;
|
||||||
|
printf("ui_dupe_pacing: ok\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_bare_dupe_pacing(void)
|
||||||
|
{
|
||||||
|
static uint16_t pixels[4 * 4];
|
||||||
|
video_frame_info_t info;
|
||||||
|
struct retro_rsx_image image;
|
||||||
|
rsx_t *rsx;
|
||||||
|
unsigned mode, i, flips, waits, calls, draws, restores, index, allocations;
|
||||||
|
retro_time_t start;
|
||||||
|
|
||||||
|
for (mode = 0; mode < 4; mode++)
|
||||||
|
{
|
||||||
|
const void *source = mode & 1 ? RETRO_HW_FRAME_BUFFER_VALID : pixels;
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
info.widgets_active = true;
|
||||||
|
info.max_swapchain_images = 3;
|
||||||
|
fake_hwr.context_type = mode & 1 ? RETRO_HW_CONTEXT_RSX : RETRO_HW_CONTEXT_NONE;
|
||||||
|
rsx = driver_up();
|
||||||
|
fake_widgets_visible = false;
|
||||||
|
image.offset = 0x10000;
|
||||||
|
image.pitch = 4 * 4;
|
||||||
|
image.width = image.height = 4;
|
||||||
|
if (mode & 1)
|
||||||
|
CHECK(rsx_set_hw_image(rsx, &image), "bare_dupe_pacing: cached image");
|
||||||
|
frame_out(rsx, source, VIDEO_SCALE_PACK(4, 4), 0, 8, NULL, &info);
|
||||||
|
rsx_set_nonblock_state(rsx, false, false, 1);
|
||||||
|
flips = rsx_flip_calls;
|
||||||
|
waits = rsx_flip_waits;
|
||||||
|
calls = fake_widget_calls;
|
||||||
|
draws = rsx_draw_calls;
|
||||||
|
restores = rsx_depth_test_calls;
|
||||||
|
index = rsx->tex_index;
|
||||||
|
allocations = rsx_alloc_calls;
|
||||||
|
fake_flip_period = 16667;
|
||||||
|
fake_time = start = 10 * fake_flip_period;
|
||||||
|
fake_gpu_time = fake_time;
|
||||||
|
fake_flip_pending();
|
||||||
|
for (i = 0; i < 120; i++)
|
||||||
|
{
|
||||||
|
fake_time += 1000;
|
||||||
|
frame_out(rsx, mode >= 2 || (i & 1) ? NULL : source,
|
||||||
|
VIDEO_SCALE_PACK(4, 4), i + 1, 8, NULL, &info);
|
||||||
|
}
|
||||||
|
CHECK(rsx_flip_calls == flips + 120 && rsx_flip_waits == waits + 120,
|
||||||
|
"bare_dupe_pacing: core duplicates lost synchronized presents (mode %u)", mode);
|
||||||
|
CHECK(fake_time - start == 120 * fake_flip_period,
|
||||||
|
"bare_dupe_pacing: lost display cadence (mode %u, %ld us)",
|
||||||
|
mode, (long)(fake_time - start));
|
||||||
|
CHECK(rsx_draw_calls == draws + 120 && !rsx->ui_presented,
|
||||||
|
"bare_dupe_pacing: cached background missing or marked as UI");
|
||||||
|
CHECK(!(mode & 1) || rsx_depth_test_calls == restores + 120,
|
||||||
|
"bare_dupe_pacing: cached hardware draw missing state restore");
|
||||||
|
CHECK(fake_widget_calls == calls + 120 && !info.widgets_visible,
|
||||||
|
"bare_dupe_pacing: invisible widget housekeeping suppressed");
|
||||||
|
CHECK(mode < 2 || (rsx->tex_index == index && rsx_alloc_calls == allocations),
|
||||||
|
"bare_dupe_pacing: duplicate uploaded or allocated a texture");
|
||||||
|
flips = rsx_flip_calls;
|
||||||
|
waits = rsx_flip_waits;
|
||||||
|
draws = rsx_draw_calls;
|
||||||
|
rsx_set_nonblock_state(rsx, true, false, 1);
|
||||||
|
frame_out(rsx, NULL, 0, 121, 0, NULL, &info);
|
||||||
|
CHECK(rsx_flip_calls == flips && rsx_flip_waits == waits && rsx_draw_calls == draws,
|
||||||
|
"bare_dupe_pacing: nonblocking duplicate presented or waited");
|
||||||
|
fake_flip_period = fake_flip_due = 0;
|
||||||
|
driver_down(rsx);
|
||||||
|
}
|
||||||
|
fake_hwr.context_type = RETRO_HW_CONTEXT_NONE;
|
||||||
|
printf("bare_dupe_pacing: ok\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input is polled as retro_run starts, when the last frame call has
|
||||||
|
* returned; the frame is seen at the vblank its flip lands on */
|
||||||
|
static void test_flip_latency(void)
|
||||||
|
{
|
||||||
|
static uint16_t pixels[4 * 4];
|
||||||
|
video_frame_info_t info;
|
||||||
|
rsx_t *rsx;
|
||||||
|
unsigned images, i, hangs;
|
||||||
|
retro_time_t poll, worst;
|
||||||
|
|
||||||
|
for (images = 2; images <= 3; images++)
|
||||||
|
{
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
info.max_swapchain_images = images;
|
||||||
|
rsx = driver_up();
|
||||||
|
rsx_set_nonblock_state(rsx, false, false, 1);
|
||||||
|
fake_flip_period = 16667;
|
||||||
|
fake_time = 10 * fake_flip_period;
|
||||||
|
fake_gpu_time = fake_time;
|
||||||
|
fake_flip_pending();
|
||||||
|
hangs = fake_flip_hangs;
|
||||||
|
worst = 0;
|
||||||
|
for (i = 0; i < 120; i++)
|
||||||
|
{
|
||||||
|
poll = fake_time;
|
||||||
|
fake_time += 1000;
|
||||||
|
frame_out(rsx, pixels, VIDEO_SCALE_PACK(4, 4), i, 8, NULL, &info);
|
||||||
|
if (i && fake_last_flip_due - poll > worst)
|
||||||
|
worst = fake_last_flip_due - poll;
|
||||||
|
}
|
||||||
|
CHECK(fake_flip_hangs == hangs,
|
||||||
|
"flip_latency: waited on a flip never queued (%u images)", images);
|
||||||
|
if (images == 2)
|
||||||
|
CHECK(worst <= fake_flip_period + 1000,
|
||||||
|
"flip_latency: two images ran the core into the pending flip"
|
||||||
|
" (%ld us to display)", (long)worst);
|
||||||
|
else
|
||||||
|
CHECK(worst > fake_flip_period + 1000,
|
||||||
|
"flip_latency: three images kept the core behind the flip");
|
||||||
|
fake_flip_period = fake_flip_due = 0;
|
||||||
|
driver_down(rsx);
|
||||||
|
}
|
||||||
|
printf("flip_latency: ok\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A dupe redraws the last frame that loaded, and with nothing loaded
|
||||||
|
* leaves the screen as it is */
|
||||||
|
static void test_dupe_keeps_image(void)
|
||||||
|
{
|
||||||
|
static uint16_t pixels[4 * 4];
|
||||||
|
video_frame_info_t info;
|
||||||
|
rsx_t *rsx;
|
||||||
|
unsigned flips, draws, index;
|
||||||
|
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
rsx = driver_up();
|
||||||
|
rsx_set_nonblock_state(rsx, false, false, 1);
|
||||||
|
|
||||||
|
flips = rsx_flip_calls;
|
||||||
|
frame_out(rsx, NULL, 0, 0, 0, NULL, &info);
|
||||||
|
CHECK(rsx_flip_calls == flips,
|
||||||
|
"dupe_keeps_image: a dupe with nothing cached presented a cleared buffer");
|
||||||
|
|
||||||
|
frame_out(rsx, pixels, VIDEO_SCALE_PACK(4, 4), 1, 8, NULL, &info);
|
||||||
|
index = rsx->tex_index;
|
||||||
|
rsx_alloc_calls = 0;
|
||||||
|
rsx_fail_at = 1;
|
||||||
|
frame_out(rsx, pixels, VIDEO_SCALE_PACK(4, 4), 2, 8, NULL, &info);
|
||||||
|
rsx_fail_at = 0;
|
||||||
|
CHECK(rsx->tex_index == index && rsx->texture[index].data,
|
||||||
|
"dupe_keeps_image: a failed upload became the cached frame");
|
||||||
|
|
||||||
|
flips = rsx_flip_calls;
|
||||||
|
draws = rsx_draw_calls;
|
||||||
|
frame_out(rsx, NULL, 0, 3, 0, NULL, &info);
|
||||||
|
CHECK(rsx_flip_calls == flips + 1 && rsx_draw_calls == draws + 1,
|
||||||
|
"dupe_keeps_image: the last loaded frame was not presented");
|
||||||
|
|
||||||
|
driver_down(rsx);
|
||||||
|
printf("dupe_keeps_image: ok\n");
|
||||||
|
}
|
||||||
|
|
||||||
static void test_widget_layer_order(void)
|
static void test_widget_layer_order(void)
|
||||||
{
|
{
|
||||||
uint32_t pixels[4] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff };
|
uint32_t pixels[4] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff };
|
||||||
@@ -1513,6 +1849,10 @@ int main(int argc, char **argv)
|
|||||||
} tests[] = {
|
} tests[] = {
|
||||||
{ "widget_dupe_frame", test_widget_dupe_frame },
|
{ "widget_dupe_frame", test_widget_dupe_frame },
|
||||||
{ "widget_visibility", test_widget_visibility },
|
{ "widget_visibility", test_widget_visibility },
|
||||||
|
{ "ui_dupe_pacing", test_ui_dupe_pacing },
|
||||||
|
{ "bare_dupe_pacing", test_bare_dupe_pacing },
|
||||||
|
{ "flip_latency", test_flip_latency },
|
||||||
|
{ "dupe_keeps_image", test_dupe_keeps_image },
|
||||||
{ "widget_layer_order", test_widget_layer_order },
|
{ "widget_layer_order", test_widget_layer_order },
|
||||||
{ "ui_cleanup", test_ui_cleanup },
|
{ "ui_cleanup", test_ui_cleanup },
|
||||||
{ "hw_interface", test_hw_interface },
|
{ "hw_interface", test_hw_interface },
|
||||||
|
|||||||
Reference in new issue
Block a user