mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 02:08:22 -10:00
gl: keep threaded hardware handoff nonblocking
Select an unqueued, completed target before publishing queried GL frames. If none is available, retain the producer's target and account for a missed presentation instead of waiting for the presenter or GPU. Return source fences through atomic exchange and retire them on target reuse, rather than deleting them immediately after a cross-context wait. Only successful completion permits reuse. Superseded disposable GL completion fences do not need a blocking wait before replacement. Preserve cached-framebuffer compatibility and other hardware APIs.
This commit is contained in:
1 parent
b3dcba0388
commit
7fa57eaf2b
10 files changed
+242
-84
No files matched your search
@@ -35,10 +35,8 @@
|
||||
#include "../video_driver.h"
|
||||
#include "gl_common.h"
|
||||
|
||||
/* The hardware ring's per-slot sync, handed from the core's thread to
|
||||
* the video thread by exchange so that one thread waits and deletes
|
||||
* each. A backend without pointer atomics only serves single-core
|
||||
* targets, where a plain swap is the same. */
|
||||
/* Source fences pass to the video thread and back to the producer
|
||||
* by exchange. Without pointer atomics, only single-core targets run. */
|
||||
#ifndef GL_HW_RING_SYNC_T
|
||||
#if defined(RETRO_ATOMIC_HAS_PTR)
|
||||
#define GL_HW_RING_SYNC_T retro_atomic_ptr_t
|
||||
@@ -189,6 +187,7 @@ struct gl2
|
||||
* placed after rendering into each slot's texture, for the frame on
|
||||
* this thread to wait before reading it. Sync objects are shared
|
||||
* between the two contexts (GL_HW_RING_SYNC_SWAP). */
|
||||
GL_HW_RING_SYNC_T hw_ring_retired_sync[3];
|
||||
GL_HW_RING_SYNC_T hw_ring_sync[3];
|
||||
void *hw_ring_done_sync; /* taken ahead of the swap */
|
||||
/* Placed after each PBO readback: a slot is read only once its
|
||||
|
||||
+32
-6
@@ -1987,6 +1987,21 @@ static void gl2_bind_core_context(gl2_t *gl, bool enable)
|
||||
static void gl2_renderchain_deinit_hw_render(gl2_t *gl, gl2_renderchain_data_t *chain)
|
||||
{
|
||||
gl2_bind_core_context(gl, true);
|
||||
#ifdef GL2_HW_RING_SYNC
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
void *sync;
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_sync[i], NULL, sync);
|
||||
if (sync)
|
||||
glDeleteSync((GLsync)sync);
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_retired_sync[i], NULL, sync);
|
||||
if (sync)
|
||||
glDeleteSync((GLsync)sync);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (gl->flags & GL2_FLAG_HW_RENDER_FBO_INIT)
|
||||
gl2_delete_fb(gl->textures, gl->hw_render_fbo);
|
||||
if (chain->flags & GL2_CHAIN_FLAG_HW_RENDER_DEPTH_INIT)
|
||||
@@ -3700,9 +3715,14 @@ static bool gl2_hw_ring_capture(void *data, unsigned slot,
|
||||
/* A sync the video thread never took belongs to a frame it
|
||||
* dropped; deleting it does not disturb a wait already placed. */
|
||||
void *old;
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_sync[slot],
|
||||
(void*)glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0), old);
|
||||
void *sync = (void*)glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
if (!sync)
|
||||
return false;
|
||||
glFlush();
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_retired_sync[slot], NULL, old);
|
||||
if (old)
|
||||
glDeleteSync((GLsync)old);
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_sync[slot], sync, old);
|
||||
if (old)
|
||||
glDeleteSync((GLsync)old);
|
||||
return true;
|
||||
@@ -3725,8 +3745,11 @@ static bool gl2_hw_ring_present_slot(void *data, unsigned slot)
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_sync[slot], NULL, sync);
|
||||
if (sync)
|
||||
{
|
||||
void *old;
|
||||
glWaitSync((GLsync)sync, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync((GLsync)sync);
|
||||
/* The producer retires this after the slot's reads complete. */
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_retired_sync[slot], sync, old);
|
||||
(void)old;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -3813,12 +3836,15 @@ static bool gl2_hw_ring_fence_wait(void *data, void *fence, unsigned timeout_us)
|
||||
if (!f)
|
||||
return true;
|
||||
#ifdef GL2_HW_RING_SYNC
|
||||
if (f->sync)
|
||||
if (((gl2_t*)data)->flags & GL2_FLAG_HAVE_SYNC)
|
||||
{
|
||||
GLenum r = glClientWaitSync((GLsync)f->sync, GL_SYNC_FLUSH_COMMANDS_BIT,
|
||||
GLenum r;
|
||||
if (!f->sync)
|
||||
return false;
|
||||
r = glClientWaitSync((GLsync)f->sync, 0,
|
||||
timeout_us == HW_RING_WAIT_FOREVER
|
||||
? GL_TIMEOUT_IGNORED : (GLuint64)timeout_us * 1000);
|
||||
if (r == GL_TIMEOUT_EXPIRED)
|
||||
if (r != GL_ALREADY_SIGNALED && r != GL_CONDITION_SATISFIED)
|
||||
return false;
|
||||
/* Not deleted here: the video thread made it and may still be
|
||||
* in the swap that follows it. It goes when the cell is
|
||||
|
||||
+33
-11
@@ -52,10 +52,8 @@
|
||||
#include "../../retroarch.h"
|
||||
#include "../../verbosity.h"
|
||||
|
||||
/* The hardware ring's per-slot sync, handed from the core's thread to
|
||||
* the video thread by exchange so that one thread waits and deletes
|
||||
* each. A backend without pointer atomics only serves single-core
|
||||
* targets, where a plain swap is the same. */
|
||||
/* Source fences pass to the video thread and back to the producer
|
||||
* by exchange. Without pointer atomics, only single-core targets run. */
|
||||
#ifndef GL_HW_RING_SYNC_T
|
||||
#if defined(RETRO_ATOMIC_HAS_PTR)
|
||||
#define GL_HW_RING_SYNC_T retro_atomic_ptr_t
|
||||
@@ -390,6 +388,7 @@ typedef struct gl3
|
||||
* the two contexts. */
|
||||
GLuint hw_ring_texture[3];
|
||||
GLuint hw_ring_fbo[3];
|
||||
GL_HW_RING_SYNC_T hw_ring_retired_sync[3];
|
||||
GL_HW_RING_SYNC_T hw_ring_sync[3]; /* GL_HW_RING_SYNC_SWAP */
|
||||
void *hw_ring_done_sync; /* taken ahead of the swap */
|
||||
|
||||
@@ -2357,6 +2356,16 @@ static void gl3_deinit_hw_render(gl3_t *gl)
|
||||
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
void *sync;
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_sync[i], NULL, sync);
|
||||
if (sync)
|
||||
glDeleteSync((GLsync)sync);
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_retired_sync[i], NULL, sync);
|
||||
if (sync)
|
||||
glDeleteSync((GLsync)sync);
|
||||
}
|
||||
for (i = 1; i < 3; i++)
|
||||
{
|
||||
if (gl->hw_ring_fbo[i])
|
||||
@@ -8556,9 +8565,14 @@ static bool gl3_hw_ring_capture(void *data, unsigned slot,
|
||||
/* A sync the video thread never took belongs to a frame it
|
||||
* dropped; deleting it does not disturb a wait already placed. */
|
||||
void *old;
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_sync[slot],
|
||||
(void*)glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0), old);
|
||||
void *sync = (void*)glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
if (!sync)
|
||||
return false;
|
||||
glFlush();
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_retired_sync[slot], NULL, old);
|
||||
if (old)
|
||||
glDeleteSync((GLsync)old);
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_sync[slot], sync, old);
|
||||
if (old)
|
||||
glDeleteSync((GLsync)old);
|
||||
}
|
||||
@@ -8576,8 +8590,11 @@ static bool gl3_hw_ring_present_slot(void *data, unsigned slot)
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_sync[slot], NULL, sync);
|
||||
if (sync)
|
||||
{
|
||||
void *old;
|
||||
glWaitSync((GLsync)sync, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync((GLsync)sync);
|
||||
/* The producer retires this after the slot's reads complete. */
|
||||
GL_HW_RING_SYNC_SWAP(&gl->hw_ring_retired_sync[slot], sync, old);
|
||||
(void)old;
|
||||
}
|
||||
gl->hw_render_texture = gl->hw_ring_texture[slot];
|
||||
return true;
|
||||
@@ -8628,12 +8645,17 @@ static bool gl3_hw_ring_fence_wait(void *data, void *fence, unsigned timeout_us)
|
||||
{
|
||||
gl3_ring_fence_t *f = (gl3_ring_fence_t*)fence;
|
||||
(void)data;
|
||||
if (!f || !f->sync)
|
||||
if (!f)
|
||||
return true;
|
||||
if (glClientWaitSync((GLsync)f->sync, GL_SYNC_FLUSH_COMMANDS_BIT,
|
||||
timeout_us == HW_RING_WAIT_FOREVER
|
||||
? GL_TIMEOUT_IGNORED : (GLuint64)timeout_us * 1000) == GL_TIMEOUT_EXPIRED)
|
||||
if (!f->sync)
|
||||
return false;
|
||||
{
|
||||
GLenum ready = glClientWaitSync((GLsync)f->sync, 0,
|
||||
timeout_us == HW_RING_WAIT_FOREVER
|
||||
? GL_TIMEOUT_IGNORED : (GLuint64)timeout_us * 1000);
|
||||
if (ready != GL_ALREADY_SIGNALED && ready != GL_CONDITION_SATISFIED)
|
||||
return false;
|
||||
}
|
||||
/* Not deleted here: the video thread made it and may still be in
|
||||
* the swap that follows it. It goes when the cell is signalled again */
|
||||
return true;
|
||||
|
||||
+23
-2
@@ -1061,6 +1061,27 @@ int video_thread_hw_publish(thread_video_t *thr, unsigned *taken_back)
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
if (ring->api == HW_API_GL && !stay)
|
||||
{
|
||||
unsigned n;
|
||||
for (n = 1; n < VIDEO_THREAD_HW_RING; n++)
|
||||
{
|
||||
unsigned next = (published + n) % VIDEO_THREAD_HW_RING;
|
||||
int st = retro_atomic_load_acquire_int(&thr->frame.state);
|
||||
if ((int)next == retro_atomic_load_acquire_int(&ring->last_presented)
|
||||
|| hw_slot_queued(thr, next, st))
|
||||
continue;
|
||||
if (!hw_fence_wait(&ring->slot[next].fence,
|
||||
thr->poke->hw_ring_fence_wait, thr->driver_data, 0))
|
||||
continue;
|
||||
if (!thr->poke->hw_ring_capture(thr->driver_data, published, NULL, 0))
|
||||
return VIDEO_THREAD_HW_BUSY;
|
||||
ring->index = next;
|
||||
return (int)published;
|
||||
}
|
||||
/* Retain the producer's slot when every other slot has a reader. */
|
||||
return VIDEO_THREAD_HW_BUSY;
|
||||
}
|
||||
if (ring->api == HW_API_GL)
|
||||
{
|
||||
/* Fence the core's rendering into the slot, on its context. */
|
||||
@@ -1221,8 +1242,8 @@ void video_thread_hw_after_frame(thread_video_t *thr, int hw_slot)
|
||||
/* Signalled after the submission the frame call just made, on a
|
||||
* fence nobody else holds: a dupe arms a slot that is armed already. */
|
||||
hw_fence_arm(&s->fence, thr->poke->hw_ring_fence_signal,
|
||||
thr->poke->hw_ring_fence_wait, thr->driver_data,
|
||||
HW_RING_WAIT_FOREVER);
|
||||
ring->api == HW_API_GL ? NULL : thr->poke->hw_ring_fence_wait,
|
||||
thr->driver_data, HW_RING_WAIT_FOREVER);
|
||||
}
|
||||
|
||||
#ifdef HAVE_VULKAN
|
||||
|
||||
@@ -56,6 +56,7 @@ RETRO_BEGIN_DECLS
|
||||
#ifdef HAVE_THREADS
|
||||
|
||||
#define VIDEO_THREAD_HW_RING 3
|
||||
#define VIDEO_THREAD_HW_BUSY (-2)
|
||||
|
||||
struct thread_video;
|
||||
|
||||
@@ -67,7 +68,9 @@ bool video_thread_get_hw_render_interface(void *data,
|
||||
|
||||
/* Main thread, at a push with RETRO_HW_FRAME_BUFFER_VALID: returns the
|
||||
* HW slot to publish, advancing the core's index and waiting the slot
|
||||
* the core will fill next; -1 if there is no ring. *taken_back counts
|
||||
* the core will fill next; -1 if there is no ring. GL returns
|
||||
* VIDEO_THREAD_HW_BUSY without publishing when no slot is ready.
|
||||
* *taken_back counts
|
||||
* the unclaimed frames taken off the frame ring for it. */
|
||||
int video_thread_hw_publish(struct thread_video *thr, unsigned *taken_back);
|
||||
|
||||
|
||||
@@ -88,7 +88,8 @@ static INLINE void hw_fence_init(hw_fence_t *f)
|
||||
|
||||
/* Video thread, after a frame drawn from the slot; or the core's
|
||||
* thread, for a frame taken back (see above). `forever` is the
|
||||
* driver's unbounded timeout. */
|
||||
* driver's unbounded timeout. NULL wait permits disposable syncs to
|
||||
* be superseded without waiting. */
|
||||
static INLINE void hw_fence_arm(hw_fence_t *f, hw_fence_signal_fn signal,
|
||||
hw_fence_wait_fn wait, void *data, unsigned forever)
|
||||
{
|
||||
@@ -105,7 +106,8 @@ static INLINE void hw_fence_arm(hw_fence_t *f, hw_fence_signal_fn signal,
|
||||
prev = retro_atomic_exchange_int(&f->armed, c + 1);
|
||||
if (prev)
|
||||
{
|
||||
wait(data, f->cell[prev - 1], forever);
|
||||
if (wait)
|
||||
wait(data, f->cell[prev - 1], forever);
|
||||
retro_atomic_store_release_int(&f->busy[prev - 1], 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3684,6 +3684,21 @@ static bool video_thread_frame(void *data, const void *frame_,
|
||||
if (frame_ == RETRO_HW_FRAME_BUFFER_VALID)
|
||||
{
|
||||
hw_slot = video_thread_hw_publish(thr, &taken_back);
|
||||
if (hw_slot == VIDEO_THREAD_HW_BUSY)
|
||||
{
|
||||
thr->miss_count++;
|
||||
if (timed)
|
||||
video_thread_handoff_account(thr,
|
||||
(uint64_t)cpu_features_get_perf_counter() - c_in - c_wait,
|
||||
0, c_wait, 0, true, false, waited, true);
|
||||
thr->last_time = cpu_features_get_time_usec();
|
||||
thr->run_start = thr->last_time;
|
||||
if (timed)
|
||||
video_thread_handoff_latch(thr,
|
||||
(uint64_t)cpu_features_get_perf_counter() - c_now,
|
||||
(uint64_t)(thr->last_time - t_now));
|
||||
return true;
|
||||
}
|
||||
frame_ = NULL;
|
||||
/* No ring: the driver cannot take a hardware frame from this
|
||||
* thread. frame_ is NULL now, which this function treats as a
|
||||
|
||||
@@ -35,7 +35,51 @@ static int run_chain(void)
|
||||
return chain.fbo_pass;
|
||||
}
|
||||
|
||||
|
||||
static unsigned ring_waits, ring_deletes;
|
||||
static bool ring_alloc_fails;
|
||||
static void APIENTRY ring_wait(GLsync sync, GLbitfield flags, GLuint64 timeout)
|
||||
{
|
||||
CHECK(sync == (GLsync)(uintptr_t)1 && !flags && timeout == GL_TIMEOUT_IGNORED,
|
||||
"HW ring queues the source dependency");
|
||||
ring_waits++;
|
||||
}
|
||||
static GLsync APIENTRY ring_fence(GLenum condition, GLbitfield flags)
|
||||
{
|
||||
(void)condition; (void)flags;
|
||||
return ring_alloc_fails ? NULL : (GLsync)(uintptr_t)2;
|
||||
}
|
||||
static void APIENTRY ring_delete(GLsync sync)
|
||||
{
|
||||
CHECK(sync == (GLsync)(uintptr_t)1, "producer retires the consumed source fence");
|
||||
ring_deletes++;
|
||||
}
|
||||
static void run_ring_cases(void)
|
||||
{
|
||||
gl2_t gl;
|
||||
void *old;
|
||||
memset(&gl, 0, sizeof(gl));
|
||||
gl.textures = 3; gl.flags = GL2_FLAG_HAVE_SYNC;
|
||||
__rglgen_glWaitSync = ring_wait;
|
||||
__rglgen_glFenceSync = ring_fence;
|
||||
__rglgen_glDeleteSync = ring_delete;
|
||||
GL_HW_RING_SYNC_SWAP(&gl.hw_ring_sync[1], (void*)(uintptr_t)1, old);
|
||||
(void)old;
|
||||
CHECK(gl2_hw_ring_present_slot(&gl, 1), "HW frame presented");
|
||||
CHECK(ring_waits == 1 && !ring_deletes, "video thread never deletes the source fence");
|
||||
CHECK(gl2_hw_ring_present_slot(&gl, 1), "duplicate reuses HW frame");
|
||||
CHECK(ring_waits == 1 && !ring_deletes, "duplicate does not consume the fence twice");
|
||||
ring_alloc_fails = true;
|
||||
CHECK(!gl2_hw_ring_capture(&gl, 1, NULL, 0), "failed source fence refuses publication");
|
||||
CHECK(!ring_deletes, "failed capture retains returned fence");
|
||||
ring_alloc_fails = false;
|
||||
CHECK(gl2_hw_ring_capture(&gl, 1, NULL, 0), "producer captures the reused slot");
|
||||
CHECK(ring_deletes == 1, "producer retires returned fence once");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
return run_cases(run_chain, -1);
|
||||
int result = run_cases(run_chain, -1);
|
||||
run_ring_cases();
|
||||
return result || fails;
|
||||
}
|
||||
@@ -304,11 +304,54 @@ static void run_resize_cases(void)
|
||||
CHECK(sync_created == sync_deleted, "resize teardown retires every recording fence");
|
||||
}
|
||||
|
||||
|
||||
static unsigned ring_waits, ring_deletes;
|
||||
static bool ring_alloc_fails;
|
||||
static void APIENTRY ring_wait(GLsync sync, GLbitfield flags, GLuint64 timeout)
|
||||
{
|
||||
CHECK(sync == (GLsync)(uintptr_t)1 && !flags && timeout == GL_TIMEOUT_IGNORED,
|
||||
"HW ring queues the source dependency");
|
||||
ring_waits++;
|
||||
}
|
||||
static GLsync APIENTRY ring_fence(GLenum condition, GLbitfield flags)
|
||||
{
|
||||
(void)condition; (void)flags;
|
||||
return ring_alloc_fails ? NULL : (GLsync)(uintptr_t)2;
|
||||
}
|
||||
static void APIENTRY ring_delete(GLsync sync)
|
||||
{
|
||||
CHECK(sync == (GLsync)(uintptr_t)1, "producer retires the consumed source fence");
|
||||
ring_deletes++;
|
||||
}
|
||||
static void run_ring_cases(void)
|
||||
{
|
||||
gl3_t gl;
|
||||
void *old;
|
||||
memset(&gl, 0, sizeof(gl));
|
||||
gl.hw_ring_texture[1] = 99;
|
||||
__rglgen_glWaitSync = ring_wait;
|
||||
__rglgen_glFenceSync = ring_fence;
|
||||
__rglgen_glDeleteSync = ring_delete;
|
||||
GL_HW_RING_SYNC_SWAP(&gl.hw_ring_sync[1], (void*)(uintptr_t)1, old);
|
||||
(void)old;
|
||||
CHECK(gl3_hw_ring_present_slot(&gl, 1), "HW frame presented");
|
||||
CHECK(ring_waits == 1 && !ring_deletes, "video thread never deletes the source fence");
|
||||
CHECK(gl3_hw_ring_present_slot(&gl, 1), "duplicate reuses HW frame");
|
||||
CHECK(ring_waits == 1 && !ring_deletes, "duplicate does not consume the fence twice");
|
||||
ring_alloc_fails = true;
|
||||
CHECK(!gl3_hw_ring_capture(&gl, 1, NULL, 0), "failed source fence refuses publication");
|
||||
CHECK(!ring_deletes, "failed capture retains returned fence");
|
||||
ring_alloc_fails = false;
|
||||
CHECK(gl3_hw_ring_capture(&gl, 1, NULL, 0), "producer captures the reused slot");
|
||||
CHECK(ring_deletes == 1, "producer retires returned fence once");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int result = run_cases(run_chain, 0);
|
||||
run_readback_cases();
|
||||
run_fence_cases();
|
||||
run_resize_cases();
|
||||
run_ring_cases();
|
||||
return result || fails;
|
||||
}
|
||||
@@ -25,77 +25,60 @@ static bool wait_fence(void *data, void *fence, unsigned us)
|
||||
static bool capture(void *data, unsigned slot, const void *context, unsigned format)
|
||||
{ (void)data; (void)slot; (void)context; (void)format; captures++; return true; }
|
||||
|
||||
typedef struct test_run
|
||||
{
|
||||
thread_video_t *thr;
|
||||
retro_atomic_int_t done;
|
||||
unsigned taken;
|
||||
int published;
|
||||
} test_run_t;
|
||||
|
||||
static void publish(void *data)
|
||||
static void signal_fence(void *data, void *fence) { (void)data; (void)fence; }
|
||||
|
||||
static bool gpu_ready = true;
|
||||
static unsigned polls;
|
||||
static bool poll_fence(void *data, void *fence, unsigned us)
|
||||
{
|
||||
test_run_t *t = (test_run_t*)data;
|
||||
t->published = video_thread_hw_publish(t->thr, &t->taken);
|
||||
retro_atomic_store_release_int(&t->done, 1);
|
||||
(void)data; (void)fence;
|
||||
CHECK(us == 0, "slot readiness never blocks");
|
||||
polls++;
|
||||
return gpu_ready;
|
||||
}
|
||||
|
||||
static void await_flag(retro_atomic_int_t *flag)
|
||||
{
|
||||
retro_time_t start = cpu_features_get_time_usec();
|
||||
while (!retro_atomic_load_acquire_int(flag))
|
||||
{
|
||||
if (cpu_features_get_time_usec() - start > 5000000)
|
||||
{
|
||||
fprintf(stderr, "FAIL: queue watchdog\n");
|
||||
exit(1);
|
||||
}
|
||||
retro_sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void run_case(int state, int last, bool blocked)
|
||||
static void run_case(int state, int last, int expected)
|
||||
{
|
||||
static thread_video_t thr;
|
||||
video_poke_interface_t poke;
|
||||
hw_ring_t *ring;
|
||||
test_run_t t;
|
||||
sthread_t *thread;
|
||||
unsigned taken = 99;
|
||||
int published;
|
||||
memset(&thr, 0, sizeof(thr));
|
||||
memset(&poke, 0, sizeof(poke));
|
||||
memset(&t, 0, sizeof(t));
|
||||
created = freed = captures = 0;
|
||||
created = freed = captures = polls = 0;
|
||||
poke.hw_ring_fence_new = new_fence;
|
||||
poke.hw_ring_fence_free = free_fence;
|
||||
poke.hw_ring_fence_wait = wait_fence;
|
||||
poke.hw_ring_fence_wait = poll_fence;
|
||||
poke.hw_ring_capture = capture;
|
||||
thr.poke = &poke;
|
||||
if (!retro_eventcount_init(&thr.frame.ring) || !hw_ring_setup(&thr, &ring))
|
||||
exit(1);
|
||||
ring->api = HW_API_GL;
|
||||
ring->index = VIDEO_THREAD_HW_RING - 1;
|
||||
ring->index = 2;
|
||||
ring->gl_queried = true;
|
||||
retro_atomic_store_release_int(&ring->last_presented, last);
|
||||
thr.frame.slot[0].hw_slot = thr.frame.slot[1].hw_slot = -1;
|
||||
thr.frame.slot[0].dupe = true;
|
||||
thr.frame.slot[0].hw_slot = 0;
|
||||
thr.frame.slot[1].hw_slot = 1;
|
||||
retro_atomic_store_release_int(&thr.frame.state, state);
|
||||
t.thr = &thr;
|
||||
if (!(thread = sthread_create(publish, &t)))
|
||||
exit(1);
|
||||
if (blocked)
|
||||
{
|
||||
await_flag(&thr.frame.ring.waiters);
|
||||
retro_sleep(80);
|
||||
CHECK(!retro_atomic_load_acquire_int(&t.done),
|
||||
"duplicate retains GL slot beyond the short wait");
|
||||
retro_atomic_store_release_int(&thr.frame.state, VIDEO_THREAD_RING_MAKE(0, 0, false));
|
||||
retro_eventcount_notify(&thr.frame.ring);
|
||||
}
|
||||
await_flag(&t.done);
|
||||
sthread_join(thread);
|
||||
CHECK(t.published == VIDEO_THREAD_HW_RING - 1 && !t.taken
|
||||
&& ring->index == 0 && captures == 1, "publication preserves slot rotation");
|
||||
CHECK(!retro_atomic_load_acquire_int(&thr.frame.ring.waiters), "waiter retired");
|
||||
/* Both candidates have GPU work left from an earlier presentation. */
|
||||
hw_fence_arm(&ring->slot[0].fence, signal_fence, NULL, NULL, 0);
|
||||
hw_fence_arm(&ring->slot[0].fence, signal_fence, NULL, NULL, 0);
|
||||
hw_fence_arm(&ring->slot[1].fence, signal_fence, NULL, NULL, 0);
|
||||
CHECK(!polls, "superseded disposable fences never wait");
|
||||
published = video_thread_hw_publish(&thr, &taken);
|
||||
CHECK(!taken, "publication never takes queued slots back");
|
||||
CHECK(!retro_atomic_load_acquire_int(&thr.frame.ring.waiters), "publication never parks");
|
||||
if (expected < 0)
|
||||
CHECK(published == VIDEO_THREAD_HW_BUSY && ring->index == 2 && !captures,
|
||||
"busy ring retains the producer's slot without publishing");
|
||||
else
|
||||
CHECK(published == 2 && ring->index == (unsigned)expected && captures == 1,
|
||||
"publication advances only to a completed unqueued slot");
|
||||
CHECK(polls <= 2, "bounded readiness checks");
|
||||
gpu_ready = true;
|
||||
poke.hw_ring_fence_wait = wait_fence;
|
||||
video_thread_hw_free(&thr);
|
||||
CHECK(!thr.frame.hw_ring && created == freed, "all scripted fences retired");
|
||||
retro_eventcount_free(&thr.frame.ring);
|
||||
@@ -103,12 +86,12 @@ static void run_case(int state, int last, bool blocked)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
run_case(VIDEO_THREAD_RING_MAKE(0, 1, false), 0, true);
|
||||
run_case(VIDEO_THREAD_RING_MAKE(1, 0, true), 0, true);
|
||||
run_case(VIDEO_THREAD_RING_MAKE(0, 2, false), 0, true);
|
||||
run_case(VIDEO_THREAD_RING_MAKE(1, 2, false), 0, true);
|
||||
run_case(VIDEO_THREAD_RING_MAKE(0, 0, false), 0, false);
|
||||
run_case(VIDEO_THREAD_RING_MAKE(0, 1, false), 1, false);
|
||||
run_case(VIDEO_THREAD_RING_MAKE(0, 1, false), 0, 1);
|
||||
run_case(VIDEO_THREAD_RING_MAKE(1, 0, true), 0, 1);
|
||||
run_case(VIDEO_THREAD_RING_MAKE(0, 2, false), -1, -1);
|
||||
run_case(VIDEO_THREAD_RING_MAKE(0, 0, false), 1, 0);
|
||||
gpu_ready = false;
|
||||
run_case(VIDEO_THREAD_RING_MAKE(0, 0, false), -1, -1);
|
||||
printf("%s hw_ring_queue_test (%u failures)\n", failures ? "FAIL" : "PASS", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user