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.
This commit is contained in:
U-DESKTOP-SPFP6AQ\twistedtechre committed 2026-04-16 13:57:48 +02:00
1 parent 17332fe245
commit a88f1076b8
1 file changed
+92 -7
+92 -7
View File
@@ -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,