While copying a descriptor, the source can be rewritten with a new descriptor by another thread without causing invalid behavior in Windows. In vkd3d this can result in transparent freeing of a vkd3d_view object in the source descriptor, so the copy gets a pointer to a deallocated object. Acquisition of a reference to a view object must be protected with a mutex. This enables SotTR to run, and causes little or no impact to performance in Metro Exodus, which doesn't require it.
Signed-off-by: Conor McCarthy cmccarthy@codeweavers.com --- libs/vkd3d/device.c | 3 +++ libs/vkd3d/resource.c | 43 ++++++++++++++++++++++++++++++++++---- libs/vkd3d/vkd3d_private.h | 1 + 3 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/libs/vkd3d/device.c b/libs/vkd3d/device.c index dc0fa0a..f76ae30 100644 --- a/libs/vkd3d/device.c +++ b/libs/vkd3d/device.c @@ -2006,6 +2006,7 @@ static ULONG STDMETHODCALLTYPE d3d12_device_Release(ID3D12Device *iface) vkd3d_fence_worker_stop(&device->fence_worker, device); d3d12_device_destroy_pipeline_cache(device); d3d12_device_destroy_vkd3d_queues(device); + pthread_mutex_destroy(&device->desc_mutex); VK_CALL(vkDestroyDevice(device->vk_device, NULL)); if (device->parent) IUnknown_Release(device->parent); @@ -3237,6 +3238,8 @@ static HRESULT d3d12_device_init(struct d3d12_device *device, vkd3d_render_pass_cache_init(&device->render_pass_cache); vkd3d_gpu_va_allocator_init(&device->gpu_va_allocator);
+ pthread_mutex_init(&device->desc_mutex, NULL); + if ((device->parent = create_info->parent)) IUnknown_AddRef(device->parent);
diff --git a/libs/vkd3d/resource.c b/libs/vkd3d/resource.c index 463f373..28f047d 100644 --- a/libs/vkd3d/resource.c +++ b/libs/vkd3d/resource.c @@ -1938,10 +1938,27 @@ static void d3d12_desc_destroy(struct d3d12_desc *descriptor, || descriptor->magic == VKD3D_DESCRIPTOR_MAGIC_UAV || descriptor->magic == VKD3D_DESCRIPTOR_MAGIC_SAMPLER) { - vkd3d_view_decref_descriptor(descriptor->u.view, descriptor, device); + /* Shadow of the Tomb Raider and possibly other titles sometimes destroy + * and rewrite a descriptor in another thread while it is being copied. */ + pthread_mutex_lock(&device->desc_mutex); + if (!descriptor->u.view) + { + /* Destroyed in another thread. */ + pthread_mutex_unlock(&device->desc_mutex); + } + else + { + vkd3d_view_decref_descriptor(descriptor->u.view, descriptor, device); + /* Another thread may free the view after the mutex is unlocked, so zero the + * descriptor while locked. This has no measurable effect on performance. */ + memset(descriptor, 0, sizeof(*descriptor)); + pthread_mutex_unlock(&device->desc_mutex); + } + } + else + { + memset(descriptor, 0, sizeof(*descriptor)); } - - memset(descriptor, 0, sizeof(*descriptor)); }
void d3d12_desc_copy(struct d3d12_desc *dst, const struct d3d12_desc *src, @@ -1951,13 +1968,31 @@ void d3d12_desc_copy(struct d3d12_desc *dst, const struct d3d12_desc *src,
d3d12_desc_destroy(dst, device);
+ /* Try to do as much as possible while unlocked to allow multithreading. */ *dst = *src;
if (src->magic == VKD3D_DESCRIPTOR_MAGIC_SRV || src->magic == VKD3D_DESCRIPTOR_MAGIC_UAV || src->magic == VKD3D_DESCRIPTOR_MAGIC_SAMPLER) { - vkd3d_view_incref(src->u.view); + pthread_mutex_lock(&device->desc_mutex); + if (src->u.view == dst->u.view) + { + /* Hopefully this happens most of the time. */ + vkd3d_view_incref(src->u.view); + pthread_mutex_unlock(&device->desc_mutex); + } + else + { + /* The source was changed while we waited for the lock. Get a copy of the new descriptor. + * (See comments in d3d12_desc_destroy()). */ + *dst = *src; + if (src->magic == VKD3D_DESCRIPTOR_MAGIC_SRV + || src->magic == VKD3D_DESCRIPTOR_MAGIC_UAV + || src->magic == VKD3D_DESCRIPTOR_MAGIC_SAMPLER) + vkd3d_view_incref(src->u.view); + pthread_mutex_unlock(&device->desc_mutex); + } } }
diff --git a/libs/vkd3d/vkd3d_private.h b/libs/vkd3d/vkd3d_private.h index 294e677..0cfd1af 100644 --- a/libs/vkd3d/vkd3d_private.h +++ b/libs/vkd3d/vkd3d_private.h @@ -1052,6 +1052,7 @@ struct d3d12_device struct vkd3d_fence_worker fence_worker;
pthread_mutex_t mutex; + pthread_mutex_t desc_mutex; struct vkd3d_render_pass_cache render_pass_cache; VkPipelineCache vk_pipeline_cache;