From a88f1076b89b5594de5f0e5357b992a451b7ea10 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Thu, 16 Apr 2026 13:57:48 +0200 Subject: [PATCH] D3D10: Route texture load/unload through video thread when threaded video active Follow-up to the D3D11 thread-safety patch. D3D10 has the same texture load/unload pattern as D3D11 (immediate CopySubresourceRegion during load, direct Release during unload) and therefore the same race classes when used with the threaded video wrapper. D3D10 is slightly less severe than D3D11 in one respect: the ID3D10Device is thread-safe by default (we do not set D3D10_CREATE_DEVICE_SINGLETHREADED at creation), so concurrent Device method calls from the main thread and video thread are serialised by an internal D3D10 mutex. This prevents outright undefined behaviour on the context methods themselves. However, the second race remains and is not mitigated by the internal mutex: Resource Release vs pending GPU work ------------------------------------ d3d10_update_texture during load_texture queues an asynchronous CopySubresourceRegion on the Device. d3d10_gfx_unload_texture calls Release with no fence or wait -- if the GPU hasn't finished processing pending commands that reference the texture, the resource can be freed while still in use. Fix: mirror the D3D11 patch. When threaded video is active, dispatch both load and unload to the video thread via video_thread_texture_handle so all texture operations are serialised with the video thread's draws. This also consolidates Device context access to a single thread even though the internal mutex already makes it correct -- reducing contention on that mutex. Implementation is structurally identical to the D3D11 patch: - d3d10_gfx_load_texture_internal / d3d10_gfx_unload_texture_internal contain the original logic. - Outer functions dispatch via video_thread_texture_handle when threaded is true, otherwise call the internal function directly. - d3d10_texture_cmd_t packages parameters and carries the handle field for both input (unload) and output (load). Depends on: 8d55232 (VIDEO/THREAD: barrier + wrapper hardening) and the D3D11 thread-safety patch (applied immediately before for structural consistency, though technically independent). No behavioural change on non-threaded video. --- gfx/drivers/d3d10.c | 99 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 92 insertions(+), 7 deletions(-) diff --git a/gfx/drivers/d3d10.c b/gfx/drivers/d3d10.c index 4a5517fe8b..cedf977bbb 100644 --- a/gfx/drivers/d3d10.c +++ b/gfx/drivers/d3d10.c @@ -63,6 +63,9 @@ #ifdef HAVE_GFX_WIDGETS #include "../gfx_widgets.h" #endif +#ifdef HAVE_THREADS +#include "../video_thread_wrapper.h" +#endif #ifdef __WINRT__ #error "UWP does not support D3D10" @@ -3595,13 +3598,27 @@ static void d3d10_gfx_set_osd_msg( } } -static uintptr_t d3d10_gfx_load_texture( - void* video_data, void* data, bool threaded, +#ifdef HAVE_THREADS +typedef struct +{ + d3d10_video_t *d3d10; + struct texture_image *image; + enum texture_filter_type filter_type; + uintptr_t handle; +} d3d10_texture_cmd_t; +#endif + +/* Inner load -- performs all Device-context-touching work. + * D3D10 serialises Device calls via an internal mutex (we do + * not set D3D10_CREATE_DEVICE_SINGLETHREADED), but the texture + * upload path calls CopySubresourceRegion which queues + * asynchronous GPU work. Serialising on the video thread + * prevents resource-lifetime races with concurrent draws. */ +static uintptr_t d3d10_gfx_load_texture_internal( + d3d10_video_t* d3d10, struct texture_image* image, enum texture_filter_type filter_type) { - d3d10_texture_t* texture = NULL; - d3d10_video_t* d3d10 = (d3d10_video_t*)video_data; - struct texture_image* image = (struct texture_image*)data; + d3d10_texture_t* texture = NULL; if (!d3d10) return 0; @@ -3643,8 +3660,11 @@ static uintptr_t d3d10_gfx_load_texture( return (uintptr_t)texture; } -static void d3d10_gfx_unload_texture(void* data, - bool threaded, uintptr_t handle) + +/* Inner unload -- COM Release only. Refcounting is + * thread-safe but does not block on GPU completion. Running + * on the video thread serialises with pending draws. */ +static void d3d10_gfx_unload_texture_internal(uintptr_t handle) { d3d10_texture_t* texture = (d3d10_texture_t*)handle; @@ -3657,6 +3677,71 @@ static void d3d10_gfx_unload_texture(void* data, free(texture); } +#ifdef HAVE_THREADS +static int d3d10_texture_load_wrap(void *data) +{ + d3d10_texture_cmd_t *cmd = (d3d10_texture_cmd_t*)data; + cmd->handle = d3d10_gfx_load_texture_internal( + cmd->d3d10, cmd->image, cmd->filter_type); + return 0; +} + +static int d3d10_texture_unload_wrap(void *data) +{ + d3d10_texture_cmd_t *cmd = (d3d10_texture_cmd_t*)data; + d3d10_gfx_unload_texture_internal(cmd->handle); + return 0; +} +#endif + +static uintptr_t d3d10_gfx_load_texture( + void* video_data, void* data, bool threaded, + enum texture_filter_type filter_type) +{ + d3d10_video_t* d3d10 = (d3d10_video_t*)video_data; + struct texture_image* image = (struct texture_image*)data; + +#ifdef HAVE_THREADS + /* When threaded video is active, dispatch to the video + * thread to serialise Device context access and GPU + * resource lifetime with the video thread's draws. */ + if (threaded) + { + d3d10_texture_cmd_t cmd; + cmd.d3d10 = d3d10; + cmd.image = image; + cmd.filter_type = filter_type; + cmd.handle = 0; + video_thread_texture_handle(&cmd, d3d10_texture_load_wrap); + return cmd.handle; + } +#endif + + return d3d10_gfx_load_texture_internal(d3d10, image, filter_type); +} + +static void d3d10_gfx_unload_texture(void* data, + bool threaded, uintptr_t handle) +{ + if (!handle) + return; + +#ifdef HAVE_THREADS + if (threaded) + { + d3d10_texture_cmd_t cmd; + cmd.d3d10 = (d3d10_video_t*)data; + cmd.image = NULL; + cmd.filter_type = TEXTURE_FILTER_LINEAR; + cmd.handle = handle; + video_thread_texture_handle(&cmd, d3d10_texture_unload_wrap); + return; + } +#endif + + d3d10_gfx_unload_texture_internal(handle); +} + #if 0 static bool d3d10_get_hw_render_interface(void* data,