mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-10 03:21:30 -10:00
D3D11: Route texture load/unload through video thread when threaded video active
The D3D11 driver had two threading issues when used with the
threaded video wrapper. Neither was specific to D3D11 as an API
-- they mirror the issues already fixed in D3D12 and Vulkan --
but D3D11 is actually the most severe case because its
ImmediateContext is explicitly documented as not thread-safe.
Race 1: ImmediateContext concurrent access
------------------------------------------
ID3D11DeviceContext (the immediate context used for rendering)
is NOT thread-safe -- Microsoft's documentation explicitly says
concurrent method calls produce undefined behaviour. RetroArch
holds a single d3d11->context pointer used for all rendering.
When threaded video is active:
- The video thread calls d3d11_gfx_frame which issues draw
commands via d3d11->context (OMSet*, VSSet*, PSSet*,
Draw, DrawIndexed, etc.).
- The main thread concurrently calls d3d11_gfx_load_texture,
which reaches d3d11_update_texture and calls context
methods: Map, Unmap, CopySubresourceRegion, GenerateMips.
These two code paths race on the same ImmediateContext. On
current D3D11 runtimes this typically does not crash outright
because the runtime has some internal locking for validation,
but correct behaviour is not guaranteed and we're relying on
an implementation detail.
Race 2: Release while GPU has pending references
------------------------------------------------
d3d11_gfx_unload_texture calls Release on the COM objects.
COM refcounting is thread-safe but does not block on GPU
completion. If the ImmediateContext has queued a draw that
references the texture (and the GPU hasn't processed it yet),
the Release can free the resource before the GPU is done.
Fix: follow the pattern established in D3D12 (a2d7b48) and
Vulkan (17332fe) -- dispatch load and unload to the video
thread via video_thread_texture_handle when threaded video is
active. This serialises all ImmediateContext access on the
video thread, and ensures Release runs only after any pending
draw that might reference the texture has been queued (the
video thread cannot be mid-recording when it handles the
dispatched command).
Implementation:
- Split d3d11_gfx_load_texture into internal / outer functions.
Same for unload.
- New d3d11_texture_cmd_t packages the parameters, with handle
used for both input (unload) and output (load).
- Return handle via cmd->handle to avoid truncation of 64-bit
uintptr_t through custom_command_method_t's int return.
- Non-threaded path is unchanged -- calls the internal function
directly.
Depends on: 8d55232 (VIDEO/THREAD: barrier + wrapper hardening).
No behavioural change on non-threaded video. No behavioural
change on HW-context cores (threaded video is disabled for
those by VIDEO_DRIVER_IS_THREADED_INTERNAL).
This commit is contained in:
1 parent
a88f1076b8
commit
61b0a1accc
1 file changed
+100
-7
+100
-7
@@ -71,6 +71,9 @@
|
||||
#ifdef HAVE_SLANG
|
||||
#include "../drivers_shader/slang_process.h"
|
||||
#endif
|
||||
#ifdef HAVE_THREADS
|
||||
#include "../video_thread_wrapper.h"
|
||||
#endif
|
||||
|
||||
#ifdef __WINRT__
|
||||
#include "../../uwp/uwp_func.h"
|
||||
@@ -5204,12 +5207,24 @@ static void d3d11_gfx_set_osd_msg(
|
||||
font_driver_render_msg(d3d11, msg, params, font);
|
||||
}
|
||||
|
||||
static uintptr_t d3d11_gfx_load_texture(
|
||||
void* video_data, void* data, bool threaded, enum texture_filter_type filter_type)
|
||||
#ifdef HAVE_THREADS
|
||||
typedef struct
|
||||
{
|
||||
d3d11_texture_t* texture = NULL;
|
||||
d3d11_video_t* d3d11 = (d3d11_video_t*)video_data;
|
||||
struct texture_image* image = (struct texture_image*)data;
|
||||
d3d11_video_t *d3d11;
|
||||
struct texture_image *image;
|
||||
enum texture_filter_type filter_type;
|
||||
uintptr_t handle;
|
||||
} d3d11_texture_cmd_t;
|
||||
#endif
|
||||
|
||||
/* Inner load function -- performs all DeviceContext-touching work.
|
||||
* Must run on the same thread that owns the D3D11 immediate
|
||||
* context (ID3D11DeviceContext is NOT thread-safe). */
|
||||
static uintptr_t d3d11_gfx_load_texture_internal(
|
||||
d3d11_video_t* d3d11, struct texture_image* image,
|
||||
enum texture_filter_type filter_type)
|
||||
{
|
||||
d3d11_texture_t* texture = NULL;
|
||||
|
||||
if (!d3d11)
|
||||
return 0;
|
||||
@@ -5254,8 +5269,12 @@ static uintptr_t d3d11_gfx_load_texture(
|
||||
return (uintptr_t)texture;
|
||||
}
|
||||
|
||||
static void d3d11_gfx_unload_texture(void* data,
|
||||
bool threaded, uintptr_t handle)
|
||||
/* Inner unload -- releases COM objects. While COM Release is
|
||||
* itself thread-safe, doing it on a resource the GPU is still
|
||||
* referencing via the ImmediateContext's pending command stream
|
||||
* can cause the GPU to access freed memory. Running unload on
|
||||
* the video thread serialises it with the context's draw calls. */
|
||||
static void d3d11_gfx_unload_texture_internal(uintptr_t handle)
|
||||
{
|
||||
d3d11_texture_t* texture = (d3d11_texture_t*)handle;
|
||||
|
||||
@@ -5268,6 +5287,80 @@ static void d3d11_gfx_unload_texture(void* data,
|
||||
free(texture);
|
||||
}
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
static int d3d11_texture_load_wrap(void *data)
|
||||
{
|
||||
d3d11_texture_cmd_t *cmd = (d3d11_texture_cmd_t*)data;
|
||||
cmd->handle = d3d11_gfx_load_texture_internal(
|
||||
cmd->d3d11, cmd->image, cmd->filter_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int d3d11_texture_unload_wrap(void *data)
|
||||
{
|
||||
d3d11_texture_cmd_t *cmd = (d3d11_texture_cmd_t*)data;
|
||||
d3d11_gfx_unload_texture_internal(cmd->handle);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static uintptr_t d3d11_gfx_load_texture(
|
||||
void* video_data, void* data, bool threaded, enum texture_filter_type filter_type)
|
||||
{
|
||||
d3d11_video_t* d3d11 = (d3d11_video_t*)video_data;
|
||||
struct texture_image* image = (struct texture_image*)data;
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
/* ID3D11DeviceContext is NOT thread-safe. When threaded
|
||||
* video is active, d3d11_gfx_frame runs on the video thread
|
||||
* and submits draws via d3d11->context. If the main thread
|
||||
* concurrently calls context methods (Map/Unmap/
|
||||
* CopySubresourceRegion) from load_texture, the resulting
|
||||
* concurrent access is undefined behaviour.
|
||||
*
|
||||
* Dispatch to the video thread so all context access is
|
||||
* serialised on a single thread. */
|
||||
if (threaded)
|
||||
{
|
||||
d3d11_texture_cmd_t cmd;
|
||||
cmd.d3d11 = d3d11;
|
||||
cmd.image = image;
|
||||
cmd.filter_type = filter_type;
|
||||
cmd.handle = 0;
|
||||
video_thread_texture_handle(&cmd, d3d11_texture_load_wrap);
|
||||
return cmd.handle;
|
||||
}
|
||||
#endif
|
||||
|
||||
return d3d11_gfx_load_texture_internal(d3d11, image, filter_type);
|
||||
}
|
||||
|
||||
static void d3d11_gfx_unload_texture(void* data,
|
||||
bool threaded, uintptr_t handle)
|
||||
{
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
/* Same reasoning as load: serialise with the video thread's
|
||||
* use of the ImmediateContext so Release does not free a
|
||||
* resource while the context still has pending commands
|
||||
* referencing it. */
|
||||
if (threaded)
|
||||
{
|
||||
d3d11_texture_cmd_t cmd;
|
||||
cmd.d3d11 = (d3d11_video_t*)data;
|
||||
cmd.image = NULL;
|
||||
cmd.filter_type = TEXTURE_FILTER_LINEAR;
|
||||
cmd.handle = handle;
|
||||
video_thread_texture_handle(&cmd, d3d11_texture_unload_wrap);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
d3d11_gfx_unload_texture_internal(handle);
|
||||
}
|
||||
|
||||
static bool d3d11_get_hw_render_interface(
|
||||
void* data, const struct retro_hw_render_interface** iface)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user