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