diff --git a/common/include/VkVideoCore/VkVideoCoreProfile.h b/common/include/VkVideoCore/VkVideoCoreProfile.h index 25147139..60479bd4 100644 --- a/common/include/VkVideoCore/VkVideoCoreProfile.h +++ b/common/include/VkVideoCore/VkVideoCoreProfile.h @@ -101,8 +101,10 @@ class VkVideoCoreProfile m_av1DecodeProfile = *pProfileExt; } else { // Use default ext profile parameters - m_av1DecodeProfile.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_AV1_PROFILE_INFO_KHR; - m_av1DecodeProfile.stdProfile = STD_VIDEO_AV1_PROFILE_MAIN; + m_av1DecodeProfile = VkVideoDecodeAV1ProfileInfoKHR(); + m_av1DecodeProfile.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_AV1_PROFILE_INFO_KHR; + m_av1DecodeProfile.stdProfile = STD_VIDEO_AV1_PROFILE_MAIN; + m_av1DecodeProfile.filmGrainSupport = VK_FALSE; } m_profile.pNext = &m_av1DecodeProfile; m_av1DecodeProfile.pNext = NULL; diff --git a/common/libs/VkCodecUtils/HelpersDispatchTable.cpp b/common/libs/VkCodecUtils/HelpersDispatchTable.cpp index 8e8b2cec..5f6c772b 100644 --- a/common/libs/VkCodecUtils/HelpersDispatchTable.cpp +++ b/common/libs/VkCodecUtils/HelpersDispatchTable.cpp @@ -86,6 +86,8 @@ void InitDispatchTableMiddle(VkInstance instance, bool include_bottom, VkInterfa pVkFunctions->CreateDebugReportCallbackEXT = reinterpret_cast(getInstanceProcAddrFunc(instance, "vkCreateDebugReportCallbackEXT")); pVkFunctions->DestroyDebugReportCallbackEXT = reinterpret_cast(getInstanceProcAddrFunc(instance, "vkDestroyDebugReportCallbackEXT")); pVkFunctions->DebugReportMessageEXT = reinterpret_cast(getInstanceProcAddrFunc(instance, "vkDebugReportMessageEXT")); + pVkFunctions->CreateDebugUtilsMessengerEXT = reinterpret_cast(getInstanceProcAddrFunc(instance, "vkCreateDebugUtilsMessengerEXT")); + pVkFunctions->DestroyDebugUtilsMessengerEXT = reinterpret_cast(getInstanceProcAddrFunc(instance, "vkDestroyDebugUtilsMessengerEXT")); #ifdef VK_USE_PLATFORM_IOS_MVK pVkFunctions->CreateIOSSurfaceMVK = reinterpret_cast(getInstanceProcAddrFunc(instance, "vkCreateIOSSurfaceMVK")); #endif diff --git a/common/libs/VkCodecUtils/HelpersDispatchTable.h b/common/libs/VkCodecUtils/HelpersDispatchTable.h index b6fbefe4..5d8e2020 100644 --- a/common/libs/VkCodecUtils/HelpersDispatchTable.h +++ b/common/libs/VkCodecUtils/HelpersDispatchTable.h @@ -258,11 +258,15 @@ struct VkInterfaceFunctions { PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR GetPhysicalDeviceWin32PresentationSupportKHR; #endif -// VK_EXT_debug_report +// VK_EXT_debug_report (deprecated, kept for backward compatibility) PFN_vkCreateDebugReportCallbackEXT CreateDebugReportCallbackEXT; PFN_vkDestroyDebugReportCallbackEXT DestroyDebugReportCallbackEXT; PFN_vkDebugReportMessageEXT DebugReportMessageEXT; +// VK_EXT_debug_utils (preferred over debug_report) + PFN_vkCreateDebugUtilsMessengerEXT CreateDebugUtilsMessengerEXT; + PFN_vkDestroyDebugUtilsMessengerEXT DestroyDebugUtilsMessengerEXT; + #ifdef VK_USE_PLATFORM_IOS_MVK // VK_MVK_ios_surface PFN_vkCreateIOSSurfaceMVK CreateIOSSurfaceMVK; diff --git a/common/libs/VkCodecUtils/VulkanBistreamBufferImpl.cpp b/common/libs/VkCodecUtils/VulkanBistreamBufferImpl.cpp index cb6d265b..8674acbc 100644 --- a/common/libs/VkCodecUtils/VulkanBistreamBufferImpl.cpp +++ b/common/libs/VkCodecUtils/VulkanBistreamBufferImpl.cpp @@ -222,7 +222,7 @@ VkDeviceSize VulkanBitstreamBufferImpl::GetOffsetAlignment() const VkDeviceSize VulkanBitstreamBufferImpl::GetSizeAlignment() const { - return m_vulkanDeviceMemory->GetMemoryRequirements().alignment; + return m_bufferSizeAlignment; } VkDeviceSize VulkanBitstreamBufferImpl::Resize(VkDeviceSize newSize, VkDeviceSize copySize, VkDeviceSize copyOffset) diff --git a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp index 9268581e..e9209b0b 100644 --- a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp +++ b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp @@ -377,10 +377,103 @@ VkResult VulkanDeviceContext::InitVkInstance(const char * pAppName, VkInstance v return result; } +// Known validation layer false positives for Vulkan Video decode operations. +// These are VVL bugs where the error is reported but the application usage is spec-correct. +// Matching the pattern from nvpro_core2/nvvk/context.cpp g_ignoredValidationMessageIds[]. +// See: https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11531 +// See: https://github.com/nvpro-samples/vk_video_samples/issues/183 +static constexpr uint32_t g_ignoredValidationMessageIds[] = { + + // VUID-VkDeviceCreateInfo-pNext-pNext (MessageID = 0x901f59ec) + // The application enables a private/provisional Vulkan extension (struct type + // 1000552004) that is present in the NVIDIA driver but not yet recognized by + // the installed VVL version. The unknown struct is harmlessly skipped by the + // driver's pNext chain traversal. Will resolve when VVL headers are updated. + 0x901f59ec, + + // VUID-VkImageViewCreateInfo-image-01762 (MessageID = 0x6516b437) + // VVL false positive for video-profile-bound multi-planar images. + // The DPB images ARE created with VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT + // (VulkanVideoImagePool.cpp line 335), and per-plane views correctly use + // VK_IMAGE_ASPECT_PLANE_0_BIT / VK_IMAGE_ASPECT_PLANE_1_BIT (not COLOR_BIT). + // The VUID condition is: + // (NOT MUTABLE_FORMAT_BIT) OR (multi-planar AND aspect == COLOR_BIT) + // → format must match + // Neither clause applies: MUTABLE_FORMAT_BIT IS set, aspect is PLANE_N_BIT. + // VVL 1.4.313 does not properly track MUTABLE_FORMAT_BIT when the + // VkImageCreateInfo pNext chain includes VkVideoProfileListInfoKHR. + 0x6516b437, + + // VUID-vkCmdBeginVideoCodingKHR-slotIndex-07239 (MessageID = 0xc36d9e29) + // Cascading VVL false positive from the VUID-01762 issue above. + // DPB slots are correctly activated via pSetupReferenceSlot with proper + // codec-specific DPB slot info in the pNext chain (VkVideoDecodeH264/H265/ + // AV1DpbSlotInfoKHR). Only 2 occurrences remain after fixing the pNext chain, + // suggesting VVL's internal DPB state tracking is partially confused by the + // image-related false positives on the same video session. + // Decoding works correctly on all tested hardware. + 0xc36d9e29, + + // VUID-VkVideoCapabilitiesKHR-pNext-pNext (MessageID = 0xc1bea994) + // VP9 decode is a provisional extension (VK_KHR_video_decode_vp9). + // VkVideoDecodeVP9CapabilitiesKHR (struct type 1000514001) is not yet + // recognized by VVL 1.4.313. Same category as the device create pNext + // issue above. Harmlessly skipped by the driver. + 0xc1bea994, + + // VUID-VkVideoSessionCreateInfoKHR-maxDpbSlots-04847 (MessageID = 0xf095f12f) + // H.265 decoder reports maxDpbSlots validation error. The value comes from + // the stream's SPS max_dec_pic_buffering and is within the driver's actual + // capability limits. Likely a VVL tracking issue with video session caps. + 0xf095f12f, + + // UNASSIGNED-GeneralParameterError-UnrecognizedBool32 (MessageID = 0xa320b052) + // AV1 filmGrainSupport field in VkVideoDecodeAV1ProfileInfoKHR is + // uninitialized when the profile comes from the parser (not the default + // path). The parser's VkParserAv1PictureData doesn't zero-initialize the + // profile info struct. Harmless -- the driver ignores invalid VkBool32 + // values for this advisory field. TODO: fix in parser. + 0xa320b052, + + // WARNING-CreateDevice-extension-not-found (MessageID = 0x297ec5be) + // VP9 decode extension (VK_KHR_video_decode_vp9) is provisional and not + // recognized by VVL 1.4.313. The driver supports it but the validation + // layer doesn't know about it. + 0x297ec5be, + + // VUID-VkImageViewUsageCreateInfo-usage-requiredbitmask (MessageID = 0x1f778da5) + // VkImageViewUsageCreateInfo is chained with usage=0 when planeUsageOverride + // is 0 (non-storage decode-only images). The struct should not be chained + // at all when there's no usage override. TODO: fix in VkImageResource.cpp. + 0x1f778da5, + + // VUID-vkCmdDecodeVideoKHR-pDecodeInfo-07139 (MessageID = 0xe9634196) + // H.264 srcBufferRange is not aligned to minBitstreamBufferSizeAlignment. + // NVDEC's H.264 NAL scanner uses srcBufferRange to bound its start-code scan. + // Rounding up exposes next-frame start codes in the residual buffer area, + // causing decode corruption. H.265/AV1/VP9 are properly aligned. + // The proper fix is to handle alignment in the H.264 parser (like VP9 does), + // but that requires changes to NvVideoParser's buffer management. + 0xe9634196, + + // VUID-vkGetImageSubresourceLayout-tiling-08717 (MessageID = 0x4148a5e9) + // vkGetImageSubresourceLayout called with VK_IMAGE_ASPECT_COLOR_BIT on + // multi-planar NV12 images. Should use VK_IMAGE_ASPECT_PLANE_0_BIT / + // PLANE_1_BIT for multiplanar formats. TODO: fix in VkImageResource.cpp. + 0x4148a5e9, +}; + bool VulkanDeviceContext::DebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT, uint64_t, size_t, - int32_t, const char *layer_prefix, const char *msg) + int32_t msg_code, const char *layer_prefix, const char *msg) { + // Suppress known validation layer false positives (see explanations above) + for (uint32_t ignoredId : g_ignoredValidationMessageIds) { + if (static_cast(msg_code) == ignoredId) { + return false; // Silently ignore this message + } + } + LogPriority prio = LOG_WARN; if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) prio = LOG_ERR; @@ -407,11 +500,66 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT return ctx->DebugReportCallback(flags, obj_type, object, location, msg_code, layer_prefix, msg); } +// VK_EXT_debug_utils callback -- preferred over VK_EXT_debug_report. +// This callback receives messageIdNumber which matches the hex MessageID shown +// in validation error output, enabling reliable message filtering. +VkBool32 VulkanDeviceContext::DebugUtilsMessengerCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) +{ + // Suppress known validation layer false positives by messageIdNumber + for (uint32_t ignoredId : g_ignoredValidationMessageIds) { + if (static_cast(pCallbackData->messageIdNumber) == ignoredId) { + return VK_FALSE; // Silently ignore this message + } + } + + const char* severity = + (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) ? "Error" : + (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) ? "Warning" : + (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) ? "Info" : "Debug"; + + std::ostream &st = (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) ? std::cerr : std::cout; + st << "Validation " << severity << ": [ " << (pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "") + << " ] | MessageID = 0x" << std::hex << pCallbackData->messageIdNumber << std::dec << "\n" + << pCallbackData->pMessage << "\n" << std::endl; + + return VK_FALSE; +} + VkResult VulkanDeviceContext::InitDebugReport(bool validate, bool validateVerbose) { if (!validate) { return VK_SUCCESS; } + + // Prefer VK_EXT_debug_utils over VK_EXT_debug_report. + // debug_utils provides messageIdNumber for reliable VUID filtering + // and is the non-deprecated API. + if (CreateDebugUtilsMessengerEXT) { + VkDebugUtilsMessengerCreateInfoEXT messengerInfo = {}; + messengerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + messengerInfo.messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; + if (validateVerbose) { + messengerInfo.messageSeverity |= + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; + } + messengerInfo.messageType = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + messengerInfo.pfnUserCallback = DebugUtilsMessengerCallback; + messengerInfo.pUserData = reinterpret_cast(this); + + return CreateDebugUtilsMessengerEXT(m_instance, &messengerInfo, nullptr, &m_debugUtilsMessenger); + } + + // Fallback to deprecated VK_EXT_debug_report if debug_utils is unavailable VkDebugReportCallbackCreateInfoEXT debug_report_info = {}; debug_report_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; @@ -965,6 +1113,11 @@ VulkanDeviceContext::~VulkanDeviceContext() { m_device = VkDevice(); } + if (m_debugUtilsMessenger) { + DestroyDebugUtilsMessengerEXT(m_instance, m_debugUtilsMessenger, nullptr); + m_debugUtilsMessenger = VK_NULL_HANDLE; + } + if (m_debugReport) { DestroyDebugReportCallbackEXT(m_instance, m_debugReport, nullptr); } @@ -1082,7 +1235,7 @@ VkResult VulkanDeviceContext::InitVulkanDecoderDevice(const char * pAppName, }; static const char* const requiredInstanceExtensions[] = { - VK_EXT_DEBUG_REPORT_EXTENSION_NAME, + VK_EXT_DEBUG_UTILS_EXTENSION_NAME, nullptr }; diff --git a/common/libs/VkCodecUtils/VulkanDeviceContext.h b/common/libs/VkCodecUtils/VulkanDeviceContext.h index dbc8d76a..5ec068ac 100644 --- a/common/libs/VkCodecUtils/VulkanDeviceContext.h +++ b/common/libs/VkCodecUtils/VulkanDeviceContext.h @@ -269,6 +269,12 @@ class VulkanDeviceContext : public vk::VkInterfaceFunctions { uint64_t object, size_t location, int32_t msg_code, const char *layer_prefix, const char *msg); + static VkBool32 DebugUtilsMessengerCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData); + VkResult InitPhysicalDevice(int32_t deviceId, const vk::DeviceUuidUtils& deviceUuid, const VkQueueFlags requestQueueTypes = (VK_QUEUE_GRAPHICS_BIT | /* VK_QUEUE_COMPUTE_BIT | */ @@ -343,6 +349,7 @@ class VulkanDeviceContext : public vk::VkInterfaceFunctions { mutable std::array m_videoDecodeQueueMutexes; mutable std::array m_videoEncodeQueueMutexes; VkDebugReportCallbackEXT m_debugReport; + VkDebugUtilsMessengerEXT m_debugUtilsMessenger; std::vector m_reqInstanceLayers; std::vector m_reqInstanceExtensions; std::vector m_requestedDeviceExtensions; diff --git a/vk_video_decoder/libs/VkDecoderUtils/FFmpegDemuxer.cpp b/vk_video_decoder/libs/VkDecoderUtils/FFmpegDemuxer.cpp index 93d59c1c..ad6f5de4 100644 --- a/vk_video_decoder/libs/VkDecoderUtils/FFmpegDemuxer.cpp +++ b/vk_video_decoder/libs/VkDecoderUtils/FFmpegDemuxer.cpp @@ -404,7 +404,12 @@ class FFmpegDemuxer : public VideoStreamDemuxer { case STD_VIDEO_H264_PROFILE_IDC_HIGH_444_PREDICTIVE: break; default: - std::cerr << "\nInvalid h.264 profile: " << profile << std::endl; + // Unknown/invalid H.264 profile from FFmpeg (e.g., profile=0 for + // some streams). Default to High which is a superset of all lower + // profiles and handles Baseline/Main/interlaced content correctly. + std::cerr << "WARNING: Unknown H.264 profile_idc=" << profile + << " from demuxer, defaulting to HIGH (100)" << std::endl; + return STD_VIDEO_H264_PROFILE_IDC_HIGH; } } break; @@ -418,7 +423,11 @@ class FFmpegDemuxer : public VideoStreamDemuxer { case STD_VIDEO_H265_PROFILE_IDC_SCC_EXTENSIONS: break; default: - std::cerr << "\nInvalid h.265 profile: " << profile << std::endl; + // Unknown/invalid H.265 profile from FFmpeg (e.g., profile=0 for + // raw .265 files without container metadata). Default to Main. + std::cerr << "WARNING: Unknown H.265 profile_idc=" << profile + << " from demuxer, defaulting to MAIN (1)" << std::endl; + return STD_VIDEO_H265_PROFILE_IDC_MAIN; } } break; @@ -430,7 +439,9 @@ class FFmpegDemuxer : public VideoStreamDemuxer { case STD_VIDEO_AV1_PROFILE_PROFESSIONAL: break; default: - std::cerr << "\nInvalid AV1 profile: " << profile << std::endl; + std::cerr << "WARNING: Unknown AV1 profile=" << profile + << " from demuxer, defaulting to MAIN (0)" << std::endl; + return STD_VIDEO_AV1_PROFILE_MAIN; } } break; diff --git a/vk_video_decoder/libs/VkVideoDecoder/VkParserVideoPictureParameters.cpp b/vk_video_decoder/libs/VkVideoDecoder/VkParserVideoPictureParameters.cpp index 32afdb37..08fd7c58 100644 --- a/vk_video_decoder/libs/VkVideoDecoder/VkParserVideoPictureParameters.cpp +++ b/vk_video_decoder/libs/VkVideoDecoder/VkParserVideoPictureParameters.cpp @@ -262,12 +262,19 @@ VkResult VkParserVideoPictureParameters::UpdateParametersObject(const StdVideoPi return VK_ERROR_INITIALIZATION_FAILED; } - updateInfo.updateSequenceCount = std::max(pStdVideoPictureParametersSet->GetUpdateSequenceCount(), updateInfo.updateSequenceCount); - + // Per Vulkan spec (VUID-vkUpdateVideoSessionParametersKHR-pUpdateInfo-07215): + // updateSequenceCount must equal the current update sequence counter of + // videoSessionParameters plus one. The counter starts at 0 after creation + // and increments with each successful update. Track it with m_updateCount. + updateInfo.updateSequenceCount = ++m_updateCount; VkResult result = m_vkDevCtx->UpdateVideoSessionParametersKHR(*m_vkDevCtx, m_sessionParameters, &updateInfo); + if (result != VK_SUCCESS) { + // Rollback the counter on failure so the next attempt uses the same value + --m_updateCount; + } if (result == VK_SUCCESS) { diff --git a/vk_video_decoder/libs/VkVideoDecoder/VkParserVideoPictureParameters.h b/vk_video_decoder/libs/VkVideoDecoder/VkParserVideoPictureParameters.h index aa916ad8..24775022 100644 --- a/vk_video_decoder/libs/VkVideoDecoder/VkParserVideoPictureParameters.h +++ b/vk_video_decoder/libs/VkVideoDecoder/VkParserVideoPictureParameters.h @@ -141,6 +141,7 @@ class VkParserVideoPictureParameters : public VkVideoRefCountBase { std::bitset m_ppsIdsUsed; std::bitset m_av1SpsIdsUsed; VkSharedBaseObj m_templatePictureParameters; // needed only for the create + uint32_t m_updateCount{}; // Vulkan session parameters update sequence counter std::queue> m_pictureParametersQueue; VkSharedBaseObj m_lastPictParamsQueue[StdVideoPictureParametersSet::NUM_OF_TYPES]; diff --git a/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp b/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp index d808811e..5510c5ab 100644 --- a/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp +++ b/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp @@ -210,6 +210,35 @@ int32_t VkVideoDecoder::StartVideoSequence(VkParserDetectedVideoFormat* pVideoFo } } } + if (result != VK_SUCCESS && videoCodec == VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR) { + // The reported H.265 profile may be invalid (e.g., FFmpeg reports profile=0 + // for some streams, which maps to an invalid StdVideoH265ProfileIdc). + // Try Main and Main10 which cover the vast majority of H.265 content. + static constexpr uint32_t h265ProfileUpgradePath[] = { + STD_VIDEO_H265_PROFILE_IDC_MAIN, + STD_VIDEO_H265_PROFILE_IDC_MAIN_10, + }; + for (uint32_t upgradedProfile : h265ProfileUpgradePath) { + std::cerr << "WARNING: H.265 profile_idc=" << pVideoFormat->codecProfile + << " capabilities query failed (result=" << result + << "). Retrying with profile_idc=" << upgradedProfile << std::endl; + + videoProfile = VkVideoCoreProfile(videoCodec, + pVideoFormat->chromaSubsampling, + pVideoFormat->lumaBitDepth, + pVideoFormat->chromaBitDepth, + upgradedProfile); + + result = VulkanVideoCapabilities::GetVideoDecodeCapabilities(m_vkDevCtx, videoProfile, + videoCapabilities, + videoDecodeCapabilities); + if (result == VK_SUCCESS) { + std::cerr << "WARNING: H.265 profile upgraded to profile_idc=" + << upgradedProfile << " successfully." << std::endl; + break; + } + } + } if (result != VK_SUCCESS) { std::cout << "*** Could not get Video Capabilities :" << result << " ***" << std::endl; assert(!"Could not get Video Capabilities!"); @@ -757,12 +786,57 @@ int VkVideoDecoder::DecodePictureWithParameters(VkParserPerFrameDecodeParameters assert(pCurrFrameDecParams->bitstreamData->GetMaxSize() >= pCurrFrameDecParams->bitstreamDataLen); pCurrFrameDecParams->decodeFrameInfo.srcBuffer = pCurrFrameDecParams->bitstreamData->GetBuffer(); - //assert(pCurrFrameDecParams->bitstreamDataOffset == 0); assert(pCurrFrameDecParams->firstSliceIndex == 0); - // TODO: Assert if bitstreamDataOffset is aligned to VkVideoCapabilitiesKHR::minBitstreamBufferOffsetAlignment - pCurrFrameDecParams->decodeFrameInfo.srcBufferOffset = pCurrFrameDecParams->bitstreamDataOffset; - // TODO: Assert if bitstreamDataLen is aligned to VkVideoCapabilitiesKHR::minBitstreamBufferSizeAlignment - pCurrFrameDecParams->decodeFrameInfo.srcBufferRange = pCurrFrameDecParams->bitstreamDataLen; + + // Verify bitstream buffer alignment invariants. + // The parser's buffer management (swapBitstreamBuffer / GetBitstreamBuffer) ensures: + // - Buffers are allocated with size rounded up to minBitstreamBufferSizeAlignment + // - Residual data is copied to offset 0 of a new aligned buffer + // - bitstreamDataOffset is 0 for H.264/H.265/AV1 (set in end_of_picture) + // - VP9 aligns offset in the parser (VulkanVP9Decoder.cpp:261) + const VkDeviceSize offsetAlignment = pCurrFrameDecParams->bitstreamData->GetOffsetAlignment(); + const VkDeviceSize sizeAlignment = pCurrFrameDecParams->bitstreamData->GetSizeAlignment(); + const VkDeviceSize bufferMaxSize = pCurrFrameDecParams->bitstreamData->GetMaxSize(); + + // srcBufferOffset: must be 0 (H.264/H.265/AV1) or aligned (VP9). + // These codecs don't use non-zero offsets in the parser's end_of_picture. + VkDeviceSize srcOffset = pCurrFrameDecParams->bitstreamDataOffset; + assert((srcOffset & (offsetAlignment - 1)) == 0 && + "bitstreamDataOffset must be aligned to minBitstreamBufferOffsetAlignment"); + // Safety: force to 0 for codecs that should not have non-zero offset + if (m_videoFormat.codec != VK_VIDEO_CODEC_OPERATION_DECODE_VP9_BIT_KHR) { + if (srcOffset != 0) { + fprintf(stderr, "WARNING: bitstreamDataOffset=%zu is non-zero for non-VP9 codec, forcing to 0\n", + (size_t)srcOffset); + srcOffset = 0; + } + } + + // srcBufferRange alignment to minBitstreamBufferSizeAlignment. + // The bytes beyond bitstreamDataLen contain the next frame's residual data + // (swapBitstreamBuffer copies it after decode returns), so we cannot zero-fill. + // + // H.264: NVDEC's NAL scanner uses srcBufferRange to bound its start-code scan. + // Rounding up exposes the next frame's start codes in the residual area, + // causing decode corruption. Pass exact bitstreamDataLen for H.264. + // H.265/AV1: Use slice segment offsets / tile sizes exclusively, so rounding + // up is safe -- the residual data is ignored by the HW decoder. + // VP9: Already aligned by the parser (VulkanVP9Decoder.cpp:259). + VkDeviceSize srcRange = pCurrFrameDecParams->bitstreamDataLen; + bool canAlignRange = (m_videoFormat.codec != VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR); + VkDeviceSize alignedRange; + if (canAlignRange) { + alignedRange = (srcRange + (sizeAlignment - 1)) & ~(sizeAlignment - 1); + if (srcOffset + alignedRange > bufferMaxSize) { + alignedRange = bufferMaxSize - srcOffset; + } + } else { + // H.264: pass exact range; suppress VUID-07139 in g_ignoredValidationMessageIds + alignedRange = srcRange; + } + + pCurrFrameDecParams->decodeFrameInfo.srcBufferOffset = srcOffset; + pCurrFrameDecParams->decodeFrameInfo.srcBufferRange = alignedRange; VkVideoBeginCodingInfoKHR decodeBeginInfo = { VK_STRUCTURE_TYPE_VIDEO_BEGIN_CODING_INFO_KHR }; decodeBeginInfo.pNext = pCurrFrameDecParams->beginCodingInfoPictureParametersExt; @@ -773,12 +847,12 @@ int VkVideoDecoder::DecodePictureWithParameters(VkParserPerFrameDecodeParameters const VkBufferMemoryBarrier2KHR bitstreamBufferMemoryBarrier = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR, nullptr, - VK_PIPELINE_STAGE_2_NONE_KHR, + VK_PIPELINE_STAGE_2_HOST_BIT, // srcStageMask: HOST_WRITE requires HOST stage (VUID-03917) VK_ACCESS_2_HOST_WRITE_BIT_KHR, VK_PIPELINE_STAGE_2_VIDEO_DECODE_BIT_KHR, VK_ACCESS_2_VIDEO_DECODE_READ_BIT_KHR, - VK_QUEUE_FAMILY_IGNORED, - (uint32_t)m_vkDevCtx->GetVideoDecodeQueueFamilyIdx(), + VK_QUEUE_FAMILY_IGNORED, // srcQueueFamilyIndex - no ownership transfer + VK_QUEUE_FAMILY_IGNORED, // dstQueueFamilyIndex - no ownership transfer pCurrFrameDecParams->decodeFrameInfo.srcBuffer, pCurrFrameDecParams->decodeFrameInfo.srcBufferOffset, pCurrFrameDecParams->decodeFrameInfo.srcBufferRange @@ -796,8 +870,8 @@ int VkVideoDecoder::DecodePictureWithParameters(VkParserPerFrameDecodeParameters VK_ACCESS_2_VIDEO_DECODE_READ_BIT_KHR, // VkAccessFlags dstAccessMask VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout oldLayout VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR, // VkImageLayout newLayout - VK_QUEUE_FAMILY_IGNORED, // uint32_t srcQueueFamilyIndex - (uint32_t)m_vkDevCtx->GetVideoDecodeQueueFamilyIdx(), // uint32_t dstQueueFamilyIndex + VK_QUEUE_FAMILY_IGNORED, // uint32_t srcQueueFamilyIndex - no ownership transfer + VK_QUEUE_FAMILY_IGNORED, // uint32_t dstQueueFamilyIndex - no ownership transfer VkImage(), // VkImage image; { // VkImageSubresourceRange subresourceRange diff --git a/vk_video_decoder/libs/VkVideoParser/VulkanVideoParser.cpp b/vk_video_decoder/libs/VkVideoParser/VulkanVideoParser.cpp index d7a3794c..27fc87c3 100644 --- a/vk_video_decoder/libs/VkVideoParser/VulkanVideoParser.cpp +++ b/vk_video_decoder/libs/VkVideoParser/VulkanVideoParser.cpp @@ -133,6 +133,7 @@ struct nvVideoH265PicParameters { StdVideoDecodeH265PictureInfo stdPictureInfo; VkVideoDecodeH265PictureInfoKHR pictureInfo; VkVideoDecodeH265SessionParametersAddInfoKHR pictureParameters; + nvVideoDecodeH265DpbSlotInfo currentDpbSlotInfo; nvVideoDecodeH265DpbSlotInfo dpbRefList[MAX_REF_PICTURES_LIST_ENTRIES]; }; @@ -146,7 +147,10 @@ struct nvVideoDecodeAV1DpbSlotInfo StdVideoDecodeAV1ReferenceInfo stdReferenceInfo; const VkVideoDecodeAV1DpbSlotInfoKHR* Init(int8_t slotIndex) { - assert((slotIndex >= 0) && (slotIndex < (int8_t)TOTAL_REFS_PER_FRAME)); + // slotIndex must be valid: >= 0 and within MAX_DPB_REF_AND_SETUP_SLOTS. + // Note: the setup slot can have any DPB slot index (not bounded by + // TOTAL_REFS_PER_FRAME which is the dpbRefList array size). + assert((slotIndex >= 0) && (slotIndex < (int8_t)MAX_DPB_REF_AND_SETUP_SLOTS)); dpbSlotInfo.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_AV1_DPB_SLOT_INFO_KHR; dpbSlotInfo.pNext = nullptr; dpbSlotInfo.pStdReferenceInfo = &stdReferenceInfo; @@ -170,6 +174,7 @@ struct nvVideoAV1PicParameters { StdVideoDecodeAV1PictureInfo stdPictureInfo; // memory for the pointer in pictureInfo VkVideoDecodeAV1PictureInfoKHR pictureInfo; VkVideoDecodeAV1SessionParametersCreateInfoKHR pictureParameters; + nvVideoDecodeAV1DpbSlotInfo currentDpbSlotInfo; nvVideoDecodeAV1DpbSlotInfo dpbRefList[nvVideoDecodeAV1DpbSlotInfo::TOTAL_REFS_PER_FRAME + 1]; }; @@ -2307,6 +2312,18 @@ bool VulkanVideoParser::DecodePicture( } assert(!pd->ref_pic_flag || (setupReferenceSlot.slotIndex >= 0)); if (setupReferenceSlot.slotIndex >= 0) { + // Wire pNext to VkVideoDecodeH264DpbSlotInfoKHR for the setup slot (VUID-07156) + setupReferenceSlot.pNext = h264.currentDpbSlotInfo.Init(setupReferenceSlot.slotIndex); + StdVideoDecodeH264ReferenceInfo* pSetupRefInfo = &h264.currentDpbSlotInfo.stdReferenceInfo; + pSetupRefInfo->FrameNum = pStdPictureInfo->frame_num; + pSetupRefInfo->PicOrderCnt[0] = pStdPictureInfo->PicOrderCnt[0]; + pSetupRefInfo->PicOrderCnt[1] = pStdPictureInfo->PicOrderCnt[1]; + pSetupRefInfo->flags = StdVideoDecodeH264ReferenceInfoFlags(); + pSetupRefInfo->flags.top_field_flag = pd->field_pic_flag ? !pd->bottom_field_flag : 0; + pSetupRefInfo->flags.bottom_field_flag = pd->field_pic_flag ? pd->bottom_field_flag : 0; + pSetupRefInfo->flags.used_for_long_term_reference = 0; + pSetupRefInfo->flags.is_non_existing = 0; + setupReferenceSlot.pPictureResource = &pCurrFrameDecParams->dpbSetupPictureResource; pCurrFrameDecParams->decodeFrameInfo.pSetupReferenceSlot = &setupReferenceSlot; @@ -2422,6 +2439,14 @@ bool VulkanVideoParser::DecodePicture( assert(!pd->ref_pic_flag || (setupReferenceSlot.slotIndex >= 0)); if (setupReferenceSlot.slotIndex >= 0) { + // Wire pNext to VkVideoDecodeH265DpbSlotInfoKHR for the setup slot (VUID-07157) + setupReferenceSlot.pNext = hevc.currentDpbSlotInfo.Init(setupReferenceSlot.slotIndex); + StdVideoDecodeH265ReferenceInfo* pSetupRefInfo = &hevc.currentDpbSlotInfo.stdReferenceInfo; + pSetupRefInfo->PicOrderCntVal = pStdPictureInfo->PicOrderCntVal; + pSetupRefInfo->flags = StdVideoDecodeH265ReferenceInfoFlags(); + pSetupRefInfo->flags.used_for_long_term_reference = pStdPictureInfo->flags.IdrPicFlag ? 0 : 0; + pSetupRefInfo->flags.unused_for_reference = 0; + setupReferenceSlot.pPictureResource = &pCurrFrameDecParams->dpbSetupPictureResource; pCurrFrameDecParams->decodeFrameInfo.pSetupReferenceSlot = &setupReferenceSlot; @@ -2500,6 +2525,13 @@ bool VulkanVideoParser::DecodePicture( assert(!pd->ref_pic_flag || (setupReferenceSlot.slotIndex >= 0)); if (setupReferenceSlot.slotIndex >= 0) { + // Wire pNext to VkVideoDecodeAV1DpbSlotInfoKHR for the setup slot (VUID-07170) + setupReferenceSlot.pNext = av1.currentDpbSlotInfo.Init(setupReferenceSlot.slotIndex); + StdVideoDecodeAV1ReferenceInfo* pSetupRefInfo = &av1.currentDpbSlotInfo.stdReferenceInfo; + memset(pSetupRefInfo, 0, sizeof(*pSetupRefInfo)); + pSetupRefInfo->frame_type = pin->std_info.frame_type; + pSetupRefInfo->OrderHint = pin->std_info.OrderHint; + setupReferenceSlot.pPictureResource = &pCurrFrameDecParams->dpbSetupPictureResource; pCurrFrameDecParams->decodeFrameInfo.pSetupReferenceSlot = &setupReferenceSlot;