diff --git a/include/private/vkd3d_threads.h b/include/private/vkd3d_threads.h index 98ea074b2c..16ed30131e 100644 --- a/include/private/vkd3d_threads.h +++ b/include/private/vkd3d_threads.h @@ -38,6 +38,7 @@ struct pthread HMODULE d3d12core_reference; HMODULE dxgi_reference; + LONG refcount; }; typedef struct pthread *pthread_t; @@ -80,10 +81,16 @@ static DWORD WINAPI win32_thread_wrapper_routine(void *arg) /* We are executing in d3d12core.dll here, so we have to use the atomic FreeLibraryAndExit thread to make this work. */ if (thread->d3d12core_reference) { + HMODULE module_ref = thread->d3d12core_reference; TRACE("Releasing module reference for d3d12core.dll and exiting thread: %p.\n", thread->d3d12core_reference); - FreeLibraryAndExitThread(thread->d3d12core_reference, 0); + if (!InterlockedDecrement(&thread->refcount)) + vkd3d_free(thread); + FreeLibraryAndExitThread(module_ref, 0); } + if (!InterlockedDecrement(&thread->refcount)) + vkd3d_free(thread); + /* Otherwise, fall back to the implicit ExitThread(). */ return 0; } @@ -97,6 +104,7 @@ static inline int pthread_create(pthread_t *out_thread, void *attr, void * (*thr (void)attr; thread->routine = thread_fun; thread->arg = arg; + thread->refcount = 2; /* Need GetModuleHandleEx which lets us get a refcount. */ if (!GetModuleHandleExA(0, "d3d12core.dll", &thread->d3d12core_reference)) @@ -134,6 +142,14 @@ static inline int pthread_join(pthread_t thread, void **ret) return success ? 0 : -1; } +static inline int pthread_detach(pthread_t thread) +{ + CloseHandle(thread->thread); + if (!InterlockedDecrement(&thread->refcount)) + vkd3d_free(thread); + return 0; +} + static inline int pthread_mutex_init(pthread_mutex_t *lock, void *attr) { (void)attr; diff --git a/include/vkd3d.h b/include/vkd3d.h index 575caf8fd1..a4658c9eb7 100644 --- a/include/vkd3d.h +++ b/include/vkd3d.h @@ -108,6 +108,7 @@ extern "C" { #define VKD3D_CONFIG_FLAG_NO_STAGGERED_SUBMIT (1ull << 50) #define VKD3D_CONFIG_FLAG_CLEAR_UAV_SYNC (1ull << 51) #define VKD3D_CONFIG_FLAG_FORCE_DYNAMIC_MSAA (1ull << 52) +#define VKD3D_CONFIG_FLAG_DELAY_DEVICE_DESTRUCTION (1ull << 53) struct vkd3d_instance; diff --git a/libs/vkd3d/device.c b/libs/vkd3d/device.c index f4b6853401..14873023b2 100644 --- a/libs/vkd3d/device.c +++ b/libs/vkd3d/device.c @@ -615,6 +615,10 @@ static const struct vkd3d_instance_application_meta application_override[] = { { VKD3D_STRING_COMPARE_STARTS_WITH, "tlou-i", VKD3D_CONFIG_FLAG_NO_STAGGERED_SUBMIT, 0 }, /* Skull and Bones (2853730). Seems to require unsupported dcomp when reflex is enabled for some reason *shrug */ { VKD3D_STRING_COMPARE_EXACT, "skullandbones.exe", 0, 0, VKD3D_APPLICATION_FEATURE_DISABLE_NV_REFLEX }, + /* Test Drive Unlimited Solar Crown (1249970). + * Device releases ID3D12Device too many times, leading to subsequent crash on fence release. + * Appears to hang on Windows though, so ... */ + { VKD3D_STRING_COMPARE_EXACT, "TDUSC.exe", VKD3D_CONFIG_FLAG_DELAY_DEVICE_DESTRUCTION, 0 }, /* Unreal Engine catch-all. ReBAR is a massive uplift on RX 7600 for example in Wukong. * AMD windows drivers also seem to have some kind of general app-opt for UE titles. * Use no-staggered-submit by default on UE. We've only observed issues in Wukong here, but @@ -3531,6 +3535,20 @@ ULONG d3d12_device_add_ref_common(struct d3d12_device *device) return InterlockedIncrement(&device->refcount); } +static void *device_destroy_thread(void *args) +{ + struct d3d12_device *device = args; +#ifdef _WIN32 + Sleep(1000); +#else + sleep(1); +#endif + INFO("Destroying device in a delayed way.\n"); + d3d12_device_destroy(device); + vkd3d_free_aligned(device); + return NULL; +} + ULONG d3d12_device_release_common(struct d3d12_device *device) { ULONG cur_refcount, cas_refcount; @@ -3559,8 +3577,26 @@ ULONG d3d12_device_release_common(struct d3d12_device *device) if (cur_refcount == 1) { d3d12_remove_device_singleton(device->adapter_luid); - d3d12_device_destroy(device); - vkd3d_free_aligned(device); + + if (vkd3d_config_flags & VKD3D_CONFIG_FLAG_DELAY_DEVICE_DESTRUCTION) + { + pthread_t thr; + INFO("Device hit 0 ref-count, but deferring destruction due to config flag.\n"); + /* Workaround buggy games that release too many device references before all child objects + * are destroyed. This crashes native Windows too. */ + if (pthread_create(&thr, NULL, device_destroy_thread, device)) + { + d3d12_device_destroy(device); + vkd3d_free_aligned(device); + } + else + pthread_detach(thr); + } + else + { + d3d12_device_destroy(device); + vkd3d_free_aligned(device); + } } if (is_locked)