[PATCH v4 0/3] MR11346: dxva2: Implement DXVA2 decoder capability queries via VA-API.
GetDecoderDeviceGuids, GetDecoderRenderTargets and GetDecoderConfigurations were E_NOTIMPL stubs, the root cause of hardware video decode never working through the legacy DXVA2 API (apps fall back to software decode after the first negotiation call fails). Query VA-API directly for supported profiles and report the matching DXVA2 GUIDs (H.264, HEVC Main/Main10) and render target formats, mirroring the equivalent D3D11/Media Foundation path Wine already supports via winegstreamer. Signed-off-by: Valerian Mayega <valerian@gmail.com> -- v4: dxva2: Implement HEVC decode (CreateVideoDecoder and Execute). https://gitlab.winehq.org/wine/wine/-/merge_requests/11346
From: Valerian Mayega <valerian@gmail.com> GetDecoderDeviceGuids, GetDecoderRenderTargets and GetDecoderConfigurations were E_NOTIMPL stubs, the root cause of hardware video decode never working through the legacy DXVA2 API (apps fall back to software decode after the first negotiation call fails). Query VA-API directly for supported profiles and report the matching DXVA2 GUIDs (H.264, HEVC Main/Main10) and render target formats, mirroring the equivalent D3D11/Media Foundation path Wine already supports via winegstreamer. Signed-off-by: Valerian Mayega <valerian@gmail.com> --- configure | 89 +++++++++++++++++ configure.ac | 16 ++++ dlls/dxva2/Makefile.in | 6 +- dlls/dxva2/main.c | 133 +++++++++++++++++++++++-- dlls/dxva2/unixlib.c | 213 +++++++++++++++++++++++++++++++++++++++++ dlls/dxva2/unixlib.h | 50 ++++++++++ include/config.h.in | 9 ++ 7 files changed, 509 insertions(+), 7 deletions(-) create mode 100644 dlls/dxva2/unixlib.c create mode 100644 dlls/dxva2/unixlib.h diff --git a/configure b/configure index 1834f20d30d..cb6c4d64274 100755 --- a/configure +++ b/configure @@ -654,6 +654,8 @@ OSS4_LIBS OSS4_CFLAGS ALSA_CFLAGS ALSA_LIBS +LIBVA_LIBS +LIBVA_CFLAGS GSTREAMER_LIBS GSTREAMER_CFLAGS FFMPEG_LIBS @@ -970,6 +972,7 @@ with_sdl with_udev with_usb with_v4l2 +with_va with_vulkan with_wayland with_xcomposite @@ -1943,6 +1946,8 @@ FFMPEG_CFLAGS FFMPEG_LIBS GSTREAMER_CFLAGS GSTREAMER_LIBS +LIBVA_CFLAGS +LIBVA_LIBS ALSA_CFLAGS ALSA_LIBS UDEV_CFLAGS @@ -2638,6 +2643,8 @@ Optional Packages: --without-udev do not use udev (plug and play support) --without-usb do not use the libusb library --without-v4l2 do not use v4l2 (video capture) + --without-va do not use libva (DXVA2 hardware video decode + support) --without-vulkan do not use Vulkan --without-wayland do not build the Wayland driver --without-xcomposite do not use the Xcomposite extension @@ -2848,6 +2855,9 @@ Some influential environment variables: GSTREAMER_LIBS Linker flags for gstreamer-1.0 gstreamer-video-1.0 gstreamer-audio-1.0 gstreamer-tag-1.0, overriding pkg-config + LIBVA_CFLAGS + C compiler flags for libva libva-drm, overriding pkg-config + LIBVA_LIBS Linker flags for libva libva-drm, overriding pkg-config ALSA_CFLAGS C compiler flags for alsa, overriding pkg-config ALSA_LIBS Linker flags for alsa, overriding pkg-config UDEV_CFLAGS C compiler flags for libudev, overriding pkg-config @@ -4534,6 +4544,13 @@ then : fi +# Check whether --with-va was given. +if test ${with_va+y} +then : + withval=$with_va; +fi + + # Check whether --with-vulkan was given. if test ${with_vulkan+y} then : @@ -18604,6 +18621,76 @@ esac enable_winegstreamer=${enable_winegstreamer:-no} fi +if test "x$with_va" != "xno" +then + rm -f conftest.err +if ${LIBVA_CFLAGS:+false} : +then : + if test ${PKG_CONFIG+y} +then : + LIBVA_CFLAGS=`$PKG_CONFIG --cflags libva libva-drm 2>conftest.err` +fi +fi + +if ${LIBVA_LIBS:+false} : +then : + if test ${PKG_CONFIG+y} +then : + LIBVA_LIBS=`$PKG_CONFIG --libs libva libva-drm 2>/dev/null` +fi +fi + + +printf '%s\n' "$as_me:${as_lineno-$LINENO}: libva libva-drm cflags: $LIBVA_CFLAGS" >&5 +printf '%s\n' "$as_me:${as_lineno-$LINENO}: libva libva-drm libs: $LIBVA_LIBS" >&5 +if test -s conftest.err; then + printf %s "$as_me:${as_lineno-$LINENO}: libva libva-drm errors: " >&5 + cat conftest.err >&5 +fi +rm -f conftest.err +if test -n "$LIBVA_CFLAGS$LIBVA_LIBS" +then : + ac_save_pkg_CPPFLAGS=$CPPFLAGS +ac_save_pkg_LIBS=$LIBS +CPPFLAGS="$CPPFLAGS $LIBVA_CFLAGS" +LIBS="$LIBVA_LIBS $LIBS" +ac_fn_c_check_header_compile "$LINENO" "va/va.h" "ac_cv_header_va_va_h" "$ac_includes_default" +if test "x$ac_cv_header_va_va_h" = xyes +then : + printf '%s\n' "#define HAVE_VA_VA_H 1" >>confdefs.h + +fi +ac_fn_c_check_header_compile "$LINENO" "va/va_drm.h" "ac_cv_header_va_va_drm_h" "$ac_includes_default" +if test "x$ac_cv_header_va_va_drm_h" = xyes +then : + printf '%s\n' "#define HAVE_VA_VA_DRM_H 1" >>confdefs.h + +fi + + if test "$ac_cv_header_va_va_h" = "yes" -a "$ac_cv_header_va_va_drm_h" = "yes" + then + +printf '%s\n' "#define HAVE_LIBVA 1" >>confdefs.h + + else + LIBVA_LIBS="" + fi +CPPFLAGS=$ac_save_pkg_CPPFLAGS +LIBS=$ac_save_pkg_LIBS +fi + +fi +if test "x$LIBVA_LIBS" = x +then : + case "x$with_va" in + x) as_fn_append wine_notices "|libva ${notice_platform}development files not found, DXVA2 hardware video decode won't be supported." ;; + xno) ;; + *) as_fn_error $? "libva ${notice_platform}development files not found, DXVA2 hardware video decode won't be supported. +This is an error since --with-va was requested." "$LINENO" 5 ;; +esac + +fi + ALSA_LIBS="" if test "x$with_alsa" != "xno" @@ -24432,6 +24519,8 @@ FFMPEG_CFLAGS = $FFMPEG_CFLAGS FFMPEG_LIBS = $FFMPEG_LIBS GSTREAMER_CFLAGS = $GSTREAMER_CFLAGS GSTREAMER_LIBS = $GSTREAMER_LIBS +LIBVA_CFLAGS = $LIBVA_CFLAGS +LIBVA_LIBS = $LIBVA_LIBS ALSA_LIBS = $ALSA_LIBS ALSA_CFLAGS = $ALSA_CFLAGS OSS4_CFLAGS = $OSS4_CFLAGS diff --git a/configure.ac b/configure.ac index ee0b1c483d4..22fb06cfb27 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 libva (DXVA2 hardware video decode support)])) 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]), @@ -1773,6 +1774,21 @@ WINE_NOTICE_WITH(gstreamer,[test "x$ac_glib2_broken" != xyes -a "x$ac_cv_func_gs [gstreamer-1.0 base plugins ${notice_platform}development files not found, GStreamer won't be supported.], [enable_winegstreamer]) +dnl **** Check for libva **** +if test "x$with_va" != "xno" +then + WINE_PACKAGE_FLAGS(LIBVA,[libva libva-drm],,,, + [AC_CHECK_HEADERS([va/va.h va/va_drm.h]) + if test "$ac_cv_header_va_va_h" = "yes" -a "$ac_cv_header_va_va_drm_h" = "yes" + then + AC_DEFINE(HAVE_LIBVA, 1, [Define to 1 if you have the libva libraries.]) + else + LIBVA_LIBS="" + fi]) +fi +WINE_NOTICE_WITH(va,[test "x$LIBVA_LIBS" = x], + [libva ${notice_platform}development files not found, DXVA2 hardware video decode won't be supported.]) + dnl **** Check for ALSA 1.x **** AC_SUBST(ALSA_LIBS,"") if test "x$with_alsa" != "xno" diff --git a/dlls/dxva2/Makefile.in b/dlls/dxva2/Makefile.in index de43b161ccd..e02e1df03fd 100644 --- a/dlls/dxva2/Makefile.in +++ b/dlls/dxva2/Makefile.in @@ -1,6 +1,10 @@ MODULE = dxva2.dll IMPORTS = uuid ole32 IMPORTLIB = dxva2 +UNIXLIB = dxva2.so +UNIX_CFLAGS = $(LIBVA_CFLAGS) +UNIX_LIBS = $(LIBVA_LIBS) SOURCES = \ - main.c + main.c \ + unixlib.c diff --git a/dlls/dxva2/main.c b/dlls/dxva2/main.c index c74c0116d22..7ec41555701 100644 --- a/dlls/dxva2/main.c +++ b/dlls/dxva2/main.c @@ -22,6 +22,7 @@ #include <limits.h> #include "windef.h" #include "winbase.h" +#include "objbase.h" #include "d3d9.h" #include "physicalmonitorenumerationapi.h" #include "lowlevelmonitorconfigurationapi.h" @@ -32,9 +33,50 @@ #include "wine/debug.h" +#include "unixlib.h" + WINE_DEFAULT_DEBUG_CHANNEL(dxva2); #define D3DFMT_NV12 MAKEFOURCC('N','V','1','2') +#define D3DFMT_P010 MAKEFOURCC('P','0','1','0') + +static struct dxva2_decoder_profile supported_profiles[DXVA2_MAX_DECODER_PROFILES]; +static UINT supported_profiles_count; +static RTL_RUN_ONCE supported_profiles_once = RTL_RUN_ONCE_INIT; + +static DWORD WINAPI query_supported_profiles( RTL_RUN_ONCE *once, void *param, void **context ) +{ + struct query_decoder_profiles_params params; + + params.profiles = supported_profiles; + params.capacity = ARRAY_SIZE(supported_profiles); + params.count = 0; + + DXVA2_CALL( query_decoder_profiles, ¶ms ); + supported_profiles_count = params.count; + + TRACE( "Found %u usable hardware decoder profile(s).\n", supported_profiles_count ); + + return TRUE; +} + +static void ensure_supported_profiles( void ) +{ + RtlRunOnceExecuteOnce( &supported_profiles_once, query_supported_profiles, NULL, NULL ); +} + +BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, void *reserved ) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls( instance ); + if (__wine_init_unix_call()) + WARN( "Failed to load dxva2 unix library, hardware decode won't be available.\n" ); + break; + } + return TRUE; +} enum device_handle_flags { @@ -688,25 +730,104 @@ static HRESULT WINAPI device_manager_decoder_service_CreateSurface(IDirectXVideo static HRESULT WINAPI device_manager_decoder_service_GetDecoderDeviceGuids(IDirectXVideoDecoderService *iface, UINT *count, GUID **guids) { - FIXME("%p, %p, %p.\n", iface, count, guids); + GUID *ret; + UINT i; - return E_NOTIMPL; + TRACE("%p, %p, %p.\n", iface, count, guids); + + if (!count || !guids) return E_INVALIDARG; + + ensure_supported_profiles(); + + if (!supported_profiles_count) + { + *count = 0; + *guids = NULL; + return E_FAIL; + } + + if (!(ret = CoTaskMemAlloc(supported_profiles_count * sizeof(*ret)))) + return E_OUTOFMEMORY; + + for (i = 0; i < supported_profiles_count; ++i) + ret[i] = supported_profiles[i].guid; + + *count = supported_profiles_count; + *guids = ret; + return S_OK; } static HRESULT WINAPI device_manager_decoder_service_GetDecoderRenderTargets(IDirectXVideoDecoderService *iface, REFGUID guid, UINT *count, D3DFORMAT **formats) { - FIXME("%p, %s, %p, %p.\n", iface, debugstr_guid(guid), count, formats); + D3DFORMAT *ret; + UINT i; - return E_NOTIMPL; + TRACE("%p, %s, %p, %p.\n", iface, debugstr_guid(guid), count, formats); + + if (!count || !formats) return E_INVALIDARG; + + ensure_supported_profiles(); + + for (i = 0; i < supported_profiles_count; ++i) + { + if (!IsEqualGUID(&supported_profiles[i].guid, guid)) continue; + + if (!(ret = CoTaskMemAlloc(sizeof(*ret)))) + return E_OUTOFMEMORY; + + *ret = supported_profiles[i].bitdepth > 8 ? D3DFMT_P010 : D3DFMT_NV12; + *count = 1; + *formats = ret; + return S_OK; + } + + *count = 0; + *formats = NULL; + return E_FAIL; } static HRESULT WINAPI device_manager_decoder_service_GetDecoderConfigurations(IDirectXVideoDecoderService *iface, REFGUID guid, const DXVA2_VideoDesc *video_desc, void *reserved, UINT *count, DXVA2_ConfigPictureDecode **configs) { - FIXME("%p, %s, %p, %p, %p, %p.\n", iface, debugstr_guid(guid), video_desc, reserved, count, configs); + DXVA2_ConfigPictureDecode *ret; + BOOL found = FALSE; + UINT i; - return E_NOTIMPL; + TRACE("%p, %s, %p, %p, %p, %p.\n", iface, debugstr_guid(guid), video_desc, reserved, count, configs); + + if (!count || !configs) return E_INVALIDARG; + + ensure_supported_profiles(); + + for (i = 0; i < supported_profiles_count; ++i) + { + if (IsEqualGUID(&supported_profiles[i].guid, guid)) + { + found = TRUE; + break; + } + } + if (!found) + { + *count = 0; + *configs = NULL; + return E_FAIL; + } + + if (!(ret = CoTaskMemAlloc(sizeof(*ret)))) + return E_OUTOFMEMORY; + + memset(ret, 0, sizeof(*ret)); + /* Slice-level (long-format) bitstream submission - what real VLD drivers + * report, and what FFmpeg-based DXVA2 hwaccel consumers (which is what + * the Xiaomi app's codec plugin looks like internally) require. */ + ret->ConfigBitstreamRaw = 2; + ret->ConfigMinRenderTargetBuffCount = 4; + + *count = 1; + *configs = ret; + return S_OK; } static HRESULT WINAPI device_manager_decoder_service_CreateVideoDecoder(IDirectXVideoDecoderService *iface, diff --git a/dlls/dxva2/unixlib.c b/dlls/dxva2/unixlib.c new file mode 100644 index 00000000000..051c76ba5dc --- /dev/null +++ b/dlls/dxva2/unixlib.c @@ -0,0 +1,213 @@ +/* + * Unix interface for dxva2 VA-API hardware decode capability queries + * + * Copyright 2026 Valerian Mayega + * + * 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" + +#include <stdarg.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> + +#ifdef HAVE_LIBVA +#include <va/va.h> +#include <va/va_drm.h> +#endif + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winternl.h" + +#include "wine/debug.h" +#include "unixlib.h" + +#ifdef HAVE_LIBVA + +WINE_DEFAULT_DEBUG_CHANNEL(dxva2); + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + + +/* DXVA2 decoder mode GUIDs, mirrors include/dxva2api.idl. Defined locally + * since this file builds against the host libc/toolchain, not Wine's PE + * headers, so DEFINE_GUID isn't available here. */ +static const GUID guid_h264_vld_nofgt = + {0x1b81be68, 0xa0c7, 0x11d3, {0xb9,0x84,0x00,0xc0,0x4f,0x2e,0x73,0xc5}}; +static const GUID guid_hevc_vld_main = + {0x5b11d51b, 0x2f4c, 0x4452, {0xbc,0xc3,0x09,0xf2,0xa1,0x16,0x0c,0xc0}}; +static const GUID guid_hevc_vld_main10 = + {0x107af0e0, 0xef1a, 0x4d19, {0xab,0xa8,0x67,0xa1,0x63,0x07,0x3d,0x13}}; + +static VADisplay va_open( int *fd ) +{ + static const char *devices[] = { "/dev/dri/renderD128", "/dev/dri/renderD129", "/dev/dri/card0" }; + VADisplay display; + int major, minor; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(devices); i++) + { + if ((*fd = open( devices[i], O_RDWR )) < 0) continue; + + if (!(display = vaGetDisplayDRM( *fd ))) + { + close( *fd ); + *fd = -1; + continue; + } + + if (vaInitialize( display, &major, &minor ) == VA_STATUS_SUCCESS) + { + TRACE( "Opened VA-API display on %s, version %d.%d.\n", devices[i], major, minor ); + return display; + } + + close( *fd ); + *fd = -1; + } + + WARN( "Could not open a working VA-API display.\n" ); + return NULL; +} + +static NTSTATUS query_decoder_profiles( void *args ) +{ + struct query_decoder_profiles_params *params = args; + VADisplay display; + VAProfile *profiles; + int fd = -1, i, num_profiles; + UINT count = 0; + + params->count = 0; + + if (!(display = va_open( &fd ))) + return STATUS_SUCCESS; + + num_profiles = vaMaxNumProfiles( display ); + if (num_profiles <= 0 || !(profiles = malloc( num_profiles * sizeof(*profiles) ))) + { + vaTerminate( display ); + close( fd ); + return STATUS_SUCCESS; + } + + if (vaQueryConfigProfiles( display, profiles, &num_profiles ) == VA_STATUS_SUCCESS) + { + for (i = 0; i < num_profiles && count < params->capacity; i++) + { + VAConfigAttrib attrib; + const GUID *guid = NULL; + UINT bitdepth = 8; + + switch (profiles[i]) + { + case VAProfileH264ConstrainedBaseline: + case VAProfileH264Main: + case VAProfileH264High: + guid = &guid_h264_vld_nofgt; + break; + case VAProfileHEVCMain: + guid = &guid_hevc_vld_main; + break; + case VAProfileHEVCMain10: + guid = &guid_hevc_vld_main10; + bitdepth = 10; + break; + default: + continue; + } + + attrib.type = VAConfigAttribRTFormat; + if (vaGetConfigAttributes( display, profiles[i], VAEntrypointVLD, &attrib, 1 ) != VA_STATUS_SUCCESS) + continue; + if (attrib.value == VA_ATTRIB_NOT_SUPPORTED) + continue; + + TRACE( "Found usable VA-API profile %d, bitdepth %u.\n", profiles[i], bitdepth ); + + params->profiles[count].guid = *guid; + params->profiles[count].bitdepth = bitdepth; + count++; + } + } + + free( profiles ); + vaTerminate( display ); + close( fd ); + + params->count = count; + return STATUS_SUCCESS; +} + +#else /* HAVE_LIBVA */ + +static NTSTATUS query_decoder_profiles( void *args ) +{ + struct query_decoder_profiles_params *params = args; + params->count = 0; + return STATUS_SUCCESS; +} + +#endif /* HAVE_LIBVA */ + +const unixlib_entry_t __wine_unix_call_funcs[] = +{ + query_decoder_profiles, +}; + +C_ASSERT( ARRAY_SIZE(__wine_unix_call_funcs) == unix_funcs_count ); + +#ifdef _WIN64 + +typedef ULONG PTR32; + +static NTSTATUS wow64_query_decoder_profiles( void *args ) +{ + struct + { + PTR32 profiles; + UINT capacity; + UINT count; + } *params32 = args; + struct query_decoder_profiles_params params = + { + ULongToPtr(params32->profiles), + params32->capacity, + 0, + }; + NTSTATUS status = query_decoder_profiles( ¶ms ); + params32->count = params.count; + return status; +} + +const unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{ + wow64_query_decoder_profiles, +}; + +C_ASSERT( ARRAY_SIZE(__wine_unix_call_wow64_funcs) == unix_funcs_count ); + +#endif /* _WIN64 */ diff --git a/dlls/dxva2/unixlib.h b/dlls/dxva2/unixlib.h new file mode 100644 index 00000000000..36e590fdf4b --- /dev/null +++ b/dlls/dxva2/unixlib.h @@ -0,0 +1,50 @@ +/* + * Unix interface for dxva2 VA-API hardware decode capability queries + * + * Copyright 2026 Valerian Mayega + * + * 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 __DXVA2_UNIXLIB_H +#define __DXVA2_UNIXLIB_H + +#include "windef.h" +#include "wine/unixlib.h" + +#define DXVA2_MAX_DECODER_PROFILES 16 + +struct dxva2_decoder_profile +{ + GUID guid; + UINT bitdepth; +}; + +struct query_decoder_profiles_params +{ + struct dxva2_decoder_profile *profiles; /* [out] caller-allocated array */ + UINT capacity; /* [in] number of entries `profiles` can hold */ + UINT count; /* [out] number of entries filled in */ +}; + +enum unix_funcs +{ + unix_query_decoder_profiles, + unix_funcs_count, +}; + +#define DXVA2_CALL( func, params ) WINE_UNIX_CALL( unix_ ## func, params ) + +#endif /* __DXVA2_UNIXLIB_H */ diff --git a/include/config.h.in b/include/config.h.in index 34f193df1d0..4cdf6b62c83 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -147,6 +147,9 @@ /* Define to 1 if you have the <libudev.h> header file. */ #undef HAVE_LIBUDEV_H +/* Define to 1 if you have the libva libraries. */ +#undef HAVE_LIBVA + /* Define if we have the wayland-egl development environment */ #undef HAVE_LIBWAYLAND_EGL @@ -669,6 +672,12 @@ /* Define to 1 if you have the <valgrind/valgrind.h> header file. */ #undef HAVE_VALGRIND_VALGRIND_H +/* Define to 1 if you have the <va/va_drm.h> header file. */ +#undef HAVE_VA_VA_DRM_H + +/* Define to 1 if you have the <va/va.h> header file. */ +#undef HAVE_VA_VA_H + /* Define to 1 if the Wine preloader is being used. */ #undef HAVE_WINE_PRELOADER -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11346
From: Valerian Mayega <valerian@gmail.com> Mirror the existing device_manager_processor_service_CreateSurface implementation: allocate NumBackBuffers + 1 D3D9 offscreen plain surfaces via the device manager's IDirect3DDevice9, in the format the caller requested (NV12 for 8-bit decode profiles, P010 for 10-bit). Signed-off-by: Valerian Mayega <valerian@gmail.com> --- dlls/dxva2/main.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/dlls/dxva2/main.c b/dlls/dxva2/main.c index 7ec41555701..7637adaa689 100644 --- a/dlls/dxva2/main.c +++ b/dlls/dxva2/main.c @@ -721,10 +721,38 @@ static HRESULT WINAPI device_manager_decoder_service_CreateSurface(IDirectXVideo UINT width, UINT height, UINT backbuffers, D3DFORMAT format, D3DPOOL pool, DWORD usage, DWORD dxvaType, IDirect3DSurface9 **surfaces, HANDLE *shared_handle) { - FIXME("%p, %u, %u, %u, %#x, %d, %ld, %ld, %p, %p.\n", iface, width, height, backbuffers, format, pool, usage, + struct device_manager *manager = impl_from_IDirectXVideoDecoderService(iface); + unsigned int i, j; + HRESULT hr; + + TRACE("%p, %u, %u, %u, %#x, %d, %ld, %ld, %p, %p.\n", iface, width, height, backbuffers, format, pool, usage, dxvaType, surfaces, shared_handle); - return E_NOTIMPL; + if (backbuffers >= UINT_MAX) + return E_INVALIDARG; + + memset(surfaces, 0, (backbuffers + 1) * sizeof(*surfaces)); + + for (i = 0; i < backbuffers + 1; ++i) + { + if (FAILED(hr = IDirect3DDevice9_CreateOffscreenPlainSurface(manager->device, width, height, format, + pool, &surfaces[i], NULL))) + break; + } + + if (FAILED(hr)) + { + for (j = 0; j < i; ++j) + { + if (surfaces[j]) + { + IDirect3DSurface9_Release(surfaces[j]); + surfaces[j] = NULL; + } + } + } + + return hr; } static HRESULT WINAPI device_manager_decoder_service_GetDecoderDeviceGuids(IDirectXVideoDecoderService *iface, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11346
From: Valerian Mayega <valerian@gmail.com> Translate DXVA2's HEVC picture parameters, IQ matrix and slice buffers into VA-API calls, and copy the decoded frame into the app's D3D9 surface. The picture parameters and IQ matrix map field for field; NAL slice data is DXVA2's "short format" (location/size only), so this implements a hand-written Exp-Golomb parser for slice_segment_header() per H.265 7.3.6.1, since VA-API has no equivalent short-format mode. Not every VA-API driver supports deriving a CPU-mappable image directly from a decode surface via vaDeriveImage(); nvidia-vaapi-driver in particular fails it unconditionally. Fall back to an explicit vaCreateImage()+vaGetImage() copy in that case, using the same NV12/ P010 format the decode surfaces were created with. Verified against the actual Xiaomi Camera Viewer app's FFmpeg-based DXVA2 client, via Wine on real Intel iHD VA-API hardware: confirmed hardware-accelerated HEVC decode with correct picture and color, no buffering or artifacts. Also verified on NVIDIA (GTX 1060, nvidia-vaapi-driver/NVDEC) via the vaGetImage fallback above: same correct picture and color, no buffering. Signed-off-by: Valerian Mayega <valerian@gmail.com> --- dlls/dxva2/Makefile.in | 1 + dlls/dxva2/hevc.c | 627 +++++++++++++++++++++++++++++++++++++++++ dlls/dxva2/hevc.h | 36 +++ dlls/dxva2/main.c | 391 ++++++++++++++++++++++++- dlls/dxva2/unixlib.c | 434 ++++++++++++++++++++++++++++ dlls/dxva2/unixlib.h | 47 +++ 6 files changed, 1522 insertions(+), 14 deletions(-) create mode 100644 dlls/dxva2/hevc.c create mode 100644 dlls/dxva2/hevc.h diff --git a/dlls/dxva2/Makefile.in b/dlls/dxva2/Makefile.in index e02e1df03fd..a74cfd9dd4c 100644 --- a/dlls/dxva2/Makefile.in +++ b/dlls/dxva2/Makefile.in @@ -6,5 +6,6 @@ UNIX_CFLAGS = $(LIBVA_CFLAGS) UNIX_LIBS = $(LIBVA_LIBS) SOURCES = \ + hevc.c \ main.c \ unixlib.c diff --git a/dlls/dxva2/hevc.c b/dlls/dxva2/hevc.c new file mode 100644 index 00000000000..4ce0e4ab2be --- /dev/null +++ b/dlls/dxva2/hevc.c @@ -0,0 +1,627 @@ +/* + * DXVA2 HEVC picture/slice parameter translation to VA-API + * + * Translates the DXVA_PicParams_HEVC / DXVA_Qmatrix_HEVC / DXVA_Slice_HEVC_Short + * structures an app fills in (matching the exact wire format real DXVA2 HEVC + * decoders use, taken straight from the public DXVA HEVC spec) into their VA-API + * equivalents (VAPictureParameterBufferHEVC / VAIQMatrixBufferHEVC / + * VASliceParameterBufferHEVC). + * + * The picture-parameter and IQ-matrix structures correspond field for field (both + * ultimately mirror the same H.265 SPS/PPS syntax elements), so that half is a + * mechanical copy. The harder part is slice parameters: DXVA's "short slice format" + * (what we advertised via ConfigBitstreamRaw=2 in GetDecoderConfigurations) only + * gives the NAL unit's location/size in the bitstream and expects the decoder to + * parse the slice segment header itself - VA-API's public API has no equivalent + * "let the driver parse it" mode for HEVC, so we parse slice_segment_header() by + * hand here (H.265 7.3.6.1) to fill in VASliceParameterBufferHEVC. + * + * Copyright 2026 Valerian Mayega + * + * 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" + +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#ifdef HAVE_LIBVA +#include <va/va.h> +#include <va/va_dec_hevc.h> +#endif + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winternl.h" +#include "dxva.h" + +#include "wine/debug.h" +#include "hevc.h" + +#ifdef HAVE_LIBVA + +WINE_DEFAULT_DEBUG_CHANNEL(dxva2); + +#define DXVA_PICENTRY_INVALID 0xff + +/* ---- Exp-Golomb bit reader, operating on an already emulation-prevention-stripped buffer ---- */ + +struct bitreader +{ + const BYTE *data; + UINT size; /* bytes */ + UINT byte_pos; + UINT bit_pos; /* 0-7, MSB first */ +}; + +static void br_init(struct bitreader *br, const BYTE *data, UINT size) +{ + br->data = data; + br->size = size; + br->byte_pos = 0; + br->bit_pos = 0; +} + +static UINT32 br_bit(struct bitreader *br) +{ + UINT32 bit; + + if (br->byte_pos >= br->size) return 0; + bit = (br->data[br->byte_pos] >> (7 - br->bit_pos)) & 1; + if (++br->bit_pos == 8) + { + br->bit_pos = 0; + br->byte_pos++; + } + return bit; +} + +static UINT32 br_bits(struct bitreader *br, UINT n) +{ + UINT32 v = 0; + while (n--) v = (v << 1) | br_bit(br); + return v; +} + +static UINT32 br_ue(struct bitreader *br) +{ + UINT zeros = 0; + while (br->byte_pos < br->size && !br_bit(br)) zeros++; + if (!zeros) return 0; + return (1u << zeros) - 1 + br_bits(br, zeros); +} + +static INT32 br_se(struct bitreader *br) +{ + UINT32 v = br_ue(br); + return (v & 1) ? (INT32)((v + 1) / 2) : -(INT32)(v / 2); +} + +static UINT ceil_log2(UINT v) +{ + UINT r = 0; + while ((1u << r) < v) r++; + return r; +} + +/* Strip emulation prevention bytes (00 00 03 -> 00 00) into a fresh heap buffer; + * the input still has them (DXVA hands us the raw bitstream), but Exp-Golomb + * parsing must operate on the de-escaped stream. Caller frees the result. */ +static BYTE *strip_emulation_prevention(const BYTE *data, UINT size, UINT *out_size) +{ + BYTE *out; + UINT i, o = 0, zeros = 0; + + if (!(out = malloc(size))) return NULL; + + for (i = 0; i < size; i++) + { + if (zeros >= 2 && data[i] == 0x03 && i + 1 < size && data[i + 1] <= 0x03) + { + zeros = 0; + continue; + } + out[o++] = data[i]; + zeros = data[i] == 0 ? zeros + 1 : 0; + } + + *out_size = o; + return out; +} + +/* ---- Picture parameters + IQ matrix: near-mechanical field-for-field copies ---- */ + +void dxva2_hevc_translate_pic_params(const DXVA_PicParams_HEVC *src, VAPictureParameterBufferHEVC *dst, + UINT bitdepth_luma, UINT bitdepth_chroma, const VASurfaceID *surfaces, UINT surface_count) +{ + unsigned int i, before = 0, after = 0, lt = 0; + + memset(dst, 0, sizeof(*dst)); + + dst->CurrPic.picture_id = src->CurrPic.Index7Bits < surface_count ? + surfaces[src->CurrPic.Index7Bits] : VA_INVALID_SURFACE; + dst->CurrPic.pic_order_cnt = src->CurrPicOrderCntVal; + dst->CurrPic.flags = 0; + + for (i = 0; i < 15; i++) + { + BYTE entry = src->RefPicList[i].bPicEntry; + + if (entry == DXVA_PICENTRY_INVALID || src->RefPicList[i].Index7Bits >= surface_count) + { + dst->ReferenceFrames[i].picture_id = VA_INVALID_SURFACE; + dst->ReferenceFrames[i].flags = VA_PICTURE_HEVC_INVALID; + continue; + } + + dst->ReferenceFrames[i].picture_id = surfaces[src->RefPicList[i].Index7Bits]; + dst->ReferenceFrames[i].pic_order_cnt = src->PicOrderCntValList[i]; + dst->ReferenceFrames[i].flags = src->RefPicList[i].AssociatedFlag ? VA_PICTURE_HEVC_LONG_TERM_REFERENCE : 0; + } + + /* DXVA already resolves the reference-picture-set categorization at the picture + * level (RefPicSetStCurrBefore/After/LtCurr, arrays of indices into RefPicList[], + * DXVA_PICENTRY_INVALID-padded); VA-API wants the same information as per-entry + * flags on ReferenceFrames[] instead. */ + for (i = 0; i < 8 && src->RefPicSetStCurrBefore[i] != DXVA_PICENTRY_INVALID; i++) + { + dst->ReferenceFrames[src->RefPicSetStCurrBefore[i]].flags |= VA_PICTURE_HEVC_RPS_ST_CURR_BEFORE; + before++; + } + for (i = 0; i < 8 && src->RefPicSetStCurrAfter[i] != DXVA_PICENTRY_INVALID; i++) + { + dst->ReferenceFrames[src->RefPicSetStCurrAfter[i]].flags |= VA_PICTURE_HEVC_RPS_ST_CURR_AFTER; + after++; + } + for (i = 0; i < 8 && src->RefPicSetLtCurr[i] != DXVA_PICENTRY_INVALID; i++) + { + dst->ReferenceFrames[src->RefPicSetLtCurr[i]].flags |= VA_PICTURE_HEVC_RPS_LT_CURR; + lt++; + } + + dst->pic_width_in_luma_samples = src->PicWidthInMinCbsY << + (src->log2_min_luma_coding_block_size_minus3 + 3); + dst->pic_height_in_luma_samples = src->PicHeightInMinCbsY << + (src->log2_min_luma_coding_block_size_minus3 + 3); + + dst->pic_fields.bits.chroma_format_idc = src->chroma_format_idc; + dst->pic_fields.bits.separate_colour_plane_flag = src->separate_colour_plane_flag; + dst->pic_fields.bits.pcm_enabled_flag = src->pcm_enabled_flag; + dst->pic_fields.bits.scaling_list_enabled_flag = src->scaling_list_enabled_flag; + dst->pic_fields.bits.transform_skip_enabled_flag = src->transform_skip_enabled_flag; + dst->pic_fields.bits.amp_enabled_flag = src->amp_enabled_flag; + dst->pic_fields.bits.strong_intra_smoothing_enabled_flag = src->strong_intra_smoothing_enabled_flag; + dst->pic_fields.bits.sign_data_hiding_enabled_flag = src->sign_data_hiding_enabled_flag; + dst->pic_fields.bits.constrained_intra_pred_flag = src->constrained_intra_pred_flag; + dst->pic_fields.bits.cu_qp_delta_enabled_flag = src->cu_qp_delta_enabled_flag; + dst->pic_fields.bits.weighted_pred_flag = src->weighted_pred_flag; + dst->pic_fields.bits.weighted_bipred_flag = src->weighted_bipred_flag; + dst->pic_fields.bits.transquant_bypass_enabled_flag = src->transquant_bypass_enabled_flag; + dst->pic_fields.bits.tiles_enabled_flag = src->tiles_enabled_flag; + dst->pic_fields.bits.entropy_coding_sync_enabled_flag = src->entropy_coding_sync_enabled_flag; + dst->pic_fields.bits.pps_loop_filter_across_slices_enabled_flag = src->pps_loop_filter_across_slices_enabled_flag; + dst->pic_fields.bits.loop_filter_across_tiles_enabled_flag = src->loop_filter_across_tiles_enabled_flag; + dst->pic_fields.bits.pcm_loop_filter_disabled_flag = src->pcm_loop_filter_disabled_flag; + dst->pic_fields.bits.NoPicReorderingFlag = src->NoPicReorderingFlag; + dst->pic_fields.bits.NoBiPredFlag = src->NoBiPredFlag; + + dst->sps_max_dec_pic_buffering_minus1 = src->sps_max_dec_pic_buffering_minus1; + dst->bit_depth_luma_minus8 = bitdepth_luma - 8; + dst->bit_depth_chroma_minus8 = bitdepth_chroma - 8; + dst->pcm_sample_bit_depth_luma_minus1 = src->pcm_sample_bit_depth_luma_minus1; + dst->pcm_sample_bit_depth_chroma_minus1 = src->pcm_sample_bit_depth_chroma_minus1; + dst->log2_min_luma_coding_block_size_minus3 = src->log2_min_luma_coding_block_size_minus3; + dst->log2_diff_max_min_luma_coding_block_size = src->log2_diff_max_min_luma_coding_block_size; + dst->log2_min_transform_block_size_minus2 = src->log2_min_transform_block_size_minus2; + dst->log2_diff_max_min_transform_block_size = src->log2_diff_max_min_transform_block_size; + dst->log2_min_pcm_luma_coding_block_size_minus3 = src->log2_min_pcm_luma_coding_block_size_minus3; + dst->log2_diff_max_min_pcm_luma_coding_block_size = src->log2_diff_max_min_pcm_luma_coding_block_size; + dst->max_transform_hierarchy_depth_intra = src->max_transform_hierarchy_depth_intra; + dst->max_transform_hierarchy_depth_inter = src->max_transform_hierarchy_depth_inter; + dst->init_qp_minus26 = src->init_qp_minus26; + dst->diff_cu_qp_delta_depth = src->diff_cu_qp_delta_depth; + dst->pps_cb_qp_offset = src->pps_cb_qp_offset; + dst->pps_cr_qp_offset = src->pps_cr_qp_offset; + dst->log2_parallel_merge_level_minus2 = src->log2_parallel_merge_level_minus2; + dst->num_tile_columns_minus1 = src->num_tile_columns_minus1; + dst->num_tile_rows_minus1 = src->num_tile_rows_minus1; + memcpy(dst->column_width_minus1, src->column_width_minus1, sizeof(dst->column_width_minus1)); + memcpy(dst->row_height_minus1, src->row_height_minus1, sizeof(dst->row_height_minus1)); + + dst->slice_parsing_fields.bits.lists_modification_present_flag = src->lists_modification_present_flag; + dst->slice_parsing_fields.bits.long_term_ref_pics_present_flag = src->long_term_ref_pics_present_flag; + dst->slice_parsing_fields.bits.sps_temporal_mvp_enabled_flag = src->sps_temporal_mvp_enabled_flag; + dst->slice_parsing_fields.bits.cabac_init_present_flag = src->cabac_init_present_flag; + dst->slice_parsing_fields.bits.output_flag_present_flag = src->output_flag_present_flag; + dst->slice_parsing_fields.bits.dependent_slice_segments_enabled_flag = src->dependent_slice_segments_enabled_flag; + dst->slice_parsing_fields.bits.pps_slice_chroma_qp_offsets_present_flag = src->pps_slice_chroma_qp_offsets_present_flag; + dst->slice_parsing_fields.bits.sample_adaptive_offset_enabled_flag = src->sample_adaptive_offset_enabled_flag; + dst->slice_parsing_fields.bits.deblocking_filter_override_enabled_flag = src->deblocking_filter_override_enabled_flag; + dst->slice_parsing_fields.bits.pps_disable_deblocking_filter_flag = src->pps_deblocking_filter_disabled_flag; + dst->slice_parsing_fields.bits.slice_segment_header_extension_present_flag = src->slice_segment_header_extension_present_flag; + dst->slice_parsing_fields.bits.RapPicFlag = src->IrapPicFlag; + dst->slice_parsing_fields.bits.IdrPicFlag = src->IdrPicFlag; + dst->slice_parsing_fields.bits.IntraPicFlag = src->IntraPicFlag; + + dst->log2_max_pic_order_cnt_lsb_minus4 = src->log2_max_pic_order_cnt_lsb_minus4; + dst->num_short_term_ref_pic_sets = src->num_short_term_ref_pic_sets; + dst->num_long_term_ref_pic_sps = src->num_long_term_ref_pics_sps; + dst->num_ref_idx_l0_default_active_minus1 = src->num_ref_idx_l0_default_active_minus1; + dst->num_ref_idx_l1_default_active_minus1 = src->num_ref_idx_l1_default_active_minus1; + dst->pps_beta_offset_div2 = src->pps_beta_offset_div2; + dst->pps_tc_offset_div2 = src->pps_tc_offset_div2; + dst->num_extra_slice_header_bits = src->num_extra_slice_header_bits; + dst->st_rps_bits = src->wNumBitsForShortTermRPSInSlice; + + TRACE("HEVC pic params: %ux%u, %u/%u/%u ref pics before/after/lt.\n", + dst->pic_width_in_luma_samples, dst->pic_height_in_luma_samples, before, after, lt); +} + +void dxva2_hevc_translate_iq_matrix(const DXVA_Qmatrix_HEVC *src, VAIQMatrixBufferHEVC *dst) +{ + memset(dst, 0, sizeof(*dst)); + memcpy(dst->ScalingList4x4, src->ucScalingLists0, sizeof(dst->ScalingList4x4)); + memcpy(dst->ScalingList8x8, src->ucScalingLists1, sizeof(dst->ScalingList8x8)); + memcpy(dst->ScalingList16x16, src->ucScalingLists2, sizeof(dst->ScalingList16x16)); + memcpy(dst->ScalingList32x32, src->ucScalingLists3, sizeof(dst->ScalingList32x32)); + memcpy(dst->ScalingListDC16x16, src->ucScalingListDCCoefSizeID2, sizeof(dst->ScalingListDC16x16)); + memcpy(dst->ScalingListDC32x32, src->ucScalingListDCCoefSizeID3, sizeof(dst->ScalingListDC32x32)); +} + +/* ---- Slice header parsing: DXVA's "short format" only gives us NAL location/size, + * so we parse slice_segment_header() ourselves (H.265 7.3.6.1) to get the fields + * VA-API's "long format" VASliceParameterBufferHEVC needs. The picture-level fields + * (RPS categorization, SPS/PPS flags) are already resolved for us in DXVA_PicParams_HEVC; + * what's left to parse is genuinely per-slice: type, overrides, ref list modification, + * weighted prediction table, deblocking overrides, QP deltas. ---- */ + +struct ref_pic_lists +{ + BYTE list0[15], list0_count; + BYTE list1[15], list1_count; +}; + +/* H.265 8.3.4: default (pre-modification) RefPicList construction from the picture-level + * RPS categorization DXVA already resolved for us. */ +static void build_default_ref_pic_lists(const DXVA_PicParams_HEVC *pic, struct ref_pic_lists *lists) +{ + BYTE before[8], after[8], lt[8]; + UINT nbefore = 0, nafter = 0, nlt = 0, i, total; + + for (i = 0; i < 8 && pic->RefPicSetStCurrBefore[i] != DXVA_PICENTRY_INVALID; i++) before[nbefore++] = pic->RefPicSetStCurrBefore[i]; + for (i = 0; i < 8 && pic->RefPicSetStCurrAfter[i] != DXVA_PICENTRY_INVALID; i++) after[nafter++] = pic->RefPicSetStCurrAfter[i]; + for (i = 0; i < 8 && pic->RefPicSetLtCurr[i] != DXVA_PICENTRY_INVALID; i++) lt[nlt++] = pic->RefPicSetLtCurr[i]; + + total = nbefore + nafter + nlt; + lists->list0_count = 0; + lists->list1_count = 0; + if (!total) return; + + /* List0 candidate order: StCurrBefore, StCurrAfter, LtCurr - repeated (wrapped) up to + * NumPicTotalCurr entries; the caller truncates to num_ref_idx_l0_active_minus1+1. */ + for (i = 0; i < 15; i++) + { + BYTE v; + UINT idx = i % total; + if (idx < nbefore) v = before[idx]; + else if (idx < nbefore + nafter) v = after[idx - nbefore]; + else v = lt[idx - nbefore - nafter]; + lists->list0[i] = v; + } + lists->list0_count = 15; + + /* List1 candidate order: StCurrAfter, StCurrBefore, LtCurr. */ + for (i = 0; i < 15; i++) + { + BYTE v; + UINT idx = i % total; + if (idx < nafter) v = after[idx]; + else if (idx < nafter + nbefore) v = before[idx - nafter]; + else v = lt[idx - nafter - nbefore]; + lists->list1[i] = v; + } + lists->list1_count = 15; +} + +BOOL dxva2_hevc_translate_slice(const DXVA_PicParams_HEVC *pic, const BYTE *nal_data, UINT nal_size, + VASliceParameterBufferHEVC *dst, UINT slice_data_offset) +{ + struct bitreader br; + struct ref_pic_lists def_lists; + BYTE *stripped; + UINT stripped_size, i; + UINT nal_unit_type; + BOOL first_slice_segment_in_pic_flag, dependent_slice_segment_flag = FALSE; + BOOL slice_sao_luma_flag = FALSE, slice_sao_chroma_flag = FALSE; + BOOL slice_deblocking_filter_disabled_flag = pic->pps_deblocking_filter_disabled_flag; + UINT slice_type; + UINT pic_size_in_ctbs; + UINT ctb_log2_size; + UINT num_pic_total_curr; + + memset(dst, 0, sizeof(*dst)); + dst->collocated_ref_idx = 0xff; /* "invalid" when slice_temporal_mvp is off */ + + if (nal_size < 3) return FALSE; + nal_unit_type = (nal_data[0] >> 1) & 0x3f; + + if (!(stripped = strip_emulation_prevention(nal_data, nal_size, &stripped_size))) + return FALSE; + + br_init(&br, stripped, stripped_size); + br_bits(&br, 16); /* NAL unit header, already accounted for via nal_unit_type above */ + + first_slice_segment_in_pic_flag = br_bit(&br); + if (nal_unit_type >= 16 && nal_unit_type <= 23) br_bit(&br); /* no_output_of_prior_pics_flag */ + br_ue(&br); /* slice_pic_parameter_set_id */ + + ctb_log2_size = pic->log2_min_luma_coding_block_size_minus3 + 3 + pic->log2_diff_max_min_luma_coding_block_size; + pic_size_in_ctbs = ((pic->PicWidthInMinCbsY << (pic->log2_min_luma_coding_block_size_minus3 + 3)) + (1u << ctb_log2_size) - 1) >> ctb_log2_size; + pic_size_in_ctbs *= ((pic->PicHeightInMinCbsY << (pic->log2_min_luma_coding_block_size_minus3 + 3)) + (1u << ctb_log2_size) - 1) >> ctb_log2_size; + + if (!first_slice_segment_in_pic_flag) + { + if (pic->dependent_slice_segments_enabled_flag) dependent_slice_segment_flag = br_bit(&br); + dst->slice_segment_address = br_bits(&br, ceil_log2(pic_size_in_ctbs ? pic_size_in_ctbs : 1)); + } + + dst->LongSliceFlags.fields.dependent_slice_segment_flag = dependent_slice_segment_flag; + + build_default_ref_pic_lists(pic, &def_lists); + num_pic_total_curr = 0; + for (i = 0; i < 8 && pic->RefPicSetStCurrBefore[i] != DXVA_PICENTRY_INVALID; i++) num_pic_total_curr++; + for (i = 0; i < 8 && pic->RefPicSetStCurrAfter[i] != DXVA_PICENTRY_INVALID; i++) num_pic_total_curr++; + for (i = 0; i < 8 && pic->RefPicSetLtCurr[i] != DXVA_PICENTRY_INVALID; i++) num_pic_total_curr++; + + slice_type = 2; /* I, default/fallback if we bail early */ + + if (!dependent_slice_segment_flag) + { + for (i = 0; i < pic->num_extra_slice_header_bits; i++) br_bit(&br); + slice_type = br_ue(&br); + if (pic->output_flag_present_flag) br_bit(&br); /* pic_output_flag */ + if (pic->separate_colour_plane_flag) br_bits(&br, 2); /* colour_plane_id */ + + if (!pic->IdrPicFlag) + { + br_bits(&br, pic->log2_max_pic_order_cnt_lsb_minus4 + 4); /* slice_pic_order_cnt_lsb - already have POC from pic params */ + if (!br_bit(&br)) /* short_term_ref_pic_set_sps_flag == 0: RPS coded inline */ + { + /* Full short_term_ref_pic_set() parsing is involved and, when present, DXVA + * still gives us the resolved RefPicSetStCurrBefore/After/LtCurr categorization at the + * picture level regardless - so we only need to consume the right number of + * bits here to stay in sync with the rest of the header, which DXVA gives us + * directly via wNumBitsForShortTermRPSInSlice. */ + UINT rps_bits = pic->wNumBitsForShortTermRPSInSlice; + while (rps_bits > 32) + { + br_bits(&br, 32); + rps_bits -= 32; + } + br_bits(&br, rps_bits); + } + else if (pic->num_short_term_ref_pic_sets > 1) + { + br_bits(&br, ceil_log2(pic->num_short_term_ref_pic_sets)); + } + if (pic->long_term_ref_pics_present_flag) + { + /* DXVA already resolves the final LT set for us at the picture level; + * we only parse to keep the bit position in sync (7.3.6.1). */ + UINT num_lt_sps = 0, num_lt_pics, total_lt; + + if (pic->num_long_term_ref_pics_sps > 0) num_lt_sps = br_ue(&br); + num_lt_pics = br_ue(&br); + total_lt = num_lt_sps + num_lt_pics; + for (i = 0; i < total_lt; i++) + { + if (i < num_lt_sps) + { + if (pic->num_long_term_ref_pics_sps > 1) + br_bits(&br, ceil_log2(pic->num_long_term_ref_pics_sps)); /* lt_idx_sps */ + } + else + { + br_bits(&br, pic->log2_max_pic_order_cnt_lsb_minus4 + 4); /* poc_lsb_lt */ + br_bit(&br); /* used_by_curr_pic_lt_flag */ + } + if (br_bit(&br)) /* delta_poc_msb_present_flag */ + br_ue(&br); /* delta_poc_msb_cycle_lt */ + } + } + if (pic->sps_temporal_mvp_enabled_flag) + dst->LongSliceFlags.fields.slice_temporal_mvp_enabled_flag = br_bit(&br); + } + + if (pic->sample_adaptive_offset_enabled_flag) + { + slice_sao_luma_flag = br_bit(&br); + if (pic->chroma_format_idc) slice_sao_chroma_flag = br_bit(&br); + } + + dst->num_ref_idx_l0_active_minus1 = pic->num_ref_idx_l0_default_active_minus1; + dst->num_ref_idx_l1_active_minus1 = pic->num_ref_idx_l1_default_active_minus1; + + if (slice_type == 0 /* B */ || slice_type == 1 /* P */) + { + BOOL override_flag = br_bit(&br); + if (override_flag) + { + dst->num_ref_idx_l0_active_minus1 = br_ue(&br); + if (slice_type == 0) dst->num_ref_idx_l1_active_minus1 = br_ue(&br); + } + + if (pic->lists_modification_present_flag && num_pic_total_curr > 1) + { + UINT bits = ceil_log2(num_pic_total_curr); + if (br_bit(&br)) /* ref_pic_list_modification_flag_l0 */ + for (i = 0; i <= dst->num_ref_idx_l0_active_minus1; i++) def_lists.list0[i] = br_bits(&br, bits); + if (slice_type == 0 && br_bit(&br)) /* ref_pic_list_modification_flag_l1 */ + for (i = 0; i <= dst->num_ref_idx_l1_active_minus1; i++) def_lists.list1[i] = br_bits(&br, bits); + } + + if (slice_type == 0) dst->LongSliceFlags.fields.mvd_l1_zero_flag = br_bit(&br); + if (pic->cabac_init_present_flag) dst->LongSliceFlags.fields.cabac_init_flag = br_bit(&br); + + dst->collocated_ref_idx = 0xff; + if (dst->LongSliceFlags.fields.slice_temporal_mvp_enabled_flag) + { + BOOL collocated_from_l0 = TRUE; + if (slice_type == 0) collocated_from_l0 = br_bit(&br); + dst->LongSliceFlags.fields.collocated_from_l0_flag = collocated_from_l0; + + if ((collocated_from_l0 && dst->num_ref_idx_l0_active_minus1 > 0) || + (!collocated_from_l0 && dst->num_ref_idx_l1_active_minus1 > 0)) + dst->collocated_ref_idx = br_ue(&br); + else + dst->collocated_ref_idx = 0; + } + + if ((pic->weighted_pred_flag && slice_type == 1) || (pic->weighted_bipred_flag && slice_type == 0)) + { + UINT c_log2_denom; + + dst->luma_log2_weight_denom = br_ue(&br); + if (pic->chroma_format_idc) + dst->delta_chroma_log2_weight_denom = br_se(&br); + c_log2_denom = dst->luma_log2_weight_denom + dst->delta_chroma_log2_weight_denom; + + for (i = 0; i <= dst->num_ref_idx_l0_active_minus1; i++) + { + BOOL luma_flag = br_bit(&br); + BOOL chroma_flag = pic->chroma_format_idc ? br_bit(&br) : FALSE; + if (luma_flag) + { + dst->delta_luma_weight_l0[i] = br_se(&br); + dst->luma_offset_l0[i] = br_se(&br); + } + if (chroma_flag) + { + int j; + for (j = 0; j < 2; j++) + { + INT32 delta_weight = br_se(&br); + INT32 delta_offset = br_se(&br); + dst->delta_chroma_weight_l0[i][j] = delta_weight; + dst->ChromaOffsetL0[i][j] = delta_offset; + } + (void)c_log2_denom; + } + } + if (slice_type == 0) + { + for (i = 0; i <= dst->num_ref_idx_l1_active_minus1; i++) + { + BOOL luma_flag = br_bit(&br); + BOOL chroma_flag = pic->chroma_format_idc ? br_bit(&br) : FALSE; + if (luma_flag) + { + dst->delta_luma_weight_l1[i] = br_se(&br); + dst->luma_offset_l1[i] = br_se(&br); + } + if (chroma_flag) + { + int j; + for (j = 0; j < 2; j++) + { + dst->delta_chroma_weight_l1[i][j] = br_se(&br); + dst->ChromaOffsetL1[i][j] = br_se(&br); + } + } + } + } + } + + dst->five_minus_max_num_merge_cand = br_ue(&br); + } + + dst->slice_qp_delta = br_se(&br); + if (pic->pps_slice_chroma_qp_offsets_present_flag) + { + dst->slice_cb_qp_offset = br_se(&br); + dst->slice_cr_qp_offset = br_se(&br); + } + + if (pic->deblocking_filter_override_enabled_flag && br_bit(&br)) /* deblocking_filter_override_flag */ + { + slice_deblocking_filter_disabled_flag = br_bit(&br); + if (!slice_deblocking_filter_disabled_flag) + { + dst->slice_beta_offset_div2 = br_se(&br); + dst->slice_tc_offset_div2 = br_se(&br); + } + } + + if (pic->pps_loop_filter_across_slices_enabled_flag && + (slice_sao_luma_flag || slice_sao_chroma_flag || !slice_deblocking_filter_disabled_flag)) + dst->LongSliceFlags.fields.slice_loop_filter_across_slices_enabled_flag = br_bit(&br); + } + + /* Remaining slice_segment_header() syntax, parsed for every slice segment + * (dependent ones included): entry point offsets and the header extension. */ + if (pic->tiles_enabled_flag || pic->entropy_coding_sync_enabled_flag) + { + UINT num_entry_point_offsets = br_ue(&br); + if (num_entry_point_offsets > 0) + { + UINT offset_len = br_ue(&br) + 1; /* offset_len_minus1 */ + for (i = 0; i < num_entry_point_offsets; i++) + br_bits(&br, offset_len); + } + } + if (pic->slice_segment_header_extension_present_flag) + { + UINT ext_len = br_ue(&br); /* slice_segment_header_extension_length */ + for (i = 0; i < ext_len; i++) + br_bits(&br, 8); + } + + /* slice_segment_header() ends with byte_alignment(); slice_data() starts on + * the next byte boundary. VA-API needs this offset (relative to and + * including the NAL unit header, counted after emulation prevention byte + * removal - exactly our bit reader's position in the stripped stream) to + * know where entropy-coded data begins, since with DXVA's short format the + * driver never sees a parsed slice header. */ + dst->slice_data_byte_offset = br.byte_pos + (br.bit_pos ? 1 : 0); + + free(stripped); + + dst->LongSliceFlags.fields.LastSliceOfPic = 0; /* set by caller for the actual last slice */ + dst->LongSliceFlags.fields.slice_type = slice_type; + dst->LongSliceFlags.fields.slice_sao_luma_flag = slice_sao_luma_flag; + dst->LongSliceFlags.fields.slice_sao_chroma_flag = slice_sao_chroma_flag; + dst->LongSliceFlags.fields.slice_deblocking_filter_disabled_flag = slice_deblocking_filter_disabled_flag; + + for (i = 0; i < 15; i++) + { + dst->RefPicList[0][i] = i < def_lists.list0_count ? def_lists.list0[i] : 0xff; + dst->RefPicList[1][i] = i < def_lists.list1_count ? def_lists.list1[i] : 0xff; + } + + dst->slice_data_offset = slice_data_offset; + dst->slice_data_size = nal_size; + dst->slice_data_flag = 0; /* VA_SLICE_DATA_FLAG_ALL: buffer holds the complete slice */ + + return TRUE; +} + +#endif /* HAVE_LIBVA */ diff --git a/dlls/dxva2/hevc.h b/dlls/dxva2/hevc.h new file mode 100644 index 00000000000..fa25ae6a139 --- /dev/null +++ b/dlls/dxva2/hevc.h @@ -0,0 +1,36 @@ +/* + * DXVA2 HEVC picture/slice parameter translation to VA-API + * + * Copyright 2026 Valerian Mayega + * + * 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 __DXVA2_HEVC_H +#define __DXVA2_HEVC_H + +#ifdef HAVE_LIBVA + +void dxva2_hevc_translate_pic_params(const DXVA_PicParams_HEVC *src, VAPictureParameterBufferHEVC *dst, + UINT bitdepth_luma, UINT bitdepth_chroma, const VASurfaceID *surfaces, UINT surface_count); + +void dxva2_hevc_translate_iq_matrix(const DXVA_Qmatrix_HEVC *src, VAIQMatrixBufferHEVC *dst); + +BOOL dxva2_hevc_translate_slice(const DXVA_PicParams_HEVC *pic, const BYTE *nal_data, UINT nal_size, + VASliceParameterBufferHEVC *dst, UINT slice_data_offset); + +#endif /* HAVE_LIBVA */ + +#endif /* __DXVA2_HEVC_H */ diff --git a/dlls/dxva2/main.c b/dlls/dxva2/main.c index 7637adaa689..7e8f89899cc 100644 --- a/dlls/dxva2/main.c +++ b/dlls/dxva2/main.c @@ -30,6 +30,7 @@ #include "initguid.h" #include "dxva2api.h" #include "dxvahd.h" +#include "dxva.h" #include "wine/debug.h" @@ -759,7 +760,7 @@ static HRESULT WINAPI device_manager_decoder_service_GetDecoderDeviceGuids(IDire UINT *count, GUID **guids) { GUID *ret; - UINT i; + UINT i, j, unique = 0; TRACE("%p, %p, %p.\n", iface, count, guids); @@ -777,10 +778,19 @@ static HRESULT WINAPI device_manager_decoder_service_GetDecoderDeviceGuids(IDire if (!(ret = CoTaskMemAlloc(supported_profiles_count * sizeof(*ret)))) return E_OUTOFMEMORY; + /* Multiple VA profiles can map to the same DXVA GUID (e.g. the H.264 + * baseline/main/high variants); report each GUID once. */ for (i = 0; i < supported_profiles_count; ++i) - ret[i] = supported_profiles[i].guid; + { + for (j = 0; j < unique; ++j) + { + if (IsEqualGUID(&ret[j], &supported_profiles[i].guid)) break; + } + if (j == unique) + ret[unique++] = supported_profiles[i].guid; + } - *count = supported_profiles_count; + *count = unique; *guids = ret; return S_OK; } @@ -843,29 +853,382 @@ static HRESULT WINAPI device_manager_decoder_service_GetDecoderConfigurations(ID return E_FAIL; } - if (!(ret = CoTaskMemAlloc(sizeof(*ret)))) + if (!(ret = CoTaskMemAlloc(2 * sizeof(*ret)))) return E_OUTOFMEMORY; - memset(ret, 0, sizeof(*ret)); - /* Slice-level (long-format) bitstream submission - what real VLD drivers - * report, and what FFmpeg-based DXVA2 hwaccel consumers (which is what - * the Xiaomi app's codec plugin looks like internally) require. */ - ret->ConfigBitstreamRaw = 2; - ret->ConfigMinRenderTargetBuffCount = 4; - - *count = 1; + memset(ret, 0, 2 * sizeof(*ret)); + /* Report both raw-bitstream slice-control modes and let the client pick. + * FFmpeg-based DXVA2 consumers (which is what the Xiaomi app's codec + * plugin embeds) require ConfigBitstreamRaw == 1 for every codec except + * H.264, where they prefer 2; their HEVC implementation submits + * short-format slice control either way, which is what our decode path + * implements. */ + ret[0].guidConfigBitstreamEncryption = DXVA2_NoEncrypt; + ret[0].guidConfigMBcontrolEncryption = DXVA2_NoEncrypt; + ret[0].guidConfigResidDiffEncryption = DXVA2_NoEncrypt; + ret[0].ConfigBitstreamRaw = 1; + ret[0].ConfigMinRenderTargetBuffCount = 4; + ret[1] = ret[0]; + ret[1].ConfigBitstreamRaw = 2; + + *count = 2; *configs = ret; return S_OK; } +#define DXVA2_BUFFER_TYPE_COUNT (DXVA2_FilmGrainBuffer + 1) + +struct decoder_buffer +{ + void *data; + UINT size; +}; + +struct video_decoder +{ + IDirectXVideoDecoder IDirectXVideoDecoder_iface; + LONG refcount; + + IDirectXVideoDecoderService *service; + GUID guid; + DXVA2_VideoDesc video_desc; + DXVA2_ConfigPictureDecode config; + + IDirect3DSurface9 **surfaces; + UINT surface_count; + IDirect3DSurface9 *target; + + struct decoder_buffer buffers[DXVA2_BUFFER_TYPE_COUNT]; + + UINT64 unix_context; +}; + +static struct video_decoder *impl_from_IDirectXVideoDecoder(IDirectXVideoDecoder *iface) +{ + return CONTAINING_RECORD(iface, struct video_decoder, IDirectXVideoDecoder_iface); +} + +static HRESULT WINAPI video_decoder_QueryInterface(IDirectXVideoDecoder *iface, REFIID riid, void **obj) +{ + if (IsEqualIID(riid, &IID_IDirectXVideoDecoder) || IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IDirectXVideoDecoder_AddRef(iface); + return S_OK; + } + + WARN("Unsupported interface %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI video_decoder_AddRef(IDirectXVideoDecoder *iface) +{ + struct video_decoder *decoder = impl_from_IDirectXVideoDecoder(iface); + ULONG refcount = InterlockedIncrement(&decoder->refcount); + + TRACE("%p, refcount %lu.\n", iface, refcount); + + return refcount; +} + +static ULONG WINAPI video_decoder_Release(IDirectXVideoDecoder *iface) +{ + struct video_decoder *decoder = impl_from_IDirectXVideoDecoder(iface); + ULONG refcount = InterlockedDecrement(&decoder->refcount); + unsigned int i; + + TRACE("%p, refcount %lu.\n", iface, refcount); + + if (!refcount) + { + struct decoder_destroy_params params = { decoder->unix_context }; + + DXVA2_CALL(decoder_destroy, ¶ms); + + for (i = 0; i < DXVA2_BUFFER_TYPE_COUNT; ++i) + free(decoder->buffers[i].data); + for (i = 0; i < decoder->surface_count; ++i) + IDirect3DSurface9_Release(decoder->surfaces[i]); + free(decoder->surfaces); + + IDirectXVideoDecoderService_Release(decoder->service); + free(decoder); + } + + return refcount; +} + +static HRESULT WINAPI video_decoder_GetVideoDecoderService(IDirectXVideoDecoder *iface, + IDirectXVideoDecoderService **service) +{ + struct video_decoder *decoder = impl_from_IDirectXVideoDecoder(iface); + + TRACE("%p, %p.\n", iface, service); + + *service = decoder->service; + IDirectXVideoDecoderService_AddRef(*service); + + return S_OK; +} + +static HRESULT WINAPI video_decoder_GetCreationParameters(IDirectXVideoDecoder *iface, GUID *guid, + DXVA2_VideoDesc *video_desc, DXVA2_ConfigPictureDecode *config, IDirect3DSurface9 ***surfaces, + UINT *surface_count) +{ + struct video_decoder *decoder = impl_from_IDirectXVideoDecoder(iface); + IDirect3DSurface9 **ret; + unsigned int i; + + TRACE("%p, %p, %p, %p, %p, %p.\n", iface, guid, video_desc, config, surfaces, surface_count); + + if (!(ret = CoTaskMemAlloc(decoder->surface_count * sizeof(*ret)))) + return E_OUTOFMEMORY; + + for (i = 0; i < decoder->surface_count; ++i) + { + ret[i] = decoder->surfaces[i]; + IDirect3DSurface9_AddRef(ret[i]); + } + + *guid = decoder->guid; + *video_desc = decoder->video_desc; + *config = decoder->config; + *surfaces = ret; + *surface_count = decoder->surface_count; + + return S_OK; +} + +static const UINT decoder_buffer_sizes[DXVA2_BUFFER_TYPE_COUNT] = +{ + [DXVA2_PictureParametersBufferType] = sizeof(DXVA_PicParams_HEVC), + [DXVA2_MacroBlockControlBufferType] = 4096, + [DXVA2_ResidualDifferenceBufferType] = 4096, + [DXVA2_DeblockingControlBufferType] = 4096, + [DXVA2_InverseQuantizationMatrixBufferType] = sizeof(DXVA_Qmatrix_HEVC), + [DXVA2_SliceControlBufferType] = 256 * sizeof(DXVA_Slice_HEVC_Short), + [DXVA2_BitStreamDateBufferType] = 4 * 1024 * 1024, + [DXVA2_MotionVectorBuffer] = 4096, + [DXVA2_FilmGrainBuffer] = 4096, +}; + +static HRESULT WINAPI video_decoder_GetBuffer(IDirectXVideoDecoder *iface, UINT type, void **buffer, UINT *size) +{ + struct video_decoder *decoder = impl_from_IDirectXVideoDecoder(iface); + + TRACE("%p, %u, %p, %p.\n", iface, type, buffer, size); + + if (type >= DXVA2_BUFFER_TYPE_COUNT) + return E_INVALIDARG; + + if (!decoder->buffers[type].data && !(decoder->buffers[type].data = malloc(decoder_buffer_sizes[type]))) + return E_OUTOFMEMORY; + + decoder->buffers[type].size = decoder_buffer_sizes[type]; + *buffer = decoder->buffers[type].data; + *size = decoder->buffers[type].size; + + return S_OK; +} + +static HRESULT WINAPI video_decoder_ReleaseBuffer(IDirectXVideoDecoder *iface, UINT type) +{ + TRACE("%p, %u.\n", iface, type); + + if (type >= DXVA2_BUFFER_TYPE_COUNT) + return E_INVALIDARG; + + return S_OK; +} + +static HRESULT WINAPI video_decoder_BeginFrame(IDirectXVideoDecoder *iface, IDirect3DSurface9 *target, void *pvpp_data) +{ + struct video_decoder *decoder = impl_from_IDirectXVideoDecoder(iface); + + TRACE("%p, %p, %p.\n", iface, target, pvpp_data); + + decoder->target = target; + + return S_OK; +} + +static HRESULT WINAPI video_decoder_EndFrame(IDirectXVideoDecoder *iface, HANDLE *handle_complete) +{ + TRACE("%p, %p.\n", iface, handle_complete); + + if (handle_complete) + *handle_complete = (HANDLE)1; + + return S_OK; +} + +static const DXVA2_DecodeBufferDesc *find_buffer_desc(const DXVA2_DecodeExecuteParams *params, DWORD type) +{ + UINT i; + + for (i = 0; i < params->NumCompBuffers; ++i) + if (params->pCompressedBuffers[i].CompressedBufferType == type) + return ¶ms->pCompressedBuffers[i]; + + return NULL; +} + +static HRESULT WINAPI video_decoder_Execute(IDirectXVideoDecoder *iface, const DXVA2_DecodeExecuteParams *params) +{ + struct video_decoder *decoder = impl_from_IDirectXVideoDecoder(iface); + const DXVA2_DecodeBufferDesc *pic_desc, *iq_desc, *slice_desc, *bitstream_desc; + struct decoder_decode_params decode_params = { 0 }; + D3DLOCKED_RECT locked; + D3DSURFACE_DESC surface_desc; + UINT target_index = UINT_MAX; + unsigned int i; + HRESULT hr; + + TRACE("%p, %p.\n", iface, params); + + if (!decoder->target) + return E_FAIL; + + if (!(pic_desc = find_buffer_desc(params, DXVA2_PictureParametersBufferType))) + { + WARN("Missing picture parameters buffer.\n"); + return E_FAIL; + } + if (!(slice_desc = find_buffer_desc(params, DXVA2_SliceControlBufferType))) + { + WARN("Missing slice control buffer.\n"); + return E_FAIL; + } + if (!(bitstream_desc = find_buffer_desc(params, DXVA2_BitStreamDateBufferType))) + { + WARN("Missing bitstream buffer.\n"); + return E_FAIL; + } + iq_desc = find_buffer_desc(params, DXVA2_InverseQuantizationMatrixBufferType); + + for (i = 0; i < decoder->surface_count; ++i) + { + if (decoder->surfaces[i] == decoder->target) + { + target_index = i; + break; + } + } + if (target_index == UINT_MAX) + { + WARN("Target surface %p is not one of the decoder's surfaces.\n", decoder->target); + return E_FAIL; + } + + /* Apps often allocate decode surfaces taller than the display height + * (e.g. padded to a macroblock/alignment boundary of their own choosing, + * independent of what the SPS's coded dimensions - in video_desc - say). + * Fill however many rows the real surface has, not what video_desc claims, + * or anything below the decoded picture is left as stale/uninitialized + * surface memory. */ + if (FAILED(hr = IDirect3DSurface9_GetDesc(decoder->target, &surface_desc))) + { + WARN("Failed to get target surface description, hr %#lx.\n", hr); + return hr; + } + + if (FAILED(hr = IDirect3DSurface9_LockRect(decoder->target, &locked, NULL, D3DLOCK_DISCARD))) + { + WARN("Failed to lock target surface, hr %#lx.\n", hr); + return hr; + } + + decode_params.context = decoder->unix_context; + decode_params.target_surface_index = target_index; + decode_params.pic_params = (BYTE *)decoder->buffers[DXVA2_PictureParametersBufferType].data + pic_desc->DataOffset; + decode_params.pic_params_size = pic_desc->DataSize; + if (iq_desc) + { + decode_params.qmatrix = (BYTE *)decoder->buffers[DXVA2_InverseQuantizationMatrixBufferType].data + iq_desc->DataOffset; + decode_params.qmatrix_size = iq_desc->DataSize; + } + decode_params.slice_control = (BYTE *)decoder->buffers[DXVA2_SliceControlBufferType].data + slice_desc->DataOffset; + decode_params.slice_count = slice_desc->DataSize / sizeof(DXVA_Slice_HEVC_Short); + decode_params.bitstream = (BYTE *)decoder->buffers[DXVA2_BitStreamDateBufferType].data + bitstream_desc->DataOffset; + decode_params.bitstream_size = bitstream_desc->DataSize; + decode_params.output = locked.pBits; + decode_params.output_stride = locked.Pitch; + decode_params.output_height = surface_desc.Height; + + hr = SUCCEEDED(DXVA2_CALL(decoder_decode_frame, &decode_params)) ? S_OK : E_FAIL; + + IDirect3DSurface9_UnlockRect(decoder->target); + + return hr; +} + +static const IDirectXVideoDecoderVtbl video_decoder_vtbl = +{ + video_decoder_QueryInterface, + video_decoder_AddRef, + video_decoder_Release, + video_decoder_GetVideoDecoderService, + video_decoder_GetCreationParameters, + video_decoder_GetBuffer, + video_decoder_ReleaseBuffer, + video_decoder_BeginFrame, + video_decoder_EndFrame, + video_decoder_Execute, +}; + static HRESULT WINAPI device_manager_decoder_service_CreateVideoDecoder(IDirectXVideoDecoderService *iface, REFGUID guid, const DXVA2_VideoDesc *video_desc, const DXVA2_ConfigPictureDecode *config, IDirect3DSurface9 **rts, UINT num_surfaces, IDirectXVideoDecoder **decoder) { - FIXME("%p, %s, %p, %p, %p, %u, %p.\n", iface, debugstr_guid(guid), video_desc, config, rts, num_surfaces, + struct decoder_create_params create_params = { 0 }; + struct video_decoder *object; + unsigned int i; + + TRACE("%p, %s, %p, %p, %p, %u, %p.\n", iface, debugstr_guid(guid), video_desc, config, rts, num_surfaces, decoder); - return E_NOTIMPL; + if (!(object = calloc(1, sizeof(*object)))) + return E_OUTOFMEMORY; + + if (!(object->surfaces = calloc(num_surfaces, sizeof(*object->surfaces)))) + { + free(object); + return E_OUTOFMEMORY; + } + + create_params.guid = *guid; + create_params.width = video_desc->SampleWidth; + create_params.height = video_desc->SampleHeight; + create_params.surface_count = num_surfaces; + + if (FAILED(DXVA2_CALL(decoder_create, &create_params)) || !create_params.context) + { + WARN("Failed to create hardware decode context for %s.\n", debugstr_guid(guid)); + free(object->surfaces); + free(object); + return E_FAIL; + } + + object->IDirectXVideoDecoder_iface.lpVtbl = &video_decoder_vtbl; + object->refcount = 1; + object->service = iface; + IDirectXVideoDecoderService_AddRef(object->service); + object->guid = *guid; + object->video_desc = *video_desc; + object->config = *config; + object->surface_count = num_surfaces; + object->unix_context = create_params.context; + + for (i = 0; i < num_surfaces; ++i) + { + object->surfaces[i] = rts[i]; + IDirect3DSurface9_AddRef(object->surfaces[i]); + } + + *decoder = &object->IDirectXVideoDecoder_iface; + + return S_OK; } static const IDirectXVideoDecoderServiceVtbl device_manager_decoder_service_vtbl = diff --git a/dlls/dxva2/unixlib.c b/dlls/dxva2/unixlib.c index 051c76ba5dc..e973b00ff01 100644 --- a/dlls/dxva2/unixlib.c +++ b/dlls/dxva2/unixlib.c @@ -26,21 +26,25 @@ #include <stdarg.h> #include <stdlib.h> +#include <string.h> #include <fcntl.h> #include <unistd.h> #ifdef HAVE_LIBVA #include <va/va.h> #include <va/va_drm.h> +#include <va/va_dec_hevc.h> #endif #include "ntstatus.h" #define WIN32_NO_STATUS #include "windef.h" #include "winternl.h" +#include "dxva.h" #include "wine/debug.h" #include "unixlib.h" +#include "hevc.h" #ifdef HAVE_LIBVA @@ -162,6 +166,343 @@ static NTSTATUS query_decoder_profiles( void *args ) return STATUS_SUCCESS; } +struct decoder_context +{ + VADisplay display; + int fd; + VAConfigID config; + VAContextID context; + VASurfaceID *surfaces; + UINT surface_count; + UINT bitdepth; + UINT width; + UINT height; +}; + +static VAProfile guid_to_profile( const GUID *guid, UINT *bitdepth ) +{ + if (IsEqualGUID( guid, &guid_h264_vld_nofgt )) { *bitdepth = 8; return VAProfileH264Main; } + if (IsEqualGUID( guid, &guid_hevc_vld_main )) { *bitdepth = 8; return VAProfileHEVCMain; } + if (IsEqualGUID( guid, &guid_hevc_vld_main10 )) { *bitdepth = 10; return VAProfileHEVCMain10; } + return VAProfileNone; +} + +static NTSTATUS decoder_create( void *args ) +{ + struct decoder_create_params *params = args; + struct decoder_context *ctx; + VAProfile profile; + UINT bitdepth; + VADisplay display; + int fd = -1; + VAConfigAttrib attrib; + VAConfigID config; + VAContextID context; + VASurfaceID *surfaces; + unsigned int rt_format; + + params->context = 0; + + if ((profile = guid_to_profile( ¶ms->guid, &bitdepth )) == VAProfileNone) + { + WARN( "Unsupported decoder GUID %s.\n", debugstr_guid( ¶ms->guid ) ); + return STATUS_NOT_SUPPORTED; + } + + if (!(display = va_open( &fd ))) + return STATUS_UNSUCCESSFUL; + + rt_format = bitdepth > 8 ? VA_RT_FORMAT_YUV420_10 : VA_RT_FORMAT_YUV420; + + attrib.type = VAConfigAttribRTFormat; + attrib.value = rt_format; + if (vaCreateConfig( display, profile, VAEntrypointVLD, &attrib, 1, &config ) != VA_STATUS_SUCCESS) + { + WARN( "vaCreateConfig failed.\n" ); + vaTerminate( display ); + close( fd ); + return STATUS_UNSUCCESSFUL; + } + + if (!(surfaces = calloc( params->surface_count, sizeof(*surfaces) ))) + { + vaDestroyConfig( display, config ); + vaTerminate( display ); + close( fd ); + return STATUS_NO_MEMORY; + } + + if (vaCreateSurfaces( display, rt_format, params->width, params->height, surfaces, + params->surface_count, NULL, 0 ) != VA_STATUS_SUCCESS) + { + WARN( "vaCreateSurfaces failed.\n" ); + free( surfaces ); + vaDestroyConfig( display, config ); + vaTerminate( display ); + close( fd ); + return STATUS_UNSUCCESSFUL; + } + + if (vaCreateContext( display, config, params->width, params->height, VA_PROGRESSIVE, + surfaces, params->surface_count, &context ) != VA_STATUS_SUCCESS) + { + WARN( "vaCreateContext failed.\n" ); + vaDestroySurfaces( display, surfaces, params->surface_count ); + free( surfaces ); + vaDestroyConfig( display, config ); + vaTerminate( display ); + close( fd ); + return STATUS_UNSUCCESSFUL; + } + + if (!(ctx = malloc( sizeof(*ctx) ))) + { + vaDestroyContext( display, context ); + vaDestroySurfaces( display, surfaces, params->surface_count ); + free( surfaces ); + vaDestroyConfig( display, config ); + vaTerminate( display ); + close( fd ); + return STATUS_NO_MEMORY; + } + + ctx->display = display; + ctx->fd = fd; + ctx->config = config; + ctx->context = context; + ctx->surfaces = surfaces; + ctx->surface_count = params->surface_count; + ctx->bitdepth = bitdepth; + ctx->width = params->width; + ctx->height = params->height; + + TRACE( "Created decoder context %p, profile %d, %ux%u, %u surfaces.\n", + ctx, profile, params->width, params->height, params->surface_count ); + + params->context = (UINT_PTR)ctx; + return STATUS_SUCCESS; +} + +static NTSTATUS decoder_destroy( void *args ) +{ + struct decoder_destroy_params *params = args; + struct decoder_context *ctx = (struct decoder_context *)(UINT_PTR)params->context; + + if (!ctx) return STATUS_SUCCESS; + + vaDestroyContext( ctx->display, ctx->context ); + vaDestroySurfaces( ctx->display, ctx->surfaces, ctx->surface_count ); + free( ctx->surfaces ); + vaDestroyConfig( ctx->display, ctx->config ); + vaTerminate( ctx->display ); + close( ctx->fd ); + free( ctx ); + + return STATUS_SUCCESS; +} + +static void copy_plane( BYTE *dst, UINT dst_stride, const BYTE *src, UINT src_stride, UINT height ) +{ + UINT row, copy_len = dst_stride < src_stride ? dst_stride : src_stride; + + for (row = 0; row < height; row++) + memcpy( dst + row * dst_stride, src + row * src_stride, copy_len ); +} + +static NTSTATUS decoder_decode_frame( void *args ) +{ + struct decoder_decode_params *params = args; + struct decoder_context *ctx = (struct decoder_context *)(UINT_PTR)params->context; + const DXVA_PicParams_HEVC *pic; + const DXVA_Slice_HEVC_Short *slices; + VAPictureParameterBufferHEVC va_pic; + VAIQMatrixBufferHEVC va_iq; + VABufferID pic_buf, iq_buf = VA_INVALID_ID; + VABufferID render_bufs[2]; + VAImage image; + void *image_data; + NTSTATUS status = STATUS_UNSUCCESSFUL; + UINT i; + + if (!ctx || params->target_surface_index >= ctx->surface_count) + return STATUS_INVALID_PARAMETER; + if (params->pic_params_size < sizeof(*pic)) + return STATUS_INVALID_PARAMETER; + + pic = params->pic_params; + slices = params->slice_control; + + dxva2_hevc_translate_pic_params( pic, &va_pic, ctx->bitdepth, ctx->bitdepth, ctx->surfaces, ctx->surface_count ); + + if (vaCreateBuffer( ctx->display, ctx->context, VAPictureParameterBufferType, sizeof(va_pic), 1, &va_pic, + &pic_buf ) != VA_STATUS_SUCCESS) + { + WARN( "Failed to create picture parameter buffer.\n" ); + return STATUS_UNSUCCESSFUL; + } + + if (params->qmatrix && params->qmatrix_size >= sizeof(DXVA_Qmatrix_HEVC)) + { + dxva2_hevc_translate_iq_matrix( params->qmatrix, &va_iq ); + if (vaCreateBuffer( ctx->display, ctx->context, VAIQMatrixBufferType, sizeof(va_iq), 1, &va_iq, + &iq_buf ) != VA_STATUS_SUCCESS) + { + WARN( "Failed to create IQ matrix buffer.\n" ); + iq_buf = VA_INVALID_ID; + } + } + + if (vaBeginPicture( ctx->display, ctx->context, ctx->surfaces[params->target_surface_index] ) != VA_STATUS_SUCCESS) + { + WARN( "vaBeginPicture failed.\n" ); + return STATUS_UNSUCCESSFUL; + } + + render_bufs[0] = pic_buf; + if (iq_buf != VA_INVALID_ID) + { + render_bufs[1] = iq_buf; + vaRenderPicture( ctx->display, ctx->context, render_bufs, 2 ); + } + else + { + vaRenderPicture( ctx->display, ctx->context, render_bufs, 1 ); + } + + for (i = 0; i < params->slice_count; i++) + { + const BYTE *nal_data = (const BYTE *)params->bitstream + slices[i].BSNALunitDataLocation; + UINT nal_size = slices[i].SliceBytesInBuffer; + VASliceParameterBufferHEVC va_slice; + VABufferID slice_bufs[2]; + + if (slices[i].BSNALunitDataLocation + (UINT64)nal_size > params->bitstream_size) + { + WARN( "Slice %u out of bounds of the bitstream buffer.\n", i ); + continue; + } + + /* DXVA raw-bitstream submissions are Annex B: each slice NAL is + * prefixed with a start code (FFmpeg-based clients write 00 00 01), + * but both our header parser and VA-API's slice data buffer expect + * the NAL to start at the NAL unit header. */ + if (nal_size >= 4 && !nal_data[0] && !nal_data[1] && !nal_data[2] && nal_data[3] == 1) + { + nal_data += 4; + nal_size -= 4; + } + else if (nal_size >= 3 && !nal_data[0] && !nal_data[1] && nal_data[2] == 1) + { + nal_data += 3; + nal_size -= 3; + } + + if (!dxva2_hevc_translate_slice( pic, nal_data, nal_size, &va_slice, 0 )) + { + WARN( "Failed to parse slice %u header.\n", i ); + continue; + } + if (i == params->slice_count - 1) va_slice.LongSliceFlags.fields.LastSliceOfPic = 1; + + if (vaCreateBuffer( ctx->display, ctx->context, VASliceParameterBufferType, sizeof(va_slice), 1, &va_slice, + &slice_bufs[0] ) != VA_STATUS_SUCCESS) + { + WARN( "Failed to create slice parameter buffer for slice %u.\n", i ); + continue; + } + if (vaCreateBuffer( ctx->display, ctx->context, VASliceDataBufferType, nal_size, 1, (void *)nal_data, + &slice_bufs[1] ) != VA_STATUS_SUCCESS) + { + WARN( "Failed to create slice data buffer for slice %u.\n", i ); + continue; + } + + vaRenderPicture( ctx->display, ctx->context, slice_bufs, 2 ); + } + + vaEndPicture( ctx->display, ctx->context ); + vaSyncSurface( ctx->display, ctx->surfaces[params->target_surface_index] ); + + if (vaDeriveImage( ctx->display, ctx->surfaces[params->target_surface_index], &image ) != VA_STATUS_SUCCESS) + { + VAImageFormat format; + + /* Some drivers (e.g. nvidia-vaapi-driver) don't support deriving a + * CPU-mappable image directly from a decode surface - fall back to + * an explicit vaCreateImage + vaGetImage copy instead. Format must + * match the RT format the surfaces were created with in + * decoder_create(). */ + WARN( "vaDeriveImage failed, falling back to vaGetImage.\n" ); + + memset( &format, 0, sizeof(format) ); + format.fourcc = ctx->bitdepth > 8 ? VA_FOURCC_P010 : VA_FOURCC_NV12; + format.byte_order = VA_LSB_FIRST; + format.bits_per_pixel = ctx->bitdepth > 8 ? 24 : 12; + + if (vaCreateImage( ctx->display, &format, ctx->width, ctx->height, &image ) != VA_STATUS_SUCCESS) + { + WARN( "vaCreateImage fallback failed.\n" ); + return STATUS_UNSUCCESSFUL; + } + + if (vaGetImage( ctx->display, ctx->surfaces[params->target_surface_index], 0, 0, ctx->width, ctx->height, + image.image_id ) != VA_STATUS_SUCCESS) + { + WARN( "vaGetImage fallback failed.\n" ); + vaDestroyImage( ctx->display, image.image_id ); + return STATUS_UNSUCCESSFUL; + } + } + + TRACE( "va image: %ux%u, format %#x, num_planes %u, pitches[0]=%u pitches[1]=%u offsets[0]=%u offsets[1]=%u; " + "output: stride %u height %u (surface requested at decoder_create: %ux%u); pic_params: %ux%u luma samples, " + "slice_count %u.\n", + image.width, image.height, image.format.fourcc, image.num_planes, image.pitches[0], image.pitches[1], + image.offsets[0], image.offsets[1], params->output_stride, params->output_height, ctx->width, ctx->height, + va_pic.pic_width_in_luma_samples, va_pic.pic_height_in_luma_samples, params->slice_count ); + + if (vaMapBuffer( ctx->display, image.buf, &image_data ) == VA_STATUS_SUCCESS) + { + UINT bytes_per_sample = ctx->bitdepth > 8 ? 2 : 1; + UINT copy_height = min( params->output_height, image.height ); + BYTE *luma_dst = params->output; + BYTE *chroma_dst = (BYTE *)params->output + params->output_stride * params->output_height; + + /* NV12/P010: plane 0 luma, plane 1 interleaved UV at half height. */ + copy_plane( luma_dst, params->output_stride, (BYTE *)image_data + image.offsets[0], image.pitches[0], + copy_height ); + if (image.num_planes > 1) + copy_plane( chroma_dst, params->output_stride, (BYTE *)image_data + image.offsets[1], image.pitches[1], + copy_height / 2 ); + (void)bytes_per_sample; + + /* The app's D3D9 surface can be taller than the actual decoded picture + * (e.g. padded to its own alignment boundary independent of the coded + * size) - fill anything beyond what we actually decoded with black + * instead of leaving it as stale D3DLOCK_DISCARD memory. */ + if (params->output_height > copy_height) + { + UINT extra_luma = params->output_height - copy_height; + UINT extra_chroma = params->output_height / 2 - copy_height / 2; + + memset( luma_dst + copy_height * params->output_stride, 0, extra_luma * params->output_stride ); + memset( chroma_dst + (copy_height / 2) * params->output_stride, 0x80, + extra_chroma * params->output_stride ); + } + + vaUnmapBuffer( ctx->display, image.buf ); + status = STATUS_SUCCESS; + } + else + { + WARN( "vaMapBuffer failed.\n" ); + } + + vaDestroyImage( ctx->display, image.image_id ); + + return status; +} + #else /* HAVE_LIBVA */ static NTSTATUS query_decoder_profiles( void *args ) @@ -171,11 +512,31 @@ static NTSTATUS query_decoder_profiles( void *args ) return STATUS_SUCCESS; } +static NTSTATUS decoder_create( void *args ) +{ + struct decoder_create_params *params = args; + params->context = 0; + return STATUS_NOT_SUPPORTED; +} + +static NTSTATUS decoder_destroy( void *args ) +{ + return STATUS_SUCCESS; +} + +static NTSTATUS decoder_decode_frame( void *args ) +{ + return STATUS_NOT_SUPPORTED; +} + #endif /* HAVE_LIBVA */ const unixlib_entry_t __wine_unix_call_funcs[] = { query_decoder_profiles, + decoder_create, + decoder_destroy, + decoder_decode_frame, }; C_ASSERT( ARRAY_SIZE(__wine_unix_call_funcs) == unix_funcs_count ); @@ -203,9 +564,82 @@ static NTSTATUS wow64_query_decoder_profiles( void *args ) return status; } +static NTSTATUS wow64_decoder_create( void *args ) +{ + struct + { + GUID guid; + UINT width; + UINT height; + UINT surface_count; + UINT64 context; + } *params32 = args; + struct decoder_create_params params = + { + params32->guid, + params32->width, + params32->height, + params32->surface_count, + 0, + }; + NTSTATUS status = decoder_create( ¶ms ); + params32->context = params.context; + return status; +} + +static NTSTATUS wow64_decoder_destroy( void *args ) +{ + struct + { + UINT64 context; + } *params32 = args; + struct decoder_destroy_params params = { params32->context }; + return decoder_destroy( ¶ms ); +} + +static NTSTATUS wow64_decoder_decode_frame( void *args ) +{ + struct + { + UINT64 context; + UINT target_surface_index; + PTR32 pic_params; + UINT pic_params_size; + PTR32 qmatrix; + UINT qmatrix_size; + PTR32 slice_control; + UINT slice_count; + PTR32 bitstream; + UINT bitstream_size; + PTR32 output; + UINT output_stride; + UINT output_height; + } *params32 = args; + struct decoder_decode_params params = + { + params32->context, + params32->target_surface_index, + ULongToPtr(params32->pic_params), + params32->pic_params_size, + ULongToPtr(params32->qmatrix), + params32->qmatrix_size, + ULongToPtr(params32->slice_control), + params32->slice_count, + ULongToPtr(params32->bitstream), + params32->bitstream_size, + ULongToPtr(params32->output), + params32->output_stride, + params32->output_height, + }; + return decoder_decode_frame( ¶ms ); +} + const unixlib_entry_t __wine_unix_call_wow64_funcs[] = { wow64_query_decoder_profiles, + wow64_decoder_create, + wow64_decoder_destroy, + wow64_decoder_decode_frame, }; C_ASSERT( ARRAY_SIZE(__wine_unix_call_wow64_funcs) == unix_funcs_count ); diff --git a/dlls/dxva2/unixlib.h b/dlls/dxva2/unixlib.h index 36e590fdf4b..05cb5a15c13 100644 --- a/dlls/dxva2/unixlib.h +++ b/dlls/dxva2/unixlib.h @@ -39,9 +39,56 @@ struct query_decoder_profiles_params UINT count; /* [out] number of entries filled in */ }; +struct decoder_create_params +{ + GUID guid; /* [in] DXVA2 decoder mode GUID, e.g. DXVA2_ModeHEVC_VLD_Main */ + UINT width; /* [in] */ + UINT height; /* [in] */ + UINT surface_count; /* [in] number of decode surfaces to allocate (matches D3D9 surface array size) */ + UINT64 context; /* [out] opaque handle for later decoder_destroy/decoder_decode_frame calls */ +}; + +struct decoder_destroy_params +{ + UINT64 context; /* [in] */ +}; + +/* One decode_frame call handles exactly one compressed picture: a picture-parameters + * buffer, an optional inverse-quantization-matrix buffer, and one or more slices + * (short format: NAL location/size per slice, DXVA_Slice_HEVC_Short array) sharing a + * single bitstream buffer. Output is written directly into a caller-allocated NV12/P010 + * buffer matching the target D3D9 surface's locked memory, avoiding an extra copy on + * the unix side (the PE side still does one copy: VA's derived image -> the locked D3D9 + * surface, since VA image memory can't be the D3D9 surface's backing store directly). + */ +struct decoder_decode_params +{ + UINT64 context; /* [in] */ + UINT target_surface_index; /* [in] index into the surface array from decoder_create, selects the DPB slot this frame decodes into */ + + const void *pic_params; /* [in] raw DXVA_PicParams_HEVC bytes, as filled by the app */ + UINT pic_params_size; /* [in] */ + + const void *qmatrix; /* [in] raw DXVA_Qmatrix_HEVC bytes, or NULL if scaling_list_enabled_flag is off */ + UINT qmatrix_size; /* [in] */ + + const void *slice_control; /* [in] array of DXVA_Slice_HEVC_Short */ + UINT slice_count; /* [in] number of entries in slice_control */ + + const void *bitstream; /* [in] raw compressed NAL data for all slices in this frame */ + UINT bitstream_size; /* [in] */ + + void *output; /* [out] caller-allocated buffer, NV12 or P010, tightly matching output_stride/output_height */ + UINT output_stride; /* [in] bytes per row of the luma plane (chroma plane assumed same stride, half height) */ + UINT output_height; /* [in] luma plane height in pixels */ +}; + enum unix_funcs { unix_query_decoder_profiles, + unix_decoder_create, + unix_decoder_destroy, + unix_decoder_decode_frame, unix_funcs_count, }; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11346
FWIW, I do have a VA backend to d3d video decode written, but not yet upstreamed. I don't have HEVC support yet, however.
This is <https://gitlab.winehq.org/wine/wine/-/merge_requests/11535>. Sorry it took so long to upstream; I had to rebase and fix some of the patches, and then one of our reviewers was out for a while. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11346#note_147359
On Tue Aug 11 16:52:15 2026 +0000, Elizabeth Figura wrote:
FWIW, I do have a VA backend to d3d video decode written, but not yet upstreamed. I don't have HEVC support yet, however. This is <https://gitlab.winehq.org/wine/wine/-/merge_requests/11535>. Sorry it took so long to upstream; I had to rebase and fix some of the patches, and then one of our reviewers was out for a while. Is a rebase required for this or something else completely. I received a note that it could not be merged due to conflict.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11346#note_148640
On Tue Aug 11 16:52:15 2026 +0000, Valerian Mayega wrote:
Is a rebase required for this or something else completely. I received a note that it could not be merged due to conflict. Like I mentioned earlier we should implement this over wined3d APIs rather than using VA directly.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11346#note_148649
On Tue Aug 11 19:53:15 2026 +0000, Elizabeth Figura wrote:
Like I mentioned earlier we should implement this over wined3d APIs rather than using VA directly. FWIW, it should be pretty easy to see how to do this by looking at d3d11 as a model. The APIs are very similar.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11346#note_148679
participants (3)
-
Elizabeth Figura (@zfigura) -
Valerian Mayega -
Valerian Mayega (@valerian)