mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
video_thread_wrapper: asynchronous texture uploads; menu icons use them
Under threaded video every menu icon went through video_driver_texture_load() -> video_thread_texture_handle(), a command the main thread posts and then waits on. The video thread only services commands between frames, so each icon cost the main thread up to one present - at swap interval 3 on 165 Hz, ~18 ms - and the menu loads dozens at startup and on every driver switch. The first trace of the companion deadlock (cb_task_icon_load -> vulkan_load_texture -> video_thread_wait_reply) was this stall. video_thread_texture_load_async() takes the image, queues it under thr->lock and wakes the worker; the worker uploads with the driver directly (it is the driver's thread), releases the image and parks the handle; video_thread_frame() delivers done(user, handle) on the main thread before the frame that may draw with it. Post order is delivery order. Teardown releases what was never uploaded and answers every outstanding load with 0 after the join, so no handle from a dead context is ever delivered. video_driver_texture_load_async() wraps it with the same CPU-side fixups as the synchronous load and falls back to the synchronous path when there is no wrapper, on the video thread, or for compressed images. cb_task_icon_load() posts instead of waiting. The generation check moves to delivery, where it belongs now that the target can be freed while the upload is in flight; a stale result is unloaded, and a target that already got a newer handle has the old one unloaded before the write, so nothing leaks. Harness: samples/gfx/threaded_video gains an async lane - posting six loads returns in under 5 ms, uploads run on the worker, done() runs on the main thread in post order with the driver's handles, every image is released once, teardown with loads in flight answers all six with 0, and the no-wrapper fallback completes before returning. Suite green three runs in a row on the release build. Checked: mingw_syntax_check on the three .c files (consumer failures identical to master), host build of the objects, full non-Qt build and harness run.
This commit is contained in:
1 parent
21661131c3
commit
2a9b96830c
6 files changed
+464
-3
No files matched your search
@@ -3662,6 +3662,45 @@ bool video_driver_texture_load(void *data,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool video_driver_texture_load_async(void *data,
|
||||
enum texture_filter_type filter_type,
|
||||
void (*done)(void *user, uintptr_t handle), void *user,
|
||||
void (*release)(void *img))
|
||||
{
|
||||
video_driver_state_t *video_st = &video_driver_st;
|
||||
const video_poke_interface_t *poke = video_st->poke;
|
||||
struct texture_image *ti = (struct texture_image*)data;
|
||||
uintptr_t id = 0;
|
||||
|
||||
if (!ti || !poke || !poke->load_texture)
|
||||
return false;
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
/* The CPU-side fixups video_driver_texture_load() applies happen
|
||||
* here, on the caller's thread, so the video thread only uploads.
|
||||
* Compressed images keep the synchronous path: their driver-native
|
||||
* upload negotiates per driver and is not worth duplicating. */
|
||||
if (!ti->compressed && video_driver_thread_wrapper_active())
|
||||
{
|
||||
if ( ti->pix10
|
||||
&& !video_driver_test_all_flags(GFX_CTX_FLAGS_SCREEN_10BPC_SOURCE))
|
||||
image_texture_narrow_10bit(ti);
|
||||
if (video_thread_texture_load_async(ti, filter_type,
|
||||
done, user, release))
|
||||
return true;
|
||||
/* Wrapper refused (tearing down, or we are the video thread):
|
||||
* synchronous below. */
|
||||
}
|
||||
#endif
|
||||
|
||||
video_driver_texture_load(ti, filter_type, &id);
|
||||
if (release)
|
||||
release(ti);
|
||||
if (done)
|
||||
done(user, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool video_driver_texture_unload(uintptr_t *id)
|
||||
{
|
||||
video_driver_state_t *video_st = &video_driver_st;
|
||||
|
||||
@@ -1500,6 +1500,21 @@ bool video_driver_texture_load(void *data,
|
||||
|
||||
bool video_driver_texture_unload(uintptr_t *id);
|
||||
|
||||
/* Upload without making the caller wait for the video thread. @data
|
||||
* is a struct texture_image the caller gives up: it is handed to
|
||||
* release() once uploaded (on whichever thread uploads it). The
|
||||
* handle arrives through done(user, handle) on the main thread - at
|
||||
* once when the upload is synchronous (no wrapper, compressed image),
|
||||
* otherwise from a later video_thread_async_poll(), which every
|
||||
* frame push runs. done() gets 0 when the upload failed or the video
|
||||
* driver went away first. Returns false, having called neither
|
||||
* callback and taken no ownership, only when no texture can be
|
||||
* loaded at all. Main thread only. */
|
||||
bool video_driver_texture_load_async(void *data,
|
||||
enum texture_filter_type filter_type,
|
||||
void (*done)(void *user, uintptr_t handle), void *user,
|
||||
void (*release)(void *img));
|
||||
|
||||
void video_driver_build_info(video_frame_info_t *video_info);
|
||||
|
||||
/* Context-cache acknowledgement. Set by the context driver (video
|
||||
|
||||
+154
-1
@@ -721,6 +721,150 @@ static void video_thread_schedule_next(thread_video_t *thr)
|
||||
thr->last_present_end = thr->present_period > 0 ? next : now;
|
||||
}
|
||||
|
||||
/* Video thread: take the whole in list under the lock, upload each
|
||||
* node with the driver directly (this is the driver's thread), release
|
||||
* the image, and queue the handle for the main thread. */
|
||||
static void video_thread_async_run(thread_video_t *thr)
|
||||
{
|
||||
video_thread_async_load_t *n;
|
||||
|
||||
slock_lock(thr->lock);
|
||||
n = thr->async.in_head;
|
||||
thr->async.in_head = thr->async.in_tail = NULL;
|
||||
slock_unlock(thr->lock);
|
||||
|
||||
while (n)
|
||||
{
|
||||
video_thread_async_load_t *next = n->next;
|
||||
n->handle = 0;
|
||||
if (thr->driver_data && thr->poke && thr->poke->load_texture)
|
||||
n->handle = thr->poke->load_texture(thr->driver_data,
|
||||
n->img, false, n->filter);
|
||||
if (n->release)
|
||||
n->release(n->img);
|
||||
n->img = NULL;
|
||||
n->next = NULL;
|
||||
|
||||
slock_lock(thr->lock);
|
||||
if (thr->async.out_tail)
|
||||
thr->async.out_tail->next = n;
|
||||
else
|
||||
thr->async.out_head = n;
|
||||
thr->async.out_tail = n;
|
||||
slock_unlock(thr->lock);
|
||||
|
||||
n = next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Main thread: deliver everything in the out list. Runs done() with
|
||||
* the lock released, so a done() that uploads or unloads a texture
|
||||
* is fine. */
|
||||
static void video_thread_async_deliver(thread_video_t *thr)
|
||||
{
|
||||
video_thread_async_load_t *n;
|
||||
|
||||
slock_lock(thr->lock);
|
||||
n = thr->async.out_head;
|
||||
thr->async.out_head = thr->async.out_tail = NULL;
|
||||
slock_unlock(thr->lock);
|
||||
|
||||
while (n)
|
||||
{
|
||||
video_thread_async_load_t *next = n->next;
|
||||
if (n->done)
|
||||
n->done(n->user, n->handle);
|
||||
free(n);
|
||||
n = next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Teardown, after the join: nothing else touches the lists now. Loads
|
||||
* never uploaded are released; every waiter learns of the handle 0,
|
||||
* since any real one died with the driver. */
|
||||
static void video_thread_async_drop_all(thread_video_t *thr)
|
||||
{
|
||||
video_thread_async_load_t *n = thr->async.in_head;
|
||||
while (n)
|
||||
{
|
||||
video_thread_async_load_t *next = n->next;
|
||||
if (n->release && n->img)
|
||||
n->release(n->img);
|
||||
if (n->done)
|
||||
n->done(n->user, 0);
|
||||
free(n);
|
||||
n = next;
|
||||
}
|
||||
n = thr->async.out_head;
|
||||
while (n)
|
||||
{
|
||||
video_thread_async_load_t *next = n->next;
|
||||
if (n->done)
|
||||
n->done(n->user, 0);
|
||||
free(n);
|
||||
n = next;
|
||||
}
|
||||
thr->async.in_head = thr->async.in_tail = NULL;
|
||||
thr->async.out_head = thr->async.out_tail = NULL;
|
||||
}
|
||||
|
||||
bool video_thread_texture_load_async(void *img,
|
||||
enum texture_filter_type filter,
|
||||
video_thread_async_done_t done, void *user,
|
||||
video_thread_async_release_t release)
|
||||
{
|
||||
video_driver_state_t *video_st = video_state_get_ptr();
|
||||
thread_video_t *thr;
|
||||
video_thread_async_load_t *n;
|
||||
|
||||
if (!video_st->thread_wrapper_active || !img)
|
||||
return false;
|
||||
thr = (thread_video_t*)video_st->data;
|
||||
if (!thr || !thr->thread)
|
||||
return false;
|
||||
/* On the video thread there is nothing to hand off to. */
|
||||
if (sthread_get_thread_id(thr->thread) == sthread_get_current_thread_id())
|
||||
return false;
|
||||
if (!(n = (video_thread_async_load_t*)calloc(1, sizeof(*n))))
|
||||
return false;
|
||||
|
||||
n->img = img;
|
||||
n->user = user;
|
||||
n->done = done;
|
||||
n->release = release;
|
||||
n->filter = filter;
|
||||
|
||||
slock_lock(thr->lock);
|
||||
if (!thr->alive)
|
||||
{
|
||||
slock_unlock(thr->lock);
|
||||
free(n);
|
||||
return false;
|
||||
}
|
||||
if (thr->async.in_tail)
|
||||
thr->async.in_tail->next = n;
|
||||
else
|
||||
thr->async.in_head = n;
|
||||
thr->async.in_tail = n;
|
||||
scond_signal(thr->cond_thread);
|
||||
slock_unlock(thr->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
void video_thread_async_poll(void)
|
||||
{
|
||||
video_driver_state_t *video_st = video_state_get_ptr();
|
||||
thread_video_t *thr;
|
||||
if (!video_st->thread_wrapper_active)
|
||||
return;
|
||||
thr = (thread_video_t*)video_st->data;
|
||||
if (!thr)
|
||||
return;
|
||||
if (sthread_get_thread_id(thr->thread) == sthread_get_current_thread_id())
|
||||
return;
|
||||
video_thread_async_deliver(thr);
|
||||
}
|
||||
|
||||
static void video_thread_loop(void *data)
|
||||
{
|
||||
thread_packet_t pkt;
|
||||
@@ -736,7 +880,9 @@ static void video_thread_loop(void *data)
|
||||
bool repeat_due = false;
|
||||
|
||||
slock_lock(thr->lock);
|
||||
while (thr->send_cmd == CMD_VIDEO_NONE && !thr->frame.pending)
|
||||
while ( thr->send_cmd == CMD_VIDEO_NONE
|
||||
&& !thr->frame.pending
|
||||
&& !thr->async.in_head)
|
||||
{
|
||||
/* With a frame retained, the wait has a deadline: the next
|
||||
* display period after the last present. Passing it with
|
||||
@@ -786,6 +932,8 @@ static void video_thread_loop(void *data)
|
||||
if (have_cmd && video_thread_handle_packet(thr, &pkt))
|
||||
return;
|
||||
|
||||
video_thread_async_run(thr);
|
||||
|
||||
if (claimed)
|
||||
{
|
||||
struct video_viewport vp;
|
||||
@@ -1113,6 +1261,10 @@ static bool video_thread_frame(void *data, const void *frame_,
|
||||
if (!thr)
|
||||
return false;
|
||||
|
||||
/* Asynchronous uploads that finished since the last frame reach
|
||||
* their owners before the frame that may draw with them. */
|
||||
video_thread_async_deliver(thr);
|
||||
|
||||
/* If called from within read_viewport, we're actually in the
|
||||
* driver thread, so just render directly. */
|
||||
if (thr->frame.within_thread)
|
||||
@@ -1604,6 +1756,7 @@ static void video_thread_free(void *data)
|
||||
if (thr->driver_data && thr->driver && thr->driver->free)
|
||||
thr->driver->free(thr->driver_data);
|
||||
}
|
||||
video_thread_async_drop_all(thr);
|
||||
|
||||
/* After the join, not before it: the video thread reads this
|
||||
* from inside driver frame callbacks, so clearing it while that
|
||||
|
||||
@@ -183,9 +183,33 @@ typedef struct thread_packet
|
||||
enum thread_cmd type;
|
||||
} thread_packet_t;
|
||||
|
||||
/* A texture upload the main thread does not wait for. Queued nodes
|
||||
* are owned by the video thread from post to completion; completed
|
||||
* nodes wait in the out list until the main thread delivers them from
|
||||
* video_thread_async_poll(). Both lists are guarded by thr->lock. */
|
||||
typedef void (*video_thread_async_done_t)(void *user, uintptr_t handle);
|
||||
typedef void (*video_thread_async_release_t)(void *img);
|
||||
|
||||
typedef struct video_thread_async_load
|
||||
{
|
||||
struct video_thread_async_load *next;
|
||||
void *img; /* struct texture_image*, ours until released */
|
||||
void *user;
|
||||
video_thread_async_done_t done;
|
||||
video_thread_async_release_t release;
|
||||
uintptr_t handle;
|
||||
enum texture_filter_type filter;
|
||||
} video_thread_async_load_t;
|
||||
|
||||
typedef struct thread_video
|
||||
{
|
||||
retro_time_t last_time;
|
||||
/* Asynchronous texture uploads, see video_thread_texture_load_async(). */
|
||||
struct
|
||||
{
|
||||
video_thread_async_load_t *in_head, *in_tail; /* to upload */
|
||||
video_thread_async_load_t *out_head, *out_tail; /* to deliver */
|
||||
} async;
|
||||
/* Presenter state, all owned by the video thread. present_period
|
||||
* is one display period in usec, taken from the refresh rate of the
|
||||
* last frame rendered; next_present is when a repeat of it falls
|
||||
@@ -470,6 +494,24 @@ bool video_thread_font_init(
|
||||
uintptr_t video_thread_texture_handle(void *data,
|
||||
custom_command_method_t func);
|
||||
|
||||
/* Upload @img on the video thread without blocking the caller. The
|
||||
* video thread runs the upload at its next wake, calls release(img),
|
||||
* and parks the handle; the main thread then gets done(user, handle)
|
||||
* from video_thread_async_poll(), which video_thread_frame() runs
|
||||
* every frame. If the wrapper is torn down first, in-flight loads are
|
||||
* released and delivered with handle 0. Returns false (and takes no
|
||||
* ownership) when the wrapper is not active - the caller does the
|
||||
* synchronous load instead. Main thread only. */
|
||||
bool video_thread_texture_load_async(void *img,
|
||||
enum texture_filter_type filter,
|
||||
video_thread_async_done_t done, void *user,
|
||||
video_thread_async_release_t release);
|
||||
|
||||
/* Deliver completed asynchronous uploads to their done() callbacks.
|
||||
* Main thread; video_thread_frame() calls it, callers that upload
|
||||
* while no frames are being pushed can call it themselves. */
|
||||
void video_thread_async_poll(void);
|
||||
|
||||
/* Barrier: wait until the video thread is idle (no pending frame).
|
||||
* Must be called from the main thread before freeing GPU resources
|
||||
* that an in-flight frame might reference. No-op on non-threaded
|
||||
|
||||
@@ -1218,6 +1218,177 @@ static void lane_zero_copy(void)
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Lane: asynchronous texture uploads */
|
||||
/* video_driver_texture_load_async() must return at once, upload on */
|
||||
/* the video thread, deliver done() on the main thread in post */
|
||||
/* order with the driver's handle, release the image exactly once, */
|
||||
/* and hand every in-flight load a 0 when the wrapper is torn down. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static video_driver_t async_driver;
|
||||
static const video_driver_t *async_inner;
|
||||
static video_poke_interface_t async_poke;
|
||||
static const video_poke_interface_t *async_inner_poke;
|
||||
static uintptr_t async_upload_thread;
|
||||
static unsigned async_uploads;
|
||||
|
||||
static uintptr_t async_fake_load(void *data, void *img, bool threaded,
|
||||
enum texture_filter_type filter)
|
||||
{
|
||||
(void)data; (void)img; (void)threaded; (void)filter;
|
||||
async_upload_thread = sthread_get_current_thread_id();
|
||||
return 0x1000 + ++async_uploads;
|
||||
}
|
||||
|
||||
static void async_fake_unload(void *data, bool threaded, uintptr_t id)
|
||||
{
|
||||
(void)data; (void)threaded; (void)id;
|
||||
}
|
||||
|
||||
static void async_get_poke(void *data, const video_poke_interface_t **iface)
|
||||
{
|
||||
async_inner->poke_interface(data, &async_inner_poke);
|
||||
async_poke = *async_inner_poke;
|
||||
async_poke.load_texture = async_fake_load;
|
||||
async_poke.unload_texture = async_fake_unload;
|
||||
*iface = &async_poke;
|
||||
}
|
||||
|
||||
#define ASYNC_N 6
|
||||
static unsigned async_done_order[ASYNC_N];
|
||||
static uintptr_t async_done_handle[ASYNC_N];
|
||||
static unsigned async_done_count;
|
||||
static unsigned async_released;
|
||||
static uintptr_t async_done_thread;
|
||||
|
||||
static void async_done_cb(void *user, uintptr_t handle)
|
||||
{
|
||||
unsigned idx = (unsigned)(uintptr_t)user;
|
||||
if (async_done_count < ASYNC_N)
|
||||
{
|
||||
async_done_order[async_done_count] = idx;
|
||||
async_done_handle[async_done_count] = handle;
|
||||
}
|
||||
async_done_count++;
|
||||
async_done_thread = sthread_get_current_thread_id();
|
||||
}
|
||||
|
||||
static void async_release_cb(void *img)
|
||||
{
|
||||
(void)img;
|
||||
async_released++;
|
||||
}
|
||||
|
||||
static void lane_async_texture_load(void)
|
||||
{
|
||||
unsigned had = failures;
|
||||
video_driver_state_t *video_st = video_state_get_ptr();
|
||||
thread_video_t *thr;
|
||||
static struct texture_image imgs[ASYNC_N];
|
||||
unsigned i;
|
||||
retro_time_t t0, t1;
|
||||
uintptr_t main_thread = sthread_get_current_thread_id();
|
||||
|
||||
set_threaded_via_setting(true);
|
||||
run_frames(3);
|
||||
expect_wrapper(true, "async lane");
|
||||
thr = (thread_video_t*)video_st->data;
|
||||
|
||||
video_thread_wait_idle();
|
||||
async_inner = thr->driver;
|
||||
async_driver = *thr->driver;
|
||||
async_driver.poke_interface = async_get_poke;
|
||||
thr->driver = &async_driver;
|
||||
async_driver.poke_interface(thr->driver_data, &thr->poke);
|
||||
/* video_driver_texture_load_async() goes through video_st->poke. */
|
||||
video_st->poke = thr->poke;
|
||||
|
||||
async_uploads = async_done_count = async_released = 0;
|
||||
for (i = 0; i < ASYNC_N; i++)
|
||||
{
|
||||
imgs[i].width = imgs[i].height = 4;
|
||||
imgs[i].pixels = (uint32_t*)&imgs[i];
|
||||
}
|
||||
|
||||
t0 = cpu_features_get_time_usec();
|
||||
for (i = 0; i < ASYNC_N; i++)
|
||||
CHECK(video_driver_texture_load_async(&imgs[i], TEXTURE_FILTER_LINEAR,
|
||||
async_done_cb, (void*)(uintptr_t)i, async_release_cb),
|
||||
"async load %u refused", i);
|
||||
t1 = cpu_features_get_time_usec();
|
||||
CHECK(t1 - t0 < 5000, "posting %u async loads took %lld us: it blocked",
|
||||
ASYNC_N, (long long)(t1 - t0));
|
||||
CHECK(async_done_count == 0, "done() ran before any frame was pushed");
|
||||
|
||||
run_frames(5);
|
||||
video_thread_wait_idle();
|
||||
run_frames(1);
|
||||
|
||||
CHECK(async_uploads == ASYNC_N, "%u of %u uploads reached the driver",
|
||||
async_uploads, ASYNC_N);
|
||||
CHECK(async_upload_thread != main_thread,
|
||||
"upload ran on the main thread");
|
||||
CHECK(async_released == ASYNC_N, "%u of %u images released",
|
||||
async_released, ASYNC_N);
|
||||
CHECK(async_done_count == ASYNC_N, "%u of %u done() calls",
|
||||
async_done_count, ASYNC_N);
|
||||
CHECK(async_done_thread == main_thread, "done() ran off the main thread");
|
||||
for (i = 0; i < ASYNC_N && i < async_done_count; i++)
|
||||
{
|
||||
CHECK(async_done_order[i] == i, "done() order: slot %u got load %u",
|
||||
i, async_done_order[i]);
|
||||
CHECK(async_done_handle[i] == 0x1000 + i + 1,
|
||||
"load %u delivered handle %lx", i, (unsigned long)async_done_handle[i]);
|
||||
}
|
||||
|
||||
/* Teardown with loads in flight: park the worker on a command so
|
||||
* the posts sit in the in-list, then drop threaded video. Every
|
||||
* one must be released and answered with 0, none twice. */
|
||||
async_done_count = async_released = 0;
|
||||
for (i = 0; i < ASYNC_N; i++)
|
||||
video_driver_texture_load_async(&imgs[i], TEXTURE_FILTER_LINEAR,
|
||||
async_done_cb, (void*)(uintptr_t)i, async_release_cb);
|
||||
thr->driver = async_inner;
|
||||
thr->poke = async_inner_poke;
|
||||
video_st->poke = async_inner_poke;
|
||||
set_threaded_via_setting(false);
|
||||
run_frames(2);
|
||||
CHECK(async_released == ASYNC_N, "teardown released %u of %u images",
|
||||
async_released, ASYNC_N);
|
||||
CHECK(async_done_count == ASYNC_N, "teardown answered %u of %u loads",
|
||||
async_done_count, ASYNC_N);
|
||||
|
||||
/* Without the wrapper the call is synchronous and still keeps the
|
||||
* contract: release, then done, before returning. The harness
|
||||
* driver has no load_texture, so lend it the fake one. */
|
||||
{
|
||||
const video_poke_interface_t *real_poke = video_st->poke;
|
||||
video_poke_interface_t sync_poke;
|
||||
if (real_poke)
|
||||
sync_poke = *real_poke;
|
||||
else
|
||||
memset(&sync_poke, 0, sizeof(sync_poke));
|
||||
sync_poke.load_texture = async_fake_load;
|
||||
sync_poke.unload_texture = async_fake_unload;
|
||||
video_st->poke = &sync_poke;
|
||||
async_done_count = async_released = 0;
|
||||
CHECK(video_driver_texture_load_async(&imgs[0], TEXTURE_FILTER_LINEAR,
|
||||
async_done_cb, (void*)0, async_release_cb),
|
||||
"synchronous fallback refused");
|
||||
CHECK(async_released == 1 && async_done_count == 1,
|
||||
"synchronous fallback: released %u, done %u",
|
||||
async_released, async_done_count);
|
||||
CHECK(async_done_thread == main_thread,
|
||||
"synchronous fallback: done() off the main thread");
|
||||
video_st->poke = real_poke;
|
||||
}
|
||||
|
||||
if (failures == had)
|
||||
fprintf(stderr, "[pass] async texture load lane (%u uploads)\n", ASYNC_N);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char cfg_path[512];
|
||||
@@ -1298,6 +1469,7 @@ int main(int argc, char *argv[])
|
||||
lane_reinit_under_wrapper(cycles / 2 + 1);
|
||||
lane_toggle_in_game(cycles);
|
||||
lane_swap_count();
|
||||
lane_async_texture_load();
|
||||
lane_present_repeat();
|
||||
lane_every_command_replies();
|
||||
lane_second_ring_waiter();
|
||||
|
||||
+42
-2
@@ -1144,6 +1144,40 @@ typedef struct
|
||||
uint64_t *generation_ptr; /* pointer to the STATIC gen counter */
|
||||
} icon_load_tag_t;
|
||||
|
||||
static void icon_image_release(void *img)
|
||||
{
|
||||
struct texture_image *ti = (struct texture_image*)img;
|
||||
if (ti)
|
||||
{
|
||||
image_texture_free(ti);
|
||||
free(ti);
|
||||
}
|
||||
}
|
||||
|
||||
/* Main thread, when the upload has a handle (or failed with 0). The
|
||||
* generation check lives here, not at queue time: the target may have
|
||||
* been freed while the upload was in flight. A stale result, or one
|
||||
* for a target that already got a newer handle, is unloaded rather
|
||||
* than leaked. */
|
||||
static void icon_load_done(void *user, uintptr_t handle)
|
||||
{
|
||||
icon_load_tag_t *tag = (icon_load_tag_t*)user;
|
||||
if (!tag)
|
||||
return;
|
||||
if (tag->generation != *tag->generation_ptr)
|
||||
{
|
||||
if (handle)
|
||||
video_driver_texture_unload(&handle);
|
||||
}
|
||||
else if (handle)
|
||||
{
|
||||
if (*tag->target)
|
||||
video_driver_texture_unload(tag->target);
|
||||
*tag->target = handle;
|
||||
}
|
||||
free(tag);
|
||||
}
|
||||
|
||||
static void cb_task_icon_load(retro_task_t *task,
|
||||
void *task_data, void *user_data, const char *error)
|
||||
{
|
||||
@@ -1163,8 +1197,14 @@ static void cb_task_icon_load(retro_task_t *task,
|
||||
if (!img || img->width < 1 || img->height < 1 || !img->pixels)
|
||||
goto end;
|
||||
|
||||
video_driver_texture_load(img, gfx_display_texture_filter(),
|
||||
tag->target);
|
||||
/* Under threaded video this used to be a blocking round trip to
|
||||
* the video thread per icon - up to one present each, on the main
|
||||
* thread, dozens of times at startup. The upload now goes to the
|
||||
* video thread's queue and the handle comes back through
|
||||
* icon_load_done() at a later frame; img and tag are theirs now. */
|
||||
if (video_driver_texture_load_async(img, gfx_display_texture_filter(),
|
||||
icon_load_done, tag, icon_image_release))
|
||||
return;
|
||||
|
||||
end:
|
||||
if (img)
|
||||
|
||||
Reference in new issue
Block a user