[PATCH v2 0/7] MR11535: wined3d: VA hardware decode support.
-- v2: wined3d: Implement VA H.264 decoding. wined3d: Share VA surfaces with Vulkan. wined3d: Create a VA decoding session. https://gitlab.winehq.org/wine/wine/-/merge_requests/11535
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/wined3d/adapter_vk.c | 8 +++- dlls/wined3d/decoder.c | 67 ++++++++++++++++++++++++++++++++++ dlls/wined3d/wined3d_main.c | 14 +++++++ dlls/wined3d/wined3d_private.h | 9 +++++ 4 files changed, 97 insertions(+), 1 deletion(-) diff --git a/dlls/wined3d/adapter_vk.c b/dlls/wined3d/adapter_vk.c index 7f68edb3651..a88faf2c9ce 100644 --- a/dlls/wined3d/adapter_vk.c +++ b/dlls/wined3d/adapter_vk.c @@ -2608,7 +2608,13 @@ static BOOL wined3d_adapter_vk_init(struct wined3d_adapter_vk *adapter_vk, adapter->fragment_pipe = wined3d_spirv_fragment_pipe_init_vk(); adapter->misc_state_template = misc_state_template_vk; adapter->shader_backend = wined3d_spirv_shader_backend_init_vk(); - adapter->decoder_ops = &wined3d_decoder_vk_ops; + + if (wined3d_settings.decoder_backend == WINED3D_DECODER_BACKEND_VA + || (wined3d_settings.decoder_backend == WINED3D_DECODER_BACKEND_AUTO + && !vk_info->supported[WINED3D_VK_KHR_VIDEO_DECODE_H264])) + adapter->decoder_ops = &wined3d_decoder_va_vk_ops; + else + adapter->decoder_ops = &wined3d_decoder_vk_ops; wined3d_adapter_vk_init_d3d_info(adapter_vk, wined3d_creation_flags); diff --git a/dlls/wined3d/decoder.c b/dlls/wined3d/decoder.c index ef37fe05f0f..18f987b6ed6 100644 --- a/dlls/wined3d/decoder.c +++ b/dlls/wined3d/decoder.c @@ -1354,6 +1354,73 @@ const struct wined3d_decoder_ops wined3d_decoder_vk_ops = .decode = wined3d_decoder_vk_decode, }; +struct wined3d_decoder_va_vk +{ + struct wined3d_decoder d; +}; + +static struct wined3d_decoder_va_vk *wined3d_decoder_va_vk(struct wined3d_decoder *decoder) +{ + return CONTAINING_RECORD(decoder, struct wined3d_decoder_va_vk, d); +} + +static void wined3d_decoder_va_vk_get_profiles(struct wined3d_adapter *adapter, unsigned int *count, GUID *profiles) +{ + *count = 0; +} + +static HRESULT wined3d_decoder_va_vk_create(struct wined3d_device *device, + const struct wined3d_decoder_desc *desc, struct wined3d_decoder **decoder) +{ + struct wined3d_decoder_va_vk *object; + HRESULT hr; + + if (!(object = calloc(1, sizeof(*object)))) + return E_OUTOFMEMORY; + if (FAILED(hr = wined3d_decoder_init(&object->d, device, desc))) + { + free(object); + return hr; + } + + TRACE("Created decoder %p.\n", object); + *decoder = &object->d; + + return WINED3D_OK; +} + +static void wined3d_decoder_va_vk_destroy_object(void *object) +{ + struct wined3d_decoder_va_vk *decoder_va = object; + + TRACE("decoder_va %p.\n", decoder_va); + + free(decoder_va); +} + +static void wined3d_decoder_va_vk_destroy(struct wined3d_decoder *decoder) +{ + struct wined3d_decoder_va_vk *decoder_va = wined3d_decoder_va_vk(decoder); + + wined3d_decoder_cleanup(&decoder_va->d); + wined3d_cs_destroy_object(decoder->device->cs, wined3d_decoder_va_vk_destroy_object, decoder_va); +} + +static void wined3d_decoder_va_vk_decode(struct wined3d_context *context, + struct wined3d_decoder *decoder, struct wined3d_decoder_output_view *output_view, + unsigned int bitstream_size, unsigned int slice_control_size) +{ + FIXME("Not implemented.\n"); +} + +const struct wined3d_decoder_ops wined3d_decoder_va_vk_ops = +{ + .get_profiles = wined3d_decoder_va_vk_get_profiles, + .create = wined3d_decoder_va_vk_create, + .destroy = wined3d_decoder_va_vk_destroy, + .decode = wined3d_decoder_va_vk_decode, +}; + struct wined3d_resource * CDECL wined3d_decoder_get_buffer( struct wined3d_decoder *decoder, enum wined3d_decoder_buffer_type type) { diff --git a/dlls/wined3d/wined3d_main.c b/dlls/wined3d/wined3d_main.c index 91d8dd567ff..976ea72d69c 100644 --- a/dlls/wined3d/wined3d_main.c +++ b/dlls/wined3d/wined3d_main.c @@ -129,6 +129,7 @@ struct wined3d_settings wined3d_settings = .max_sm_cs = UINT_MAX, .renderer = WINED3D_RENDERER_AUTO, .shader_backend = WINED3D_SHADER_BACKEND_AUTO, + .decoder_backend = WINED3D_DECODER_BACKEND_AUTO, }; enum wined3d_renderer CDECL wined3d_get_renderer(void) @@ -363,6 +364,19 @@ static BOOL wined3d_dll_init(HINSTANCE hInstDLL) wined3d_settings.shader_backend = WINED3D_SHADER_BACKEND_GLSL; } } + if (!get_config_key(hkey, appkey, env, "decoder_backend", buffer, size)) + { + if (!stricmp(buffer, "vulkan")) + { + ERR_(winediag)("Using the Vulkan video decoder backend.\n"); + wined3d_settings.decoder_backend = WINED3D_DECODER_BACKEND_VULKAN; + } + else if (!stricmp(buffer, "va")) + { + ERR_(winediag)("Using the VA video decoder backend.\n"); + wined3d_settings.decoder_backend = WINED3D_DECODER_BACKEND_VA; + } + } if (!get_config_key_dword(hkey, appkey, env, "VideoPciDeviceID", &tmpvalue)) { int pci_device_id = tmpvalue; diff --git a/dlls/wined3d/wined3d_private.h b/dlls/wined3d/wined3d_private.h index 9537670f7c5..1be740c94b3 100644 --- a/dlls/wined3d/wined3d_private.h +++ b/dlls/wined3d/wined3d_private.h @@ -457,6 +457,13 @@ enum wined3d_shader_backend WINED3D_SHADER_BACKEND_GLSL_VKD3D, }; +enum wined3d_decoder_backend +{ + WINED3D_DECODER_BACKEND_AUTO, + WINED3D_DECODER_BACKEND_VULKAN, + WINED3D_DECODER_BACKEND_VA, +}; + #define WINED3D_CSMT_ENABLE 0x00000001 #define WINED3D_CSMT_SERIALIZE 0x00000002 @@ -482,6 +489,7 @@ struct wined3d_settings unsigned int max_sm_cs; enum wined3d_renderer renderer; enum wined3d_shader_backend shader_backend; + enum wined3d_decoder_backend decoder_backend; bool check_float_constants; bool cb_access_map_w; bool ffp_hlsl; @@ -4520,6 +4528,7 @@ struct wined3d_decoder_ops }; extern const struct wined3d_decoder_ops wined3d_decoder_vk_ops; +extern const struct wined3d_decoder_ops wined3d_decoder_va_vk_ops; extern const struct wined3d_decoder_ops wined3d_null_decoder_ops; /* DirectDraw utility functions */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11535
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/wined3d/decoder.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/dlls/wined3d/decoder.c b/dlls/wined3d/decoder.c index 18f987b6ed6..6a7ca54554f 100644 --- a/dlls/wined3d/decoder.c +++ b/dlls/wined3d/decoder.c @@ -84,7 +84,7 @@ static bool is_supported_codec(struct wined3d_adapter *adapter, const GUID *code } static HRESULT wined3d_decoder_init(struct wined3d_decoder *decoder, - struct wined3d_device *device, const struct wined3d_decoder_desc *desc) + struct wined3d_device *device, const struct wined3d_decoder_desc *desc, bool gpu_bitstream) { HRESULT hr; @@ -128,9 +128,12 @@ static HRESULT wined3d_decoder_init(struct wined3d_decoder *decoder, * is at most 1 byte). AMD makes it larger than that. * Go with the smaller of the two. */ buffer_desc.byte_width = desc->width * desc->height; - buffer_desc.bind_flags = WINED3D_BIND_DECODER_SRC; - buffer_desc.access = WINED3D_RESOURCE_ACCESS_GPU | WINED3D_RESOURCE_ACCESS_MAP_W; - buffer_desc.usage = WINED3DUSAGE_DYNAMIC; + if (gpu_bitstream) + { + buffer_desc.bind_flags = WINED3D_BIND_DECODER_SRC; + buffer_desc.access = WINED3D_RESOURCE_ACCESS_GPU | WINED3D_RESOURCE_ACCESS_MAP_W; + buffer_desc.usage = WINED3DUSAGE_DYNAMIC; + } if (FAILED(hr = wined3d_buffer_create(device, &buffer_desc, NULL, NULL, &wined3d_null_parent_ops, &decoder->bitstream))) @@ -616,7 +619,7 @@ static HRESULT wined3d_decoder_vk_create(struct wined3d_device *device, if (!(object = calloc(1, sizeof(*object)))) return E_OUTOFMEMORY; - if (FAILED(hr = wined3d_decoder_init(&object->d, device, desc))) + if (FAILED(hr = wined3d_decoder_init(&object->d, device, desc, true))) { free(object); return hr; @@ -1377,7 +1380,7 @@ static HRESULT wined3d_decoder_va_vk_create(struct wined3d_device *device, if (!(object = calloc(1, sizeof(*object)))) return E_OUTOFMEMORY; - if (FAILED(hr = wined3d_decoder_init(&object->d, device, desc))) + if (FAILED(hr = wined3d_decoder_init(&object->d, device, desc, false))) { free(object); return hr; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11535
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/win32u/vulkan.c | 4 ++++ dlls/winevulkan/make_vulkan | 2 +- include/wine/vulkan.h | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/dlls/win32u/vulkan.c b/dlls/win32u/vulkan.c index 6feb6a0416e..5303a8e9bd5 100644 --- a/dlls/win32u/vulkan.c +++ b/dlls/win32u/vulkan.c @@ -689,6 +689,10 @@ static VkResult convert_device_create_info( struct vulkan_physical_device *physi device->extensions.has_VK_KHR_external_fence_win32 = 0; device->extensions.has_VK_KHR_external_semaphore_win32 = 0; + /* For Direct3D VA support. */ + device->extensions.has_VK_EXT_external_memory_dma_buf = physical_device->extensions.has_VK_EXT_external_memory_dma_buf; + device->extensions.has_VK_EXT_physical_device_drm = physical_device->extensions.has_VK_EXT_physical_device_drm; + if (physical_device->map_placed_align) { VkPhysicalDeviceMapMemoryPlacedFeaturesEXT *map_placed_features; diff --git a/dlls/winevulkan/make_vulkan b/dlls/winevulkan/make_vulkan index f8375c96e32..05af73601da 100755 --- a/dlls/winevulkan/make_vulkan +++ b/dlls/winevulkan/make_vulkan @@ -103,7 +103,6 @@ UNSUPPORTED_EXTENSIONS = { # Extensions for other platforms "VK_EXT_metal_objects", - "VK_EXT_physical_device_drm", "VK_GOOGLE_surfaceless_query", "VK_SEC_amigo_profiling", # Angle specific. @@ -129,6 +128,7 @@ UNEXPOSED_EXTENSIONS = { "VK_EXT_external_memory_dma_buf", "VK_EXT_image_drm_format_modifier", "VK_EXT_metal_surface", + "VK_EXT_physical_device_drm", "VK_KHR_external_fence_fd", "VK_KHR_external_memory_fd", "VK_EXT_external_memory_metal", diff --git a/include/wine/vulkan.h b/include/wine/vulkan.h index 9b00aeeec04..a53df3e8230 100644 --- a/include/wine/vulkan.h +++ b/include/wine/vulkan.h @@ -479,6 +479,8 @@ typedef void* VkRemoteAddressNV; #define VK_EXT_PAGEABLE_DEVICE_LOCAL_MEMORY_SPEC_VERSION 1 #define VK_EXT_PCI_BUS_INFO_EXTENSION_NAME "VK_EXT_pci_bus_info" #define VK_EXT_PCI_BUS_INFO_SPEC_VERSION 2 +#define VK_EXT_PHYSICAL_DEVICE_DRM_EXTENSION_NAME "VK_EXT_physical_device_drm" +#define VK_EXT_PHYSICAL_DEVICE_DRM_SPEC_VERSION 1 #define VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME "VK_EXT_pipeline_creation_cache_control" #define VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_SPEC_VERSION 3 #define VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME "VK_EXT_pipeline_creation_feedback" @@ -6544,6 +6546,7 @@ typedef enum VkStructureType VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_INPUT_DYNAMIC_STATE_FEATURES_EXT = 1000352000, VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT = 1000352001, VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT = 1000352002, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT = 1000353000, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ADDRESS_BINDING_REPORT_FEATURES_EXT = 1000354000, VK_STRUCTURE_TYPE_DEVICE_ADDRESS_BINDING_CALLBACK_DATA_EXT = 1000354001, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT = 1000355000, @@ -15630,6 +15633,18 @@ typedef struct VkPhysicalDeviceDriverProperties } VkPhysicalDeviceDriverProperties; typedef VkPhysicalDeviceDriverProperties VkPhysicalDeviceDriverPropertiesKHR; +typedef struct VkPhysicalDeviceDrmPropertiesEXT +{ + VkStructureType sType; + void *pNext; + VkBool32 hasPrimary; + VkBool32 hasRender; + int64_t primaryMajor; + int64_t primaryMinor; + int64_t renderMajor; + int64_t renderMinor; +} VkPhysicalDeviceDrmPropertiesEXT; + typedef struct VkPhysicalDeviceDynamicRenderingFeatures { VkStructureType sType; @@ -23752,6 +23767,7 @@ VkResult VKAPI_CALL vkWriteSamplerDescriptorsEXT(VkDevice device, uint32_t sampl USE_VK_EXT(VK_EXT_external_memory_metal) \ USE_VK_EXT(VK_EXT_image_drm_format_modifier) \ USE_VK_EXT(VK_EXT_map_memory_placed) \ + USE_VK_EXT(VK_EXT_physical_device_drm) \ USE_VK_EXT(VK_KHR_external_fence_fd) \ USE_VK_EXT(VK_KHR_external_memory_fd) \ USE_VK_EXT(VK_KHR_external_semaphore_fd) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11535
From: Elizabeth Figura <zfigura@codeweavers.com> --- configure.ac | 15 +++++ dlls/wined3d/Makefile.in | 4 ++ dlls/wined3d/decoder.c | 24 +++++++ dlls/wined3d/unixlib.h | 44 +++++++++++++ dlls/wined3d/va.c | 139 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+) create mode 100644 dlls/wined3d/unixlib.h create mode 100644 dlls/wined3d/va.c diff --git a/configure.ac b/configure.ac index ee0b1c483d4..ad0eb7ead6a 100644 --- a/configure.ac +++ b/configure.ac @@ -59,6 +59,7 @@ AC_ARG_WITH(sdl, AS_HELP_STRING([--without-sdl],[do not use SDL])) AC_ARG_WITH(udev, AS_HELP_STRING([--without-udev],[do not use udev (plug and play support)])) AC_ARG_WITH(usb, AS_HELP_STRING([--without-usb],[do not use the libusb library])) AC_ARG_WITH(v4l2, AS_HELP_STRING([--without-v4l2],[do not use v4l2 (video capture)])) +AC_ARG_WITH(va, AS_HELP_STRING([--without-va],[do not use VA-API (hardware decode)])) AC_ARG_WITH(vulkan, AS_HELP_STRING([--without-vulkan],[do not use Vulkan])) AC_ARG_WITH(wayland, AS_HELP_STRING([--without-wayland],[do not build the Wayland driver])) AC_ARG_WITH(xcomposite,AS_HELP_STRING([--without-xcomposite],[do not use the Xcomposite extension]), @@ -1618,6 +1619,20 @@ fi WINE_NOTICE_WITH(v4l2,[test "x$ac_cv_lib_soname_v4l2" = "x"], [libv4l2 ${notice_platform}development files not found.]) +dnl **** Check for libva **** +if test "x$with_va" != "xno" +then + WINE_PACKAGE_FLAGS(VA,[libva libva-drm],[-lva-drm -lva],,, + [AC_CHECK_HEADER([va/va_drm.h], + [AC_CHECK_FUNC(vaGetDisplayDRM, + [AC_DEFINE(HAVE_VA, 1, [Define to 1 if you have the 'va' library (-lva).])], + [VA_LIBS=""])], + [VA_LIBS=""])]) +fi +WINE_NOTICE_WITH(va,[test "$ac_cv_lib_va_drm_vaGetDisplayDRM" != "yes"], + [libva-drm ${notice_platform}development files not found, VA video acceleration won't be supported.], + [enable_wineva]) + dnl **** Check for libgphoto2 **** if test "x$with_gphoto" != "xno" then diff --git a/dlls/wined3d/Makefile.in b/dlls/wined3d/Makefile.in index bbf8f260b02..ecf9c264e75 100644 --- a/dlls/wined3d/Makefile.in +++ b/dlls/wined3d/Makefile.in @@ -1,7 +1,10 @@ MODULE = wined3d.dll +UNIXLIB = wined3d.so IMPORTLIB = wined3d IMPORTS = $(VKD3D_PE_LIBS) dxguid opengl32 user32 gdi32 advapi32 EXTRAINCL = $(VKD3D_PE_CFLAGS) +UNIX_CFLAGS = $(VA_CFLAGS) +UNIX_LIBS = -lwin32u $(VA_LIBS) VER_FILEDESCRIPTION_STR = "Wine D3D" @@ -36,6 +39,7 @@ SOURCES = \ texture_gl.c \ texture_vk.c \ utils.c \ + va.c \ vertexdeclaration.c \ view.c \ wined3d_main.c diff --git a/dlls/wined3d/decoder.c b/dlls/wined3d/decoder.c index 6a7ca54554f..2cb1c5fdbfb 100644 --- a/dlls/wined3d/decoder.c +++ b/dlls/wined3d/decoder.c @@ -18,6 +18,7 @@ #include "wined3d_private.h" #include "wined3d_vk.h" +#include "unixlib.h" WINE_DEFAULT_DEBUG_CHANNEL(d3d); @@ -1369,15 +1370,38 @@ static struct wined3d_decoder_va_vk *wined3d_decoder_va_vk(struct wined3d_decode static void wined3d_decoder_va_vk_get_profiles(struct wined3d_adapter *adapter, unsigned int *count, GUID *profiles) { + struct va_get_profiles_vk_params params; + NTSTATUS status; + *count = 0; + + /* We are under wined3d_mutex, so this is thread safe. */ + if ((status = __wine_init_unix_call())) + { + WARN("Failed to load Unix library, status %#lx.\n", status); + return; + } + + params.physical_device = (uintptr_t)wined3d_adapter_vk(adapter)->physical_device; + params.count = (uintptr_t)count; + params.profiles = (uintptr_t)profiles; + WINE_UNIX_CALL(unix_va_get_profiles_vk, ¶ms); } static HRESULT wined3d_decoder_va_vk_create(struct wined3d_device *device, const struct wined3d_decoder_desc *desc, struct wined3d_decoder **decoder) { struct wined3d_decoder_va_vk *object; + NTSTATUS status; HRESULT hr; + /* We are under wined3d_mutex, so this is thread safe. */ + if ((status = __wine_init_unix_call())) + { + WARN("Failed to load Unix library, status %#lx.\n", status); + return E_FAIL; + } + if (!(object = calloc(1, sizeof(*object)))) return E_OUTOFMEMORY; if (FAILED(hr = wined3d_decoder_init(&object->d, device, desc, false))) diff --git a/dlls/wined3d/unixlib.h b/dlls/wined3d/unixlib.h new file mode 100644 index 00000000000..45630418d08 --- /dev/null +++ b/dlls/wined3d/unixlib.h @@ -0,0 +1,44 @@ +/* + * Copyright 2024 Elizabeth Figura for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __WINE_WINED3D_UNIXLIB_H +#define __WINE_WINED3D_UNIXLIB_H + +/* We cannot put these definitions in wined3d_private.h, because + * wined3d_private.h uses vkd3d-shader definitions, and we cannot access + * vkd3d_shader.h from the Unix side. */ + +#define COBJMACROS +#include <stdint.h> +#include "objbase.h" +#include "wine/wined3d.h" +#include "wine/unixlib.h" + +struct va_get_profiles_vk_params +{ + UINT64 physical_device; /* VkPhysicalDevice */ + UINT64 count; /* unsigned int * */ + UINT64 profiles; /* GUID * */ +}; + +enum unix_funcs +{ + unix_va_get_profiles_vk, +}; + +#endif diff --git a/dlls/wined3d/va.c b/dlls/wined3d/va.c new file mode 100644 index 00000000000..bfb8d51522f --- /dev/null +++ b/dlls/wined3d/va.c @@ -0,0 +1,139 @@ +/* + * Copyright 2024 Elizabeth Figura for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#ifdef HAVE_VA + +#include "initguid.h" +#include "unixlib.h" +#include "dxva.h" +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <sys/stat.h> +#include <unistd.h> +#include <va/va.h> +#include <va/va_drm.h> +#include <va/va_drmcommon.h> +#include "ntgdi.h" +#include "wine/vulkan_driver.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(d3d); + +static NTSTATUS open_va_display(UINT64 handle, VADisplay *ret_display, int *ret_fd) +{ + VkPhysicalDeviceDrmPropertiesEXT drm_properties = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT}; + VkPhysicalDeviceProperties2 properties = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2}; + const struct vulkan_physical_device *device = vulkan_physical_device_from_handle((VkPhysicalDevice)handle); + int major, minor, fd; + VADisplay display; + VAStatus status; + char *path; + + if (!device->extensions.has_VK_EXT_physical_device_drm) + return E_NOTIMPL; + + properties.pNext = &drm_properties; + device->instance->p_vkGetPhysicalDeviceProperties2KHR(device->host.physical_device, &properties); + + if (drm_properties.hasPrimary) + { + asprintf(&path, "/dev/dri/card%"PRIu64, drm_properties.primaryMinor); + } + else if (drm_properties.hasRender) + { + asprintf(&path, "/dev/dri/renderD%"PRIu64, drm_properties.primaryMinor); + } + else + { + ERR("Vulkan device has neither a primary nor render node.\n"); + return E_FAIL; + } + + if ((fd = open(path, O_RDWR | O_CLOEXEC)) < 0) + { + ERR("Failed to open %s: %s\n", path, strerror(errno)); + free(path); + return E_FAIL; + } + free(path); + + display = vaGetDisplayDRM(fd); + if ((status = vaInitialize(display, &major, &minor)) != VA_STATUS_SUCCESS) + { + ERR("Failed to initialize VA, error %#x.\n", status); + close(fd); + return E_FAIL; + } + + *ret_display = display; + *ret_fd = fd; + return S_OK; +} + +static NTSTATUS va_get_profiles_vk(void *args) +{ + struct va_get_profiles_vk_params *params = args; + unsigned int *count = (unsigned int *)(uintptr_t)params->count; + GUID *profiles = (GUID *)(uintptr_t)params->profiles; + VAProfile *va_profiles; + int drm_fd, max_count; + VADisplay display; + NTSTATUS status; + + if ((status = open_va_display(params->physical_device, &display, &drm_fd))) + return status; + + max_count = vaMaxNumProfiles(display); + va_profiles = malloc(max_count * sizeof(*va_profiles)); + vaQueryConfigProfiles(display, va_profiles, &max_count); + + for (int i = 0; i < max_count; ++i) + { + if (va_profiles[i] == VAProfileH264High) + { + profiles[(*count)++] = DXVA_ModeH264_VLD_NoFGT; + /* FIXME: Native GPUs also support DXVA2_ModeH264_VLD_Stereo_NoFGT + * and DXVA2_ModeH264_VLD_Stereo_Progressive_NoFGT. */ + } + } + + free(va_profiles); + vaTerminate(display); + close(drm_fd); + + return S_OK; +} + +const unixlib_entry_t __wine_unix_call_funcs[] = +{ + [unix_va_get_profiles_vk] = va_get_profiles_vk, +}; + +const unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{ + [unix_va_get_profiles_vk] = va_get_profiles_vk, +}; + +#endif -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11535
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/wined3d/decoder.c | 29 ++++++++++++ dlls/wined3d/unixlib.h | 19 ++++++++ dlls/wined3d/va.c | 101 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) diff --git a/dlls/wined3d/decoder.c b/dlls/wined3d/decoder.c index 2cb1c5fdbfb..ce92568d642 100644 --- a/dlls/wined3d/decoder.c +++ b/dlls/wined3d/decoder.c @@ -1361,6 +1361,7 @@ const struct wined3d_decoder_ops wined3d_decoder_vk_ops = struct wined3d_decoder_va_vk { struct wined3d_decoder d; + uint64_t va_decoder; }; static struct wined3d_decoder_va_vk *wined3d_decoder_va_vk(struct wined3d_decoder *decoder) @@ -1388,6 +1389,29 @@ static void wined3d_decoder_va_vk_get_profiles(struct wined3d_adapter *adapter, WINE_UNIX_CALL(unix_va_get_profiles_vk, ¶ms); } +static void wined3d_decoder_va_vk_cs_init(void *object) +{ + struct wined3d_decoder_va_vk *decoder_va = object; + struct wined3d_adapter_vk *adapter_vk = wined3d_adapter_vk(decoder_va->d.device->adapter); + struct wined3d_device_vk *device_vk = wined3d_device_vk(decoder_va->d.device); + struct va_decoder_create_vk_params params; + HRESULT hr; + + params.desc = decoder_va->d.desc; + params.physical_device = (uintptr_t)adapter_vk->physical_device; + params.device = (uintptr_t)device_vk->vk_device; + params.width = decoder_va->d.width; + params.height = decoder_va->d.height; + + if (FAILED(hr = WINE_UNIX_CALL(unix_va_decoder_create_vk, ¶ms))) + { + ERR("Failed to initialize decoder, hr %#lx.\n", hr); + return; + } + + decoder_va->va_decoder = params.decoder; +} + static HRESULT wined3d_decoder_va_vk_create(struct wined3d_device *device, const struct wined3d_decoder_desc *desc, struct wined3d_decoder **decoder) { @@ -1410,6 +1434,8 @@ static HRESULT wined3d_decoder_va_vk_create(struct wined3d_device *device, return hr; } + wined3d_cs_init_object(device->cs, wined3d_decoder_va_vk_cs_init, object); + TRACE("Created decoder %p.\n", object); *decoder = &object->d; @@ -1419,9 +1445,12 @@ static HRESULT wined3d_decoder_va_vk_create(struct wined3d_device *device, static void wined3d_decoder_va_vk_destroy_object(void *object) { struct wined3d_decoder_va_vk *decoder_va = object; + struct va_decoder_destroy_vk_params params; TRACE("decoder_va %p.\n", decoder_va); + params.decoder = decoder_va->va_decoder; + WINE_UNIX_CALL(unix_va_decoder_destroy_vk, ¶ms); free(decoder_va); } diff --git a/dlls/wined3d/unixlib.h b/dlls/wined3d/unixlib.h index 45630418d08..9ed19aed67d 100644 --- a/dlls/wined3d/unixlib.h +++ b/dlls/wined3d/unixlib.h @@ -29,6 +29,8 @@ #include "wine/wined3d.h" #include "wine/unixlib.h" +#define VA_DECODER_SURFACE_COUNT 17 + struct va_get_profiles_vk_params { UINT64 physical_device; /* VkPhysicalDevice */ @@ -36,9 +38,26 @@ struct va_get_profiles_vk_params UINT64 profiles; /* GUID * */ }; +struct va_decoder_create_vk_params +{ + struct wined3d_decoder_desc desc; + UINT64 physical_device; /* VkPhysicalDevice */ + UINT64 device; /* VkDevice */ + UINT64 decoder; + unsigned int width; + unsigned int height; +}; + +struct va_decoder_destroy_vk_params +{ + UINT64 decoder; +}; + enum unix_funcs { unix_va_get_profiles_vk, + unix_va_decoder_create_vk, + unix_va_decoder_destroy_vk, }; #endif diff --git a/dlls/wined3d/va.c b/dlls/wined3d/va.c index bfb8d51522f..50a300be607 100644 --- a/dlls/wined3d/va.c +++ b/dlls/wined3d/va.c @@ -41,6 +41,15 @@ WINE_DEFAULT_DEBUG_CHANNEL(d3d); +struct va_decoder +{ + int drm_fd; + VADisplay display; + VAConfigID config; + VAContextID context; + VASurfaceID surfaces[VA_DECODER_SURFACE_COUNT]; +}; + static NTSTATUS open_va_display(UINT64 handle, VADisplay *ret_display, int *ret_fd) { VkPhysicalDeviceDrmPropertiesEXT drm_properties = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT}; @@ -126,14 +135,106 @@ static NTSTATUS va_get_profiles_vk(void *args) return S_OK; } +static NTSTATUS va_decoder_create_vk(void *args) +{ + struct va_decoder_create_vk_params *params = args; + VASurfaceAttrib surface_attribs[1]; + VAConfigAttrib config_attribs[1]; + unsigned int format, fourcc; + struct va_decoder *decoder; + VAStatus status; + + if (params->desc.output_format == WINED3DFMT_NV12_PLANAR) + { + format = VA_RT_FORMAT_YUV420; + fourcc = VA_FOURCC_NV12; + } + else + { + FIXME("Unhandled output format %#x.\n", params->desc.output_format); + return E_NOTIMPL; + } + + if (!(decoder = calloc(1, sizeof(*decoder)))) + return E_OUTOFMEMORY; + + if ((status = open_va_display(params->physical_device, &decoder->display, &decoder->drm_fd))) + return status; + + config_attribs[0].type = VAConfigAttribRTFormat; + config_attribs[0].value = format; + + if ((status = vaCreateConfig(decoder->display, VAProfileH264High, VAEntrypointVLD, + config_attribs, ARRAY_SIZE(config_attribs), &decoder->config)) != VA_STATUS_SUCCESS) + { + ERR("Failed to create config, error %#x.\n", status); + vaTerminate(decoder->display); + close(decoder->drm_fd); + goto fail; + } + + surface_attribs[0].type = VASurfaceAttribPixelFormat; + surface_attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE; + surface_attribs[0].value.type = VAGenericValueTypeInteger; + surface_attribs[0].value.value.i = fourcc; + + if ((status = vaCreateSurfaces(decoder->display, format, params->width, params->height, decoder->surfaces, + VA_DECODER_SURFACE_COUNT, surface_attribs, ARRAY_SIZE(surface_attribs))) != VA_STATUS_SUCCESS) + { + ERR("Failed to create surfaces, error %#x.\n", status); + vaDestroyConfig(decoder->display, decoder->config); + vaTerminate(decoder->display); + close(decoder->drm_fd); + goto fail; + } + + if ((status = vaCreateContext(decoder->display, decoder->config, params->desc.width, params->desc.height, + VA_PROGRESSIVE, decoder->surfaces, ARRAY_SIZE(decoder->surfaces), &decoder->context)) != VA_STATUS_SUCCESS) + { + ERR("Failed to create context, error %#x.\n", status); + vaDestroySurfaces(decoder->display, decoder->surfaces, VA_DECODER_SURFACE_COUNT); + vaDestroyConfig(decoder->display, decoder->config); + vaTerminate(decoder->display); + close(decoder->drm_fd); + goto fail; + } + + TRACE("Created VA decoder %p.\n", decoder); + + params->decoder = (uintptr_t)decoder; + return S_OK; + +fail: + free(decoder); + return E_FAIL; +} + +static NTSTATUS va_decoder_destroy_vk(void *args) +{ + struct va_decoder_destroy_vk_params *params = args; + struct va_decoder *decoder = (struct va_decoder *)(uintptr_t)params->decoder; + + vaDestroyContext(decoder->display, decoder->context); + vaDestroySurfaces(decoder->display, decoder->surfaces, VA_DECODER_SURFACE_COUNT); + vaDestroyConfig(decoder->display, decoder->config); + vaTerminate(decoder->display); + close(decoder->drm_fd); + free(decoder); + return S_OK; +} + const unixlib_entry_t __wine_unix_call_funcs[] = { [unix_va_get_profiles_vk] = va_get_profiles_vk, + [unix_va_decoder_create_vk] = va_decoder_create_vk, + [unix_va_decoder_destroy_vk] = va_decoder_destroy_vk, }; const unixlib_entry_t __wine_unix_call_wow64_funcs[] = { [unix_va_get_profiles_vk] = va_get_profiles_vk, + [unix_va_decoder_create_vk] = va_decoder_create_vk, + [unix_va_decoder_destroy_vk] = va_decoder_destroy_vk, }; #endif -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11535
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/win32u/vulkan.c | 2 + dlls/wined3d/adapter_vk.c | 6 ++ dlls/wined3d/context_vk.c | 28 +++++++++ dlls/wined3d/decoder.c | 56 ++++++++++++++++- dlls/wined3d/unixlib.h | 5 ++ dlls/wined3d/va.c | 124 +++++++++++++++++++++++++++++++++++++- dlls/wined3d/wined3d_vk.h | 6 ++ 7 files changed, 223 insertions(+), 4 deletions(-) diff --git a/dlls/win32u/vulkan.c b/dlls/win32u/vulkan.c index 5303a8e9bd5..a91f477859c 100644 --- a/dlls/win32u/vulkan.c +++ b/dlls/win32u/vulkan.c @@ -691,6 +691,8 @@ static VkResult convert_device_create_info( struct vulkan_physical_device *physi /* For Direct3D VA support. */ device->extensions.has_VK_EXT_external_memory_dma_buf = physical_device->extensions.has_VK_EXT_external_memory_dma_buf; + device->extensions.has_VK_EXT_image_drm_format_modifier = physical_device->extensions.has_VK_EXT_image_drm_format_modifier; + device->extensions.has_VK_KHR_image_format_list = physical_device->extensions.has_VK_KHR_image_format_list; device->extensions.has_VK_EXT_physical_device_drm = physical_device->extensions.has_VK_EXT_physical_device_drm; if (physical_device->map_placed_align) diff --git a/dlls/wined3d/adapter_vk.c b/dlls/wined3d/adapter_vk.c index a88faf2c9ce..3e349247899 100644 --- a/dlls/wined3d/adapter_vk.c +++ b/dlls/wined3d/adapter_vk.c @@ -2414,6 +2414,12 @@ static bool wined3d_adapter_vk_init_device_extensions(struct wined3d_adapter_vk {VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME, ~0u}, {VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME, ~0u}, {VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME, ~0u}, + {VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, ~0u}, + /* FIXME: We don't use KHR_external_memory_win32 yet, but we do use + * KHR_external_memory_fd for VA support, and we need to enable the + * fd extension by enabling the win32 extension. */ + {VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, ~0u}, + {VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME, ~0u}, {VK_KHR_MAINTENANCE1_EXTENSION_NAME, VK_API_VERSION_1_1, true}, {VK_KHR_MAINTENANCE2_EXTENSION_NAME, VK_API_VERSION_1_1}, {VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME,VK_API_VERSION_1_2}, diff --git a/dlls/wined3d/context_vk.c b/dlls/wined3d/context_vk.c index 119c498c78b..96d8c15ad7c 100644 --- a/dlls/wined3d/context_vk.c +++ b/dlls/wined3d/context_vk.c @@ -1159,6 +1159,30 @@ void wined3d_context_vk_destroy_vk_video_parameters(struct wined3d_context_vk *c o->command_buffer_id = command_buffer_id; } +void wined3d_context_vk_destroy_va_decoder(struct wined3d_context_vk *context_vk, + uint64_t handle, uint64_t command_buffer_id) +{ + struct wined3d_device_vk *device_vk = wined3d_device_vk(context_vk->c.device); + struct wined3d_retired_object_vk *o; + + if (context_vk->completed_command_buffer_id >= command_buffer_id) + { + wined3d_decoder_va_vk_destroy_va_decoder(device_vk, handle); + TRACE("Destroyed VA decoder 0x%s.\n", wine_dbgstr_longlong(handle)); + return; + } + + if (!(o = wined3d_context_vk_get_retired_object_vk(context_vk))) + { + ERR("Leaking VA decoder 0x%s.\n", wine_dbgstr_longlong(handle)); + return; + } + + o->type = WINED3D_RETIRED_DECODER_VA_VK; + o->u.va_decoder = handle; + o->command_buffer_id = command_buffer_id; +} + void wined3d_context_vk_destroy_image(struct wined3d_context_vk *context_vk, struct wined3d_image_vk *image) { wined3d_context_vk_destroy_vk_image(context_vk, image->vk_image, image->command_buffer_id); @@ -1472,6 +1496,10 @@ static void wined3d_context_vk_cleanup_resources(struct wined3d_context_vk *cont o->u.aux_command_buffer.pool, &o->u.aux_command_buffer.buffer); break; + case WINED3D_RETIRED_DECODER_VA_VK: + wined3d_decoder_va_vk_destroy_va_decoder(device_vk, o->u.va_decoder); + break; + default: ERR("Unhandled object type %#x.\n", o->type); break; diff --git a/dlls/wined3d/decoder.c b/dlls/wined3d/decoder.c index ce92568d642..e335adce7cb 100644 --- a/dlls/wined3d/decoder.c +++ b/dlls/wined3d/decoder.c @@ -1362,6 +1362,12 @@ struct wined3d_decoder_va_vk { struct wined3d_decoder d; uint64_t va_decoder; + + struct wined3d_decoder_image_va_vk + { + VkImage image; + uint64_t command_buffer_id; + } images[VA_DECODER_SURFACE_COUNT]; }; static struct wined3d_decoder_va_vk *wined3d_decoder_va_vk(struct wined3d_decoder *decoder) @@ -1395,6 +1401,7 @@ static void wined3d_decoder_va_vk_cs_init(void *object) struct wined3d_adapter_vk *adapter_vk = wined3d_adapter_vk(decoder_va->d.device->adapter); struct wined3d_device_vk *device_vk = wined3d_device_vk(decoder_va->d.device); struct va_decoder_create_vk_params params; + struct wined3d_context_vk *context_vk; HRESULT hr; params.desc = decoder_va->d.desc; @@ -1410,6 +1417,25 @@ static void wined3d_decoder_va_vk_cs_init(void *object) } decoder_va->va_decoder = params.decoder; + + context_vk = wined3d_context_vk(context_acquire(&device_vk->d, NULL, 0)); + + for (unsigned int i = 0; i < VA_DECODER_SURFACE_COUNT; ++i) + { + struct wined3d_decoder_image_va_vk *image = &decoder_va->images[i]; + VkImageSubresourceRange vk_range = {0}; + + image->image = params.surfaces[i].image; + + vk_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + vk_range.levelCount = 1; + vk_range.layerCount = 1; + wined3d_context_vk_image_barrier(context_vk, wined3d_context_vk_get_command_buffer(context_vk), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, image->image, &vk_range); + } + + context_release(&context_vk->c); } static HRESULT wined3d_decoder_va_vk_create(struct wined3d_device *device, @@ -1442,16 +1468,40 @@ static HRESULT wined3d_decoder_va_vk_create(struct wined3d_device *device, return WINED3D_OK; } +void wined3d_decoder_va_vk_destroy_va_decoder(struct wined3d_device_vk *device_vk, uint64_t handle) +{ + struct va_decoder_destroy_vk_params params; + + params.device = (uintptr_t)device_vk->vk_device; + params.decoder = handle; + WINE_UNIX_CALL(unix_va_decoder_destroy_vk, ¶ms); +} + static void wined3d_decoder_va_vk_destroy_object(void *object) { struct wined3d_decoder_va_vk *decoder_va = object; - struct va_decoder_destroy_vk_params params; + struct wined3d_context_vk *context_vk; + uint64_t command_buffer_id = 0; TRACE("decoder_va %p.\n", decoder_va); - params.decoder = decoder_va->va_decoder; - WINE_UNIX_CALL(unix_va_decoder_destroy_vk, ¶ms); + context_vk = wined3d_context_vk(context_acquire(decoder_va->d.device, NULL, 0)); + + for (unsigned int i = 0; i < ARRAY_SIZE(decoder_va->images); ++i) + { + struct wined3d_decoder_image_va_vk *image = &decoder_va->images[i]; + + wined3d_context_vk_destroy_vk_image(context_vk, image->image, image->command_buffer_id); + command_buffer_id = max(command_buffer_id, image->command_buffer_id); + } + + /* We probably don't need to wait to destroy the VA decoder, since it's not + * a Vulkan object, but we did allocate Vulkan memory on the Unix side + * which we need to make sure isn't freed until the GPU is done with it. */ + wined3d_context_vk_destroy_va_decoder(context_vk, decoder_va->va_decoder, command_buffer_id); free(decoder_va); + + context_release(&context_vk->c); } static void wined3d_decoder_va_vk_destroy(struct wined3d_decoder *decoder) diff --git a/dlls/wined3d/unixlib.h b/dlls/wined3d/unixlib.h index 9ed19aed67d..ec53d636afb 100644 --- a/dlls/wined3d/unixlib.h +++ b/dlls/wined3d/unixlib.h @@ -46,10 +46,15 @@ struct va_decoder_create_vk_params UINT64 decoder; unsigned int width; unsigned int height; + struct + { + UINT64 image; /* VkImage */ + } surfaces[VA_DECODER_SURFACE_COUNT]; }; struct va_decoder_destroy_vk_params { + UINT64 device; /* VkDevice */ UINT64 decoder; }; diff --git a/dlls/wined3d/va.c b/dlls/wined3d/va.c index 50a300be607..53f9797892b 100644 --- a/dlls/wined3d/va.c +++ b/dlls/wined3d/va.c @@ -48,6 +48,7 @@ struct va_decoder VAConfigID config; VAContextID context; VASurfaceID surfaces[VA_DECODER_SURFACE_COUNT]; + VkDeviceMemory vk_memory[VA_DECODER_SURFACE_COUNT]; }; static NTSTATUS open_va_display(UINT64 handle, VADisplay *ret_display, int *ret_fd) @@ -60,7 +61,9 @@ static NTSTATUS open_va_display(UINT64 handle, VADisplay *ret_display, int *ret_ VAStatus status; char *path; - if (!device->extensions.has_VK_EXT_physical_device_drm) + if (!device->extensions.has_VK_EXT_external_memory_dma_buf + || !device->extensions.has_VK_EXT_image_drm_format_modifier + || !device->extensions.has_VK_EXT_physical_device_drm) return E_NOTIMPL; properties.pNext = &drm_properties; @@ -138,16 +141,30 @@ static NTSTATUS va_get_profiles_vk(void *args) static NTSTATUS va_decoder_create_vk(void *args) { struct va_decoder_create_vk_params *params = args; + VkImageDrmFormatModifierExplicitCreateInfoEXT drm_format_desc = {.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT}; + VkExternalMemoryImageCreateInfo external_image_desc = {.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO}; + VkMemoryDedicatedAllocateInfo dedicated_desc = {.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO}; + VkMemoryDedicatedRequirements dedicated_reqs = {.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS}; + VkImageMemoryRequirementsInfo2 reqs_desc = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2}; + VkMemoryFdPropertiesKHR fd_properties = {.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR}; + VkImportMemoryFdInfoKHR fd_desc = {.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR}; + VkMemoryRequirements2 memory_reqs = {.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2}; + VkMemoryAllocateInfo memory_desc = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + struct vulkan_device *device = vulkan_device_from_handle((VkDevice)params->device); + VkImageCreateInfo image_desc = {.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + VADRMPRIMESurfaceDescriptor descriptor; VASurfaceAttrib surface_attribs[1]; VAConfigAttrib config_attribs[1]; unsigned int format, fourcc; struct va_decoder *decoder; VAStatus status; + VkResult vr; if (params->desc.output_format == WINED3DFMT_NV12_PLANAR) { format = VA_RT_FORMAT_YUV420; fourcc = VA_FOURCC_NV12; + image_desc.format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; } else { @@ -199,6 +216,108 @@ static NTSTATUS va_decoder_create_vk(void *args) goto fail; } + image_desc.imageType = VK_IMAGE_TYPE_2D; + image_desc.extent.width = params->width; + image_desc.extent.height = params->height; + image_desc.extent.depth = 1; + image_desc.mipLevels = 1; + image_desc.arrayLayers = 1; + image_desc.samples = 1; + image_desc.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + image_desc.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + image_desc.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + image_desc.pNext = &external_image_desc; + external_image_desc.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; + + external_image_desc.pNext = &drm_format_desc; + + memory_desc.pNext = &fd_desc; + fd_desc.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; + + for (unsigned int i = 0; i < VA_DECODER_SURFACE_COUNT; ++i) + { + VkSubresourceLayout plane_layouts[4] = {0}; + VkImage image; + DWORD index; + + if ((status = vaExportSurfaceHandle(decoder->display, decoder->surfaces[i], VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, + VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_COMPOSED_LAYERS, &descriptor))) + ERR("Failed to export, error %#x.\n", status); + + if (descriptor.num_objects != 1) + FIXME("Unhandled object count %u.\n", descriptor.num_objects); + /* We passed VA_EXPORT_SURFACE_COMPOSED_LAYERS; this shouldn't happen. */ + if (descriptor.num_layers != 1) + ERR("Unexpected layer count %u.\n", descriptor.num_layers); + + if ((vr = device->p_vkGetMemoryFdPropertiesKHR(device->host.device, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, descriptor.objects[0].fd, &fd_properties)) != VK_SUCCESS) + { + ERR("Failed to get fd properties, vr %#x.\n", vr); + continue; + } + + if (!BitScanForward(&index, fd_properties.memoryTypeBits)) + { + ERR("No valid type bits.\n"); + return E_NOTIMPL; + } + memory_desc.memoryTypeIndex = index; + + memory_desc.allocationSize = descriptor.objects[0].size; + + for (unsigned int j = 0; j < descriptor.layers[0].num_planes; ++j) + { + plane_layouts[j].offset = descriptor.layers[0].offset[j]; + plane_layouts[j].rowPitch = descriptor.layers[0].pitch[j]; + } + drm_format_desc.drmFormatModifier = descriptor.objects[0].drm_format_modifier; + drm_format_desc.drmFormatModifierPlaneCount = descriptor.layers[0].num_planes; + drm_format_desc.pPlaneLayouts = plane_layouts; + + /* Images aren't wrapped; we can just call the host version. */ + if ((vr = device->p_vkCreateImage(device->host.device, &image_desc, NULL, &image)) != VK_SUCCESS) + { + ERR("Failed to create image, vr %#x.\n", vr); + continue; + } + + if (device->extensions.has_VK_KHR_dedicated_allocation) + { + memory_reqs.pNext = &dedicated_reqs; + reqs_desc.image = image; + device->p_vkGetImageMemoryRequirements2(device->host.device, &reqs_desc, &memory_reqs); + + if (dedicated_reqs.prefersDedicatedAllocation) + { + fd_desc.pNext = &dedicated_desc; + dedicated_desc.image = image; + } + } + + fd_desc.fd = descriptor.objects[0].fd; + + if ((vr = device->p_vkAllocateMemory(device->host.device, + &memory_desc, NULL, &decoder->vk_memory[i])) != VK_SUCCESS) + { + ERR("Failed to allocate memory, vr %d.\n", vr); + device->p_vkDestroyImage(device->host.device, image, NULL); + continue; + } + + if ((vr = device->p_vkBindImageMemory(device->host.device, image, decoder->vk_memory[i], 0)) != VK_SUCCESS) + { + ERR("Failed to bind memory, vr %d.\n", vr); + device->p_vkFreeMemory(device->host.device, decoder->vk_memory[i], NULL); + device->p_vkDestroyImage(device->host.device, image, NULL); + continue; + } + + params->surfaces[i].image = image; + } + TRACE("Created VA decoder %p.\n", decoder); params->decoder = (uintptr_t)decoder; @@ -213,7 +332,10 @@ static NTSTATUS va_decoder_destroy_vk(void *args) { struct va_decoder_destroy_vk_params *params = args; struct va_decoder *decoder = (struct va_decoder *)(uintptr_t)params->decoder; + struct vulkan_device *device = vulkan_device_from_handle((VkDevice)params->device); + for (unsigned int i = 0; i < ARRAY_SIZE(decoder->vk_memory); ++i) + device->p_vkFreeMemory(device->host.device, decoder->vk_memory[i], NULL); vaDestroyContext(decoder->display, decoder->context); vaDestroySurfaces(decoder->display, decoder->surfaces, VA_DECODER_SURFACE_COUNT); vaDestroyConfig(decoder->display, decoder->config); diff --git a/dlls/wined3d/wined3d_vk.h b/dlls/wined3d/wined3d_vk.h index 937a958760c..434e9b5e013 100644 --- a/dlls/wined3d/wined3d_vk.h +++ b/dlls/wined3d/wined3d_vk.h @@ -475,6 +475,7 @@ enum wined3d_retired_object_type_vk WINED3D_RETIRED_VIDEO_SESSION_VK, WINED3D_RETIRED_VIDEO_PARAMETERS_VK, WINED3D_RETIRED_AUX_COMMAND_BUFFER_VK, + WINED3D_RETIRED_DECODER_VA_VK, }; struct wined3d_retired_object_vk @@ -512,6 +513,7 @@ struct wined3d_retired_object_vk struct wined3d_aux_command_pool_vk *pool; struct wined3d_aux_command_buffer_vk buffer; } aux_command_buffer; + uint64_t va_decoder; } u; uint64_t command_buffer_id; }; @@ -758,6 +760,8 @@ void wined3d_context_vk_destroy_bo(struct wined3d_context_vk *context_vk, const struct wined3d_bo_vk *bo); void wined3d_context_vk_destroy_image(struct wined3d_context_vk *context_vk, struct wined3d_image_vk *image_vk); +void wined3d_context_vk_destroy_va_decoder(struct wined3d_context_vk *context_vk, + uint64_t handle, uint64_t command_buffer_id); void wined3d_context_vk_destroy_vk_buffer_view(struct wined3d_context_vk *context_vk, VkBufferView vk_view, uint64_t command_buffer_id); void wined3d_context_vk_destroy_vk_framebuffer(struct wined3d_context_vk *context_vk, @@ -1150,6 +1154,8 @@ HRESULT wined3d_decoder_output_view_vk_init(struct wined3d_decoder_output_view_v const struct wined3d_view_desc *desc, struct wined3d_texture *texture, void *parent, const struct wined3d_parent_ops *parent_ops); +void wined3d_decoder_va_vk_destroy_va_decoder(struct wined3d_device_vk *device_vk, uint64_t handle); + struct wined3d_swapchain_vk { struct wined3d_swapchain s; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11535
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/wined3d/decoder.c | 214 ++++++++++++++++++++++++++++++++++--- dlls/wined3d/unixlib.h | 9 ++ dlls/wined3d/va.c | 235 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 442 insertions(+), 16 deletions(-) diff --git a/dlls/wined3d/decoder.c b/dlls/wined3d/decoder.c index e335adce7cb..bf59265c128 100644 --- a/dlls/wined3d/decoder.c +++ b/dlls/wined3d/decoder.c @@ -1367,6 +1367,8 @@ struct wined3d_decoder_va_vk { VkImage image; uint64_t command_buffer_id; + bool valid, used; + uint8_t dxva_index; } images[VA_DECODER_SURFACE_COUNT]; }; @@ -1395,13 +1397,12 @@ static void wined3d_decoder_va_vk_get_profiles(struct wined3d_adapter *adapter, WINE_UNIX_CALL(unix_va_get_profiles_vk, ¶ms); } -static void wined3d_decoder_va_vk_cs_init(void *object) +static void wined3d_decoder_va_vk_create_va_decoder(struct wined3d_decoder_va_vk *decoder_va, + struct wined3d_context_vk *context_vk) { - struct wined3d_decoder_va_vk *decoder_va = object; struct wined3d_adapter_vk *adapter_vk = wined3d_adapter_vk(decoder_va->d.device->adapter); struct wined3d_device_vk *device_vk = wined3d_device_vk(decoder_va->d.device); struct va_decoder_create_vk_params params; - struct wined3d_context_vk *context_vk; HRESULT hr; params.desc = decoder_va->d.desc; @@ -1418,8 +1419,6 @@ static void wined3d_decoder_va_vk_cs_init(void *object) decoder_va->va_decoder = params.decoder; - context_vk = wined3d_context_vk(context_acquire(&device_vk->d, NULL, 0)); - for (unsigned int i = 0; i < VA_DECODER_SURFACE_COUNT; ++i) { struct wined3d_decoder_image_va_vk *image = &decoder_va->images[i]; @@ -1434,7 +1433,15 @@ static void wined3d_decoder_va_vk_cs_init(void *object) VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, image->image, &vk_range); } +} +static void wined3d_decoder_va_vk_cs_init(void *object) +{ + struct wined3d_decoder_va_vk *decoder_va = object; + struct wined3d_context_vk *context_vk; + + context_vk = wined3d_context_vk(context_acquire(decoder_va->d.device, NULL, 0)); + wined3d_decoder_va_vk_create_va_decoder(decoder_va, context_vk); context_release(&context_vk->c); } @@ -1477,16 +1484,11 @@ void wined3d_decoder_va_vk_destroy_va_decoder(struct wined3d_device_vk *device_v WINE_UNIX_CALL(unix_va_decoder_destroy_vk, ¶ms); } -static void wined3d_decoder_va_vk_destroy_object(void *object) +static void wined3d_decoder_va_vk_cleanup(struct wined3d_decoder_va_vk *decoder_va, + struct wined3d_context_vk *context_vk) { - struct wined3d_decoder_va_vk *decoder_va = object; - struct wined3d_context_vk *context_vk; uint64_t command_buffer_id = 0; - TRACE("decoder_va %p.\n", decoder_va); - - context_vk = wined3d_context_vk(context_acquire(decoder_va->d.device, NULL, 0)); - for (unsigned int i = 0; i < ARRAY_SIZE(decoder_va->images); ++i) { struct wined3d_decoder_image_va_vk *image = &decoder_va->images[i]; @@ -1499,9 +1501,19 @@ static void wined3d_decoder_va_vk_destroy_object(void *object) * a Vulkan object, but we did allocate Vulkan memory on the Unix side * which we need to make sure isn't freed until the GPU is done with it. */ wined3d_context_vk_destroy_va_decoder(context_vk, decoder_va->va_decoder, command_buffer_id); - free(decoder_va); +} + +static void wined3d_decoder_va_vk_destroy_object(void *object) +{ + struct wined3d_decoder_va_vk *decoder_va = object; + struct wined3d_context_vk *context_vk; + + TRACE("decoder_va %p.\n", decoder_va); + context_vk = wined3d_context_vk(context_acquire(decoder_va->d.device, NULL, 0)); + wined3d_decoder_va_vk_cleanup(decoder_va, context_vk); context_release(&context_vk->c); + free(decoder_va); } static void wined3d_decoder_va_vk_destroy(struct wined3d_decoder *decoder) @@ -1512,11 +1524,185 @@ static void wined3d_decoder_va_vk_destroy(struct wined3d_decoder *decoder) wined3d_cs_destroy_object(decoder->device->cs, wined3d_decoder_va_vk_destroy_object, decoder_va); } +static bool decoder_va_vk_find_reference_slot(struct wined3d_decoder_va_vk *decoder_va, + uint8_t dxva_index, unsigned int *slot) +{ + for (unsigned int i = 0; i < VA_DECODER_SURFACE_COUNT; ++i) + { + if (decoder_va->images[i].valid && decoder_va->images[i].dxva_index == dxva_index) + { + *slot = i; + return true; + } + } + + ERR("Reference index %u was never written.\n", dxva_index); + return false; +} + +static unsigned int decoder_va_vk_find_available_output_slot(struct wined3d_decoder_va_vk *decoder_va, + struct wined3d_context_vk *context_vk) +{ + uint64_t earliest_command_buffer_id = UINT64_MAX; + unsigned int earliest_slot = 0; + + for (unsigned int i = 0; i < VA_DECODER_SURFACE_COUNT; ++i) + { + struct wined3d_decoder_image_va_vk *image = &decoder_va->images[i]; + + if (image->used) + continue; + + if (!image->valid || context_vk->completed_command_buffer_id >= image->command_buffer_id) + return i; + + if (image->command_buffer_id < earliest_command_buffer_id) + { + earliest_command_buffer_id = image->command_buffer_id; + earliest_slot = i; + } + } + + /* We need to make sure that Vulkan is done reading from this image. + * Unfortunately, we cannot do GPU-side synchronization between VA and + * Vulkan. We need to do a CPU wait. */ + if (earliest_command_buffer_id == context_vk->current_command_buffer.id) + wined3d_context_vk_submit_command_buffer(context_vk, 0, NULL, NULL, 0, NULL); + wined3d_context_vk_wait_command_buffer(context_vk, earliest_command_buffer_id); + return earliest_slot; +} + +/* Copy from the shared VA image to the application destination image. */ +static void blit_va_image(struct wined3d_decoder_va_vk *decoder_va, struct wined3d_context_vk *context_vk, + unsigned int output_idx, struct wined3d_decoder_output_view_vk *output_view_vk) +{ + struct wined3d_texture_vk *texture_vk = wined3d_texture_vk(output_view_vk->v.texture); + unsigned int sub_resource_idx = output_view_vk->v.desc.u.texture.layer_idx; + const struct wined3d_vk_info *vk_info = context_vk->vk_info; + VkCommandBuffer command_buffer; + VkImageSubresourceRange range; + VkImageLayout dst_layout; + VkImageCopy region = {0}; + + command_buffer = wined3d_context_vk_get_command_buffer(context_vk); + + if (texture_vk->layout == VK_IMAGE_LAYOUT_GENERAL) + dst_layout = VK_IMAGE_LAYOUT_GENERAL; + else + dst_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + + range.aspectMask = vk_aspect_mask_from_format(texture_vk->t.resource.format); + range.baseMipLevel = sub_resource_idx % texture_vk->t.level_count; + range.levelCount = 1; + range.baseArrayLayer = sub_resource_idx / texture_vk->t.level_count; + range.layerCount = 1; + + wined3d_context_vk_image_barrier(context_vk, command_buffer, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + vk_access_mask_from_bind_flags(texture_vk->t.resource.bind_flags), + VK_ACCESS_TRANSFER_WRITE_BIT, + texture_vk->layout, dst_layout, texture_vk->image.vk_image, &range); + + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT; + region.srcSubresource.baseArrayLayer = 0; + region.srcSubresource.layerCount = 1; + region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT; + region.dstSubresource.baseArrayLayer = sub_resource_idx; + region.dstSubresource.layerCount = 1; + region.extent.width = decoder_va->d.width; + region.extent.height = decoder_va->d.height; + region.extent.depth = 1; + + VK_CALL(vkCmdCopyImage(command_buffer, decoder_va->images[output_idx].image, + VK_IMAGE_LAYOUT_GENERAL, texture_vk->image.vk_image, dst_layout, 1, ®ion)); + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT; + region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT; + region.extent.width /= 2; + region.extent.height /= 2; + VK_CALL(vkCmdCopyImage(command_buffer, decoder_va->images[output_idx].image, + VK_IMAGE_LAYOUT_GENERAL, texture_vk->image.vk_image, dst_layout, 1, ®ion)); + + wined3d_context_vk_image_barrier(context_vk, command_buffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, + vk_access_mask_from_bind_flags(texture_vk->t.resource.bind_flags), + dst_layout, texture_vk->layout, texture_vk->image.vk_image, &range); + + decoder_va->images[output_idx].command_buffer_id = context_vk->current_command_buffer.id; +} + static void wined3d_decoder_va_vk_decode(struct wined3d_context *context, struct wined3d_decoder *decoder, struct wined3d_decoder_output_view *output_view, unsigned int bitstream_size, unsigned int slice_control_size) { - FIXME("Not implemented.\n"); + struct wined3d_decoder_output_view_vk *output_view_vk = wined3d_decoder_output_view_vk(output_view); + const DXVA_PicParams_H264 *h264_params = wined3d_buffer_load_sysmem(decoder->parameters, context); + struct wined3d_decoder_va_vk *decoder_va = wined3d_decoder_va_vk(decoder); + unsigned int sub_resource_idx = output_view_vk->v.desc.u.texture.layer_idx; + struct wined3d_context_vk *context_vk = wined3d_context_vk(context); + struct wined3d_texture *texture = output_view->texture; + struct va_decoder_decode_params params; + unsigned int output_idx, width, height; + + wined3d_texture_prepare_location(texture, sub_resource_idx, &context_vk->c, WINED3D_LOCATION_TEXTURE_RGB); + wined3d_texture_validate_location(texture, sub_resource_idx, WINED3D_LOCATION_TEXTURE_RGB); + wined3d_texture_invalidate_location(texture, sub_resource_idx, ~WINED3D_LOCATION_TEXTURE_RGB); + + for (unsigned int i = 0; i < VA_DECODER_SURFACE_COUNT; ++i) + decoder_va->images[i].used = false; + + for (unsigned int i = 0; i < ARRAY_SIZE(h264_params->RefFrameList); ++i) + { + unsigned int slot_index; + + if (h264_params->RefFrameList[i].bPicEntry == 0xff) + continue; + + /* NVidia's DXVA implementation apparently expects each frame to appear + * in its own references list. VA does not expect or need this. */ + if (h264_params->RefFrameList[i].Index7Bits == h264_params->CurrPic.Index7Bits) + continue; + + if (!decoder_va_vk_find_reference_slot(decoder_va, h264_params->RefFrameList[i].Index7Bits, &slot_index)) + return; + + decoder_va->images[slot_index].used = true; + } + + output_idx = decoder_va_vk_find_available_output_slot(decoder_va, context_vk); + decoder_va->images[output_idx].valid = true; + decoder_va->images[output_idx].dxva_index = h264_params->CurrPic.Index7Bits; + + width = (h264_params->wFrameWidthInMbsMinus1 + 1) * 16; + height = (h264_params->wFrameHeightInMbsMinus1 + 1) * 16; + if (width != decoder_va->d.width || height != decoder_va->d.height) + { + TRACE("Dynamic resize from %ux%u to %ux%u.\n", decoder_va->d.width, decoder_va->d.height, width, height); + wined3d_decoder_va_vk_cleanup(decoder_va, context_vk); + decoder_va->d.width = width; + decoder_va->d.height = height; + wined3d_decoder_va_vk_create_va_decoder(decoder_va, context_vk); + } + + params.decoder = decoder_va->va_decoder; + params.bitstream = (uintptr_t)wined3d_buffer_load_sysmem(decoder_va->d.bitstream, context); + params.parameters = (uintptr_t)wined3d_buffer_load_sysmem(decoder_va->d.parameters, context); + params.matrix = (uintptr_t)wined3d_buffer_load_sysmem(decoder_va->d.matrix, context); + params.slice_control = (uintptr_t)wined3d_buffer_load_sysmem(decoder_va->d.slice_control, context); + params.bitstream_size = bitstream_size; + params.slice_control_size = slice_control_size; + params.output_idx = output_idx; + WINE_UNIX_CALL(unix_va_decoder_decode, ¶ms); + + blit_va_image(decoder_va, context_vk, output_idx, output_view_vk); + + EnterCriticalSection(&decoder_va->d.feedback_cs); + decoder_va->d.feedback_number = h264_params->StatusReportFeedbackNumber; + decoder_va->d.feedback_pic_entry = h264_params->CurrPic.bPicEntry; + decoder_va->d.feedback_field = h264_params->field_pic_flag; + LeaveCriticalSection(&decoder_va->d.feedback_cs); + + wined3d_context_vk_reference_texture(context_vk, wined3d_texture_vk(texture)); } const struct wined3d_decoder_ops wined3d_decoder_va_vk_ops = diff --git a/dlls/wined3d/unixlib.h b/dlls/wined3d/unixlib.h index ec53d636afb..6e5a83da21a 100644 --- a/dlls/wined3d/unixlib.h +++ b/dlls/wined3d/unixlib.h @@ -58,11 +58,20 @@ struct va_decoder_destroy_vk_params UINT64 decoder; }; +struct va_decoder_decode_params +{ + UINT64 decoder; + UINT64 bitstream, parameters, matrix, slice_control; + UINT32 bitstream_size, slice_control_size; + UINT32 output_idx; +}; + enum unix_funcs { unix_va_get_profiles_vk, unix_va_decoder_create_vk, unix_va_decoder_destroy_vk, + unix_va_decoder_decode, }; #endif diff --git a/dlls/wined3d/va.c b/dlls/wined3d/va.c index 53f9797892b..581618661fc 100644 --- a/dlls/wined3d/va.c +++ b/dlls/wined3d/va.c @@ -49,6 +49,12 @@ struct va_decoder VAContextID context; VASurfaceID surfaces[VA_DECODER_SURFACE_COUNT]; VkDeviceMemory vk_memory[VA_DECODER_SURFACE_COUNT]; + struct + { + bool valid; + uint8_t dxva_index; + } references[VA_DECODER_SURFACE_COUNT]; + bool long_slice_info; }; static NTSTATUS open_va_display(UINT64 handle, VADisplay *ret_display, int *ret_fd) @@ -205,8 +211,8 @@ static NTSTATUS va_decoder_create_vk(void *args) goto fail; } - if ((status = vaCreateContext(decoder->display, decoder->config, params->desc.width, params->desc.height, - VA_PROGRESSIVE, decoder->surfaces, ARRAY_SIZE(decoder->surfaces), &decoder->context)) != VA_STATUS_SUCCESS) + if ((status = vaCreateContext(decoder->display, decoder->config, params->width, params->height, VA_PROGRESSIVE, + decoder->surfaces, ARRAY_SIZE(decoder->surfaces), &decoder->context)) != VA_STATUS_SUCCESS) { ERR("Failed to create context, error %#x.\n", status); vaDestroySurfaces(decoder->display, decoder->surfaces, VA_DECODER_SURFACE_COUNT); @@ -318,6 +324,8 @@ static NTSTATUS va_decoder_create_vk(void *args) params->surfaces[i].image = image; } + decoder->long_slice_info = params->desc.long_slice_info; + TRACE("Created VA decoder %p.\n", decoder); params->decoder = (uintptr_t)decoder; @@ -345,11 +353,233 @@ static NTSTATUS va_decoder_destroy_vk(void *args) return S_OK; } +static bool find_reference_slot(struct va_decoder *decoder, uint8_t dxva_index, unsigned int *idx) +{ + for (unsigned int i = 0; i < VA_DECODER_SURFACE_COUNT; ++i) + { + if (decoder->references[i].valid && decoder->references[i].dxva_index == dxva_index) + { + *idx = i; + return true; + } + } + + ERR("Reference index %u was never written.\n", dxva_index); + return false; +} + +static NTSTATUS va_decoder_decode(void *args) +{ + struct va_decoder_decode_params *params = args; + const DXVA_PicParams_H264 *dxva_params = (const void *)(uintptr_t)params->parameters; + const DXVA_Qmatrix_H264 *dxva_matrix = (const void *)(uintptr_t)params->matrix; + struct va_decoder *decoder = (struct va_decoder *)(uintptr_t)params->decoder; + VAPictureParameterBufferH264 va_params = {0}; + VAIQMatrixBufferH264 va_matrix; + unsigned int ref_count = 0; + unsigned int slice_count; + VABufferID buffers[3]; + VAStatus status; + + if (decoder->long_slice_info) + slice_count = params->slice_control_size / sizeof(DXVA_Slice_H264_Long); + else + slice_count = params->slice_control_size / sizeof(DXVA_Slice_H264_Short); + + if ((status = vaBeginPicture(decoder->display, decoder->context, + decoder->surfaces[params->output_idx])) != VA_STATUS_SUCCESS) + ERR("Failed to begin picture, error %#x.\n", status); + + for (unsigned int i = 0; i < slice_count; ++i) + { + VASliceParameterBufferH264 slice_params = {0}; + VABufferID slice_buffer; + + if (decoder->long_slice_info) + { + const DXVA_Slice_H264_Long *slices = (const void *)(uintptr_t)params->slice_control; + + /* VA doesn't want the start codes. */ + slice_params.slice_data_size = slices[i].SliceBytesInBuffer - 3; + slice_params.slice_data_offset = slices[i].BSNALunitDataLocation + 3; + slice_params.slice_data_flag = VA_SLICE_DATA_FLAG_ALL; + + slice_params.num_ref_idx_l0_active_minus1 = slices[i].num_ref_idx_l0_active_minus1; + slice_params.num_ref_idx_l1_active_minus1 = slices[i].num_ref_idx_l1_active_minus1; + slice_params.slice_type = slices[i].slice_type; + } + else + { + const DXVA_Slice_H264_Short *slices = (const void *)(uintptr_t)params->slice_control; + + slice_params.slice_data_size = slices[i].SliceBytesInBuffer - 3; + slice_params.slice_data_offset = slices[i].BSNALunitDataLocation + 3; + slice_params.slice_data_flag = VA_SLICE_DATA_FLAG_ALL; + + /* FIXME: We can't fill any of the other parameters. + * Mesa doesn't care about most of them, but it does care about + * these two. However, it also treats them as per-picture, and we + * have the "default" values presumably from the PPS, so provide + * them here. */ + slice_params.num_ref_idx_l0_active_minus1 = dxva_params->num_ref_idx_l0_active_minus1; + slice_params.num_ref_idx_l1_active_minus1 = dxva_params->num_ref_idx_l1_active_minus1; + } + + if ((status = vaCreateBuffer(decoder->display, decoder->context, VASliceParameterBufferType, + sizeof(slice_params), 1, &slice_params, &slice_buffer)) != VA_STATUS_SUCCESS) + ERR("Failed to create slice parameters, error %#x.\n", status); + + if ((status = vaRenderPicture(decoder->display, decoder->context, + &slice_buffer, 1)) != VA_STATUS_SUCCESS) + ERR("Failed to render slice parameters, error %#x.\n", status); + + vaDestroyBuffer(decoder->display, slice_buffer); + } + + TRACE("Decoding frame %02x/%02x/%u, RefPicFlag %#x, reference frames", + dxva_params->CurrPic.bPicEntry, dxva_params->frame_num, params->output_idx, dxva_params->RefPicFlag); + + for (unsigned int i = 0; i < ARRAY_SIZE(dxva_params->RefFrameList); ++i) + { + unsigned int field_flags = ((dxva_params->UsedForReferenceFlags >> (2 * i)) & 3u); + VAPictureH264 *va_ref = &va_params.ReferenceFrames[ref_count]; + unsigned int ref_idx; + + if (dxva_params->RefFrameList[i].bPicEntry == 0xff) + continue; + + /* NVidia's DXVA implementation apparently expects each frame to appear + * in its own references list. VA does not expect or need this. */ + if (dxva_params->RefFrameList[i].Index7Bits == dxva_params->CurrPic.Index7Bits) + continue; + + if (!find_reference_slot(decoder, dxva_params->RefFrameList[i].Index7Bits, &ref_idx)) + return E_FAIL; + + TRACE(" %02x/%02x/%u", dxva_params->RefFrameList[i].bPicEntry, dxva_params->FrameNumList[i], ref_idx); + + va_ref->picture_id = decoder->surfaces[ref_idx]; + va_ref->frame_idx = dxva_params->FrameNumList[i]; + if (dxva_params->RefFrameList[i].AssociatedFlag) + va_ref->flags = VA_PICTURE_H264_LONG_TERM_REFERENCE; + else + va_ref->flags = VA_PICTURE_H264_SHORT_TERM_REFERENCE; + + if (field_flags == 1) + va_ref->flags |= VA_PICTURE_H264_TOP_FIELD; + else if (field_flags == 2) + va_ref->flags |= VA_PICTURE_H264_BOTTOM_FIELD; + + va_ref->TopFieldOrderCnt = dxva_params->FieldOrderCntList[i][0]; + va_ref->BottomFieldOrderCnt = dxva_params->FieldOrderCntList[i][1]; + + ++ref_count; + } + + for (unsigned int i = ref_count; i < ARRAY_SIZE(va_params.ReferenceFrames); ++i) + { + va_params.ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID; + va_params.ReferenceFrames[i].picture_id = VA_INVALID_SURFACE; + } + + TRACE(".\n"); + + va_params.CurrPic.picture_id = decoder->surfaces[params->output_idx]; + va_params.CurrPic.frame_idx = dxva_params->frame_num; + /* FIXME: What on earth do we put here? For some reason DXVA supplies these + * flags for reference frames, but not for the current frame. + * Mesa doesn't care about most of these, but it does care whether the + * current frame is a bottom field. */ + va_params.CurrPic.flags = 0; + va_params.CurrPic.TopFieldOrderCnt = dxva_params->CurrFieldOrderCnt[0]; + va_params.CurrPic.BottomFieldOrderCnt = dxva_params->CurrFieldOrderCnt[1]; + + va_params.picture_width_in_mbs_minus1 = dxva_params->wFrameWidthInMbsMinus1; + va_params.picture_height_in_mbs_minus1 = dxva_params->wFrameHeightInMbsMinus1; + va_params.bit_depth_luma_minus8 = dxva_params->bit_depth_luma_minus8; + va_params.bit_depth_chroma_minus8 = dxva_params->bit_depth_chroma_minus8; + va_params.num_ref_frames = dxva_params->num_ref_frames; + va_params.seq_fields.bits.chroma_format_idc = dxva_params->chroma_format_idc; + va_params.seq_fields.bits.residual_colour_transform_flag = dxva_params->residual_colour_transform_flag; + /* We don't have this value, so we have to say it's allowed. */ + va_params.seq_fields.bits.gaps_in_frame_num_value_allowed_flag = 1; + va_params.seq_fields.bits.frame_mbs_only_flag = dxva_params->frame_mbs_only_flag; + /* We don't have mb_adaptive_frame_field_flag, but we do have MbaffFrameFlag + * which is (mb_adaptive_frame_field_flag && !field_pic_flag). + * If field_pic_flag is 1, we don't know, so we set it to 1, which is the + * less constrained option. */ + if (!dxva_params->field_pic_flag) + va_params.seq_fields.bits.mb_adaptive_frame_field_flag = dxva_params->MbaffFrameFlag; + else + va_params.seq_fields.bits.mb_adaptive_frame_field_flag = 1; + va_params.seq_fields.bits.direct_8x8_inference_flag = dxva_params->direct_8x8_inference_flag; + va_params.seq_fields.bits.MinLumaBiPredSize8x8 = dxva_params->MinLumaBipredSize8x8Flag; + va_params.seq_fields.bits.log2_max_frame_num_minus4 = dxva_params->log2_max_frame_num_minus4; + va_params.seq_fields.bits.pic_order_cnt_type = dxva_params->pic_order_cnt_type; + va_params.seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 = dxva_params->log2_max_pic_order_cnt_lsb_minus4; + va_params.seq_fields.bits.delta_pic_order_always_zero_flag = dxva_params->delta_pic_order_always_zero_flag; + /* The slice group fields are "va_deprecated" and GStreamer doesn't fill them. */ + va_params.pic_init_qp_minus26 = dxva_params->pic_init_qp_minus26; + va_params.pic_init_qs_minus26 = dxva_params->pic_init_qs_minus26; + va_params.chroma_qp_index_offset = dxva_params->chroma_qp_index_offset; + va_params.second_chroma_qp_index_offset = dxva_params->second_chroma_qp_index_offset; + va_params.pic_fields.bits.entropy_coding_mode_flag = dxva_params->entropy_coding_mode_flag; + va_params.pic_fields.bits.weighted_pred_flag = dxva_params->weighted_pred_flag; + va_params.pic_fields.bits.weighted_bipred_idc = dxva_params->weighted_bipred_idc; + va_params.pic_fields.bits.transform_8x8_mode_flag = dxva_params->transform_8x8_mode_flag; + va_params.pic_fields.bits.field_pic_flag = dxva_params->field_pic_flag; + va_params.pic_fields.bits.constrained_intra_pred_flag = dxva_params->constrained_intra_pred_flag; + va_params.pic_fields.bits.pic_order_present_flag = dxva_params->pic_order_present_flag; + va_params.pic_fields.bits.deblocking_filter_control_present_flag = dxva_params->deblocking_filter_control_present_flag; + va_params.pic_fields.bits.redundant_pic_cnt_present_flag = dxva_params->redundant_pic_cnt_present_flag; + /* This seems to be equivalent, although the GStreamer code is not the easiest to follow. */ + va_params.pic_fields.bits.reference_pic_flag = dxva_params->RefPicFlag; + /* This too. */ + va_params.frame_num = dxva_params->frame_num; + + decoder->references[params->output_idx].valid = true; + decoder->references[params->output_idx].dxva_index = dxva_params->CurrPic.Index7Bits; + + /* The DXVA and VA matrices are byte-compatible. */ + memcpy(&va_matrix, dxva_matrix, sizeof(*dxva_matrix)); + memset(va_matrix.va_reserved, 0, sizeof(va_matrix.va_reserved)); + + /* The parameters need to be submitted first, or Mesa fails. */ + + if ((status = vaCreateBuffer(decoder->display, decoder->context, VAPictureParameterBufferType, + sizeof(va_params), 1, &va_params, &buffers[0])) != VA_STATUS_SUCCESS) + ERR("Failed to create parameters buffer, error %#x.\n", status); + + if ((status = vaCreateBuffer(decoder->display, decoder->context, VAIQMatrixBufferType, + sizeof(va_matrix), 1, &va_matrix, &buffers[1])) != VA_STATUS_SUCCESS) + ERR("Failed to create parameters buffer, error %#x.\n", status); + + if ((status = vaCreateBuffer(decoder->display, decoder->context, VASliceDataBufferType, + params->bitstream_size, 1, (void *)(uintptr_t)params->bitstream, &buffers[2])) != VA_STATUS_SUCCESS) + ERR("Failed to create bitstream buffer, error %#x.\n", status); + + if ((status = vaRenderPicture(decoder->display, decoder->context, + buffers, ARRAY_SIZE(buffers))) != VA_STATUS_SUCCESS) + ERR("Failed to render buffers, error %#x.\n", status); + + vaDestroyBuffer(decoder->display, buffers[0]); + vaDestroyBuffer(decoder->display, buffers[1]); + vaDestroyBuffer(decoder->display, buffers[2]); + + if ((status = vaEndPicture(decoder->display, decoder->context)) != VA_STATUS_SUCCESS) + ERR("Failed to end picture, error %#x.\n", status); + + vaSyncSurface(decoder->display, decoder->surfaces[params->output_idx]); + + return S_OK; +} + const unixlib_entry_t __wine_unix_call_funcs[] = { [unix_va_get_profiles_vk] = va_get_profiles_vk, [unix_va_decoder_create_vk] = va_decoder_create_vk, [unix_va_decoder_destroy_vk] = va_decoder_destroy_vk, + [unix_va_decoder_decode] = va_decoder_decode, }; const unixlib_entry_t __wine_unix_call_wow64_funcs[] = @@ -357,6 +587,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = [unix_va_get_profiles_vk] = va_get_profiles_vk, [unix_va_decoder_create_vk] = va_decoder_create_vk, [unix_va_decoder_destroy_vk] = va_decoder_destroy_vk, + [unix_va_decoder_decode] = va_decoder_decode, }; #endif -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11535
I was halfway through trying to convert things to use the host vkCreateImage when I realized it doesn't actually make things any more future-proof. The problem is that in the case of a dedicated allocation we need to unwrap the image, but currently that's a no-op. So either way there's a no-op conversion between PE and Unix VkImage that risks getting missed. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11535#note_147505
On Mon Aug 3 08:29:08 2026 +0000, Elizabeth Figura wrote:
I was halfway through trying to convert things to use the host vkCreateImage when I realized it doesn't actually make things any more future-proof. The problem is that in the case of a dedicated allocation we need to unwrap the image, but currently that's a no-op. So either way there's a no-op conversion between PE and Unix VkImage that risks getting missed. Yeah, and it seems brittle to me, if we end up needing a VkImage wrapper we might miss this. It seems cleaner to me to have unix-created objects be used on the unix side only, while PE-side created object might be used across sides more freely.
Also, as you say we might want to skip the blit later on, and share the resource directly, it seems to me that there's a lot of synchronization aspects that simply will require a proper shared resource, so IMO might be better to investigate that way already. Then this MR doesn't require any change in win32u, so if you feel this is good enough for now I don't really mind. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11535#note_147630
This merge request was approved by Rémi Bernon. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11535
On Fri Jul 31 17:29:27 2026 +0000, Elizabeth Figura wrote:
changed this line in [version 2 of the diff](/wine/wine/-/merge_requests/11535/diffs?diff_id=286237&start_sha=4aa9f41d1140a094b6afd77e4080742c06f00731#dcb06a041ed868c4edf96285c55f6258da0c3321_367_367) I think if we need interop between wrapped/PE side and unwrapped/Unix side objects the safest way would be to use D3DKMT shared resources, either with global or NT handles. We would need to add DMA-BUF support to `d3dkmt_create_resource`, or allocate a temporary unix-only Vulkan image + memory and export it back as opaque fd, and expose the D3DKMT function. That could even be a Wine-specific `D3DKMTCreateAllocation` extension if we want to try staying close to native. The PE side could then create a resource and import these handles, as a full featured PE-side object. That would also even fit nicely with a shared resources implementation.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11535#note_147629
I think if we need interop between wrapped/PE side and unwrapped/Unix side objects the safest way would be to use D3DKMT shared resources, either with global or NT handles. We would need to add DMA-BUF support to `d3dkmt_create_resource`,
Sure. I'm not convinced the extra work is worth the risk of VkImage being wrapped though. I can't imagine any reason why it would be.
or allocate a temporary unix-only Vulkan image + memory and export it back as opaque fd, and expose the D3DKMT function.
That would require an unnecessary extra copy.
Also, as you say we might want to skip the blit later on, and share the resource directly, it seems to me that there's a lot of synchronization aspects that simply will require a proper shared resource, so IMO might be better to investigate that way already.
I don't know what you mean by this, but VA synchronization is implicit; there's nothing we will need to do there. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11535#note_147700
Alexandre Julliard (@julliard) commented about configure.ac:
WINE_NOTICE_WITH(v4l2,[test "x$ac_cv_lib_soname_v4l2" = "x"], [libv4l2 ${notice_platform}development files not found.])
+dnl **** Check for libva **** +if test "x$with_va" != "xno" +then + WINE_PACKAGE_FLAGS(VA,[libva libva-drm],[-lva-drm -lva],,, + [AC_CHECK_HEADER([va/va_drm.h], + [AC_CHECK_FUNC(vaGetDisplayDRM, + [AC_DEFINE(HAVE_VA, 1, [Define to 1 if you have the 'va' library (-lva).])], This should use WINE_CHECK_SONAME and dynamic loading, like we do for other library dependencies.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11535#note_148196
On Thu Aug 6 17:43:58 2026 +0000, Alexandre Julliard wrote:
This should use WINE_CHECK_SONAME and dynamic loading, like we do for other library dependencies. I'm not sure I understand why, sorry? Obviously not all libraries are loaded dynamically, and my understanding is that those that are linked directly are those which aren't useful without the library. wined3d's unixlib currently falls in that category; obviously the DLL as a whole is still useful but if va isn't available we can leave out the unixlib.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11535#note_148197
On Thu Aug 6 17:57:23 2026 +0000, Elizabeth Figura wrote:
I'm not sure I understand why, sorry? Obviously not all libraries are loaded dynamically, and my understanding is that those that are linked directly are those which aren't useful without the library. wined3d's unixlib currently falls in that category; obviously the DLL as a whole is still useful but if va isn't available we can leave out the unixlib. My understanding was that there are plans to put more things in the wined3d unixlib. If that's not the case then sure, we can have it fail to load.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11535#note_148210
On Thu Aug 6 18:31:13 2026 +0000, Alexandre Julliard wrote:
My understanding was that there are plans to put more things in the wined3d unixlib. If that's not the case then sure, we can have it fail to load. That's a fair point; I wasn't sure it would happen any time soon, but it's probably better to be safe here. I'll change it to load dynamically.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11535#note_148332
participants (4)
-
Alexandre Julliard (@julliard) -
Elizabeth Figura -
Elizabeth Figura (@zfigura) -
Rémi Bernon (@rbernon)