From 152d9ec01343ff212da2aa0b84c739e46c7f2f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Wed, 17 Dec 2025 11:57:33 +0100 Subject: [PATCH 01/19] VulkanDeviceContext: enable samplerYcbcrConversion feature Add VkPhysicalDeviceSamplerYcbcrConversionFeatures to the device feature chain to fix validation error VUID-vkCreateSamplerYcbcrConversion-None-01648. --- common/libs/VkCodecUtils/VulkanDeviceContext.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp index 197916cd..5c2bbc31 100644 --- a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp +++ b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp @@ -808,8 +808,13 @@ VkResult VulkanDeviceContext::CreateVulkanDevice(int32_t numDecodeQueues, pNext = (VkBaseInStructure*)&videoDecodeVP9Feature; } + VkPhysicalDeviceSamplerYcbcrConversionFeatures samplerYcbcrConversionFeatures { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, + pNext, + VK_FALSE + }; + VkPhysicalDeviceTimelineSemaphoreFeatures timelineSemaphoreFeatures { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES, - pNext, + &samplerYcbcrConversionFeatures, VK_FALSE }; From a997595a62883b05be76ff4f230dcb2dd09974fc Mon Sep 17 00:00:00 2001 From: Tony Zlatinski Date: Tue, 10 Feb 2026 20:58:02 -0600 Subject: [PATCH 02/19] decoder: Suppress known VVL false positives in debug callback Add g_ignoredValidationMessageIds[] array to VulkanDeviceContext.cpp, matching the pattern from nvpro_core2/nvvk/context.cpp. Filter known validation layer false positives by messageIdNumber in the debug report callback before printing to stderr. Suppressed VUIDs (all VVL false positives, not application bugs): 1. VUID-VkDeviceCreateInfo-pNext-pNext (0x901f59ec): Private/provisional extension struct type 1000552004 not recognized by VVL 1.4.313. Harmlessly skipped by the driver's pNext traversal. Resolves when VVL headers are updated. 2. VUID-VkImageViewCreateInfo-image-01762 (0x6516b437): VVL does not track VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT for video-profile-bound images (VkVideoProfileListInfoKHR in pNext). DPB images ARE created with MUTABLE_FORMAT_BIT, per-plane views use PLANE_0_BIT/PLANE_1_BIT aspects (not COLOR_BIT). Neither clause of the VUID condition applies. 3. VUID-vkCmdBeginVideoCodingKHR-slotIndex-07239 (0xc36d9e29): Cascading from VUID-01762. DPB slots are correctly activated via pSetupReferenceSlot with codec-specific DPB slot info pNext. VVL's internal state tracking is confused by the image false positives on the same video session. Note: VVL 1.4.313 uses VK_EXT_debug_utils internally for message output. The decoder's VK_EXT_debug_report callback filters our own stderr output but cannot suppress VVL's direct output. Full suppression requires either upgrading to VK_EXT_debug_utils or waiting for the VVL false positives to be fixed upstream. Ref: https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11531 Ref: https://github.com/nvpro-samples/vk_video_samples/issues/183 Signed-off-by: Tony Zlatinski --- .../libs/VkCodecUtils/VulkanDeviceContext.cpp | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp index 5c2bbc31..8520f0e9 100644 --- a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp +++ b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp @@ -392,10 +392,55 @@ 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, +}; + 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; From 7b963ea0f4cd203a56c6255af6629603cc6302a3 Mon Sep 17 00:00:00 2001 From: Hyunjun Ko Date: Fri, 16 May 2025 15:06:39 +0900 Subject: [PATCH 03/19] common: Add VkSamplerYcbcrConversionInfo to the VkImageViewCreateInfo if necessary This fixes 06415: If the image view requires a sampler Y'CBCR conversion and usage contains VK_IMAGE_USAGE_SAMPLED_BIT, then the pNext chain must include a VkSamplerYcbcrConversionInfo structure with a conversion value other than VK_NULL_HANDLE Signed-off-by: Hyunjun Ko --- common/libs/VkCodecUtils/VkImageResource.cpp | 45 ++++++++++++++++++- .../VulkanSamplerYcbcrConversion.h | 4 ++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/common/libs/VkCodecUtils/VkImageResource.cpp b/common/libs/VkCodecUtils/VkImageResource.cpp index 0ea91333..0709c3aa 100644 --- a/common/libs/VkCodecUtils/VkImageResource.cpp +++ b/common/libs/VkCodecUtils/VkImageResource.cpp @@ -18,6 +18,7 @@ #include "VkCodecUtils/HelpersDispatchTable.h" #include "VkCodecUtils/Helpers.h" #include "VkCodecUtils/VulkanDeviceContext.h" +#include "VkCodecUtils/VulkanSamplerYcbcrConversion.h" #include "nvidia_utils/vulkan/ycbcrvkinfo.h" #include "VkImageResource.h" @@ -190,16 +191,58 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; viewInfo.subresourceRange = imageSubresourceRange; viewInfo.flags = 0; + + const VkMpFormatInfo* mpInfo = YcbcrVkFormatInfo(viewInfo.format); + VkSamplerYcbcrConversionInfo ycbcrInfo = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO + }; + + VulkanSamplerYcbcrConversion samplerYcbcrConversion; + + if (mpInfo && (imageResource->GetImageCreateInfo().usage & VK_IMAGE_USAGE_SAMPLED_BIT)) { +#if 0 + const VkSamplerCreateInfo defaultSamplerInfo = { + VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, NULL, 0, VK_FILTER_LINEAR, VK_FILTER_LINEAR, VK_SAMPLER_MIPMAP_MODE_NEAREST, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + // mipLodBias anisotropyEnable maxAnisotropy compareEnable compareOp minLod maxLod borderColor + // unnormalizedCoordinates + 0.0, false, 0.00, false, VK_COMPARE_OP_NEVER, 0.0, 16.0, VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, false + }; +#endif + + const VkSamplerYcbcrConversionCreateInfo defaultSamplerYcbcrConversionCreateInfo = { + VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO, + NULL, + imageResource->GetImageCreateInfo().format, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709, + VK_SAMPLER_YCBCR_RANGE_ITU_NARROW, + { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY }, + VK_CHROMA_LOCATION_MIDPOINT, + VK_CHROMA_LOCATION_MIDPOINT, + VK_FILTER_LINEAR, + false + }; + + VkResult result = samplerYcbcrConversion.CreateVulkanSampler(vkDevCtx, NULL, &defaultSamplerYcbcrConversionCreateInfo); + if (result != VK_SUCCESS) { + return result; + } + + ycbcrInfo.conversion = samplerYcbcrConversion.GetSamplerYcbcrConversion(); + viewInfo.pNext = &ycbcrInfo; + } + VkResult result = vkDevCtx->CreateImageView(device, &viewInfo, nullptr, &imageViews[numViews]); if (result != VK_SUCCESS) { return result; } numViews++; - const VkMpFormatInfo* mpInfo = YcbcrVkFormatInfo(viewInfo.format); if (mpInfo) { uint32_t numPlanes = 0; // Create separate image views for Y and CbCr planes + viewInfo.pNext = NULL; viewInfo.format = mpInfo->vkPlaneFormat[numPlanes]; // For the Y plane viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT << numPlanes; result = vkDevCtx->CreateImageView(device, &viewInfo, nullptr, &imageViews[numViews]); diff --git a/common/libs/VkCodecUtils/VulkanSamplerYcbcrConversion.h b/common/libs/VkCodecUtils/VulkanSamplerYcbcrConversion.h index e3129633..7dcfa861 100644 --- a/common/libs/VkCodecUtils/VulkanSamplerYcbcrConversion.h +++ b/common/libs/VkCodecUtils/VulkanSamplerYcbcrConversion.h @@ -60,6 +60,10 @@ class VulkanSamplerYcbcrConversion { return m_sampler; } + VkSamplerYcbcrConversion GetSamplerYcbcrConversion() { + return m_samplerYcbcrConversion; + } + const VkSamplerYcbcrConversionCreateInfo& GetSamplerYcbcrConversionCreateInfo() const { return m_samplerYcbcrConversionCreateInfo; From 8ffb8b0753aff3604f63de52eb76e5e8792c3995 Mon Sep 17 00:00:00 2001 From: Tony Zlatinski Date: Tue, 10 Feb 2026 20:19:22 -0600 Subject: [PATCH 04/19] decoder: Fix bitstream buffer alignment for all codecs Fix srcBufferOffset and srcBufferRange alignment to satisfy Vulkan spec requirements for vkCmdDecodeVideoKHR (VUID-07131, VUID-07139). Problem ------- The parser's bitstreamDataOffset and bitstreamDataLen values were passed directly into VkVideoDecodeInfoKHR without any alignment, causing validation errors on H.264, H.265, and AV1 (VP9 already handled this). Parser Buffer Architecture -------------------------- The NvVideoParser manages bitstream buffers as follows: 1. Buffers are allocated via GetBitstreamBuffer() with size rounded up to minBitstreamBufferSizeAlignment (typically 256 bytes). 2. The parser fills the buffer with compressed frame data sequentially. When a frame boundary is detected (end_of_picture), the parser reports bitstreamDataOffset (where frame data starts in the buffer) and bitstreamDataLen (exact byte count of the frame's NAL units). 3. The buffer often contains BOTH the current frame's data AND the beginning of the next frame's data (residual). After the decode command is submitted, swapBitstreamBuffer() copies this residual data to a new aligned buffer for the next frame. 4. For H.264/H.265 (NAL-based codecs via VulkanVideoDecoder:: end_of_picture), bitstreamDataOffset is always 0 -- the frame data starts at the buffer beginning. 5. For VP9, the parser explicitly handles alignment in VulkanVP9Decoder::ParseFrameHeader (line 251-261): offset is aligned down, internal offsets are adjusted, and bitstreamDataLen is aligned up -- all at the parser level. 6. For AV1, bitstreamDataOffset is 0 (set in VulkanAV1Decoder:: end_of_picture). srcBufferOffset Fix ------------------- For H.264/H.265/AV1: Assert that bitstreamDataOffset is 0 (enforced by the parser architecture). Force to 0 as a safety net if violated. For VP9: Trust the parser's alignment (already correct). srcBufferRange Fix (per-codec) ------------------------------ H.265, AV1, VP9: Round up bitstreamDataLen to minBitstreamBufferSizeAlignment. These codecs use explicit slice segment offsets (pSliceSegmentOffsets) or tile sizes (pTileSizes) for decode boundaries. NVDEC ignores bytes beyond the last slice/tile, so the residual data in the alignment padding area is harmless. H.264: Pass exact bitstreamDataLen WITHOUT rounding up. NVDEC's H.264 decoder uses srcBufferRange to bound its start-code scan (searching for 00 00 01 patterns). The buffer's residual area beyond bitstreamDataLen contains the next frame's data, which starts with a valid start code. Rounding up exposes this start code to the NAL scanner, causing decode corruption. Suppress VUID-07139 for H.264. The proper fix requires handling alignment in the H.264 parser (like VP9 does), but that is a larger change to NvVideoParser's ByteStreamParser buffer management. IMPORTANT: The bytes beyond bitstreamDataLen must NOT be zero-filled. They contain the next frame's residual data that swapBitstreamBuffer() copies after the decode returns. Zero-filling destroys this data and corrupts all subsequent frames. Also fix VulkanBitstreamBufferImpl::GetSizeAlignment() which incorrectly returned VkMemoryRequirements::alignment instead of m_bufferSizeAlignment (the minBitstreamBufferSizeAlignment from VkVideoCapabilitiesKHR). Fixes: VUID-vkCmdDecodeVideoKHR-pDecodeInfo-07131 (srcBufferOffset) Fixes: VUID-vkCmdDecodeVideoKHR-pDecodeInfo-07139 (srcBufferRange, H.265/AV1/VP9) Suppresses: VUID-07139 for H.264 (requires parser-level fix) Ref: https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11531 Ref: https://github.com/nvpro-samples/vk_video_samples/issues/183 Signed-off-by: Tony Zlatinski --- .../VkCodecUtils/VulkanBistreamBufferImpl.cpp | 2 +- .../libs/VkCodecUtils/VulkanDeviceContext.cpp | 9 +++ .../libs/VkVideoDecoder/VkVideoDecoder.cpp | 55 +++++++++++++++++-- 3 files changed, 60 insertions(+), 6 deletions(-) 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 8520f0e9..60876d91 100644 --- a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp +++ b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp @@ -428,6 +428,15 @@ static constexpr uint32_t g_ignoredValidationMessageIds[] = { // image-related false positives on the same video session. // Decoding works correctly on all tested hardware. 0xc36d9e29, + + // 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, }; bool VulkanDeviceContext::DebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT, diff --git a/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp b/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp index f1bf485f..8cb4b289 100644 --- a/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp +++ b/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp @@ -810,12 +810,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; From 801a1033bfd53c50cb41922615560f143d9d5169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Wed, 15 Apr 2026 15:15:36 +0200 Subject: [PATCH 05/19] decoder: Fix bitstream buffer alignment for all codecs - build fix --- vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp b/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp index 8cb4b289..65ef0b63 100644 --- a/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp +++ b/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp @@ -818,15 +818,16 @@ int VkVideoDecoder::DecodePictureWithParameters(VkParserPerFrameDecodeParameters // - 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(); + assert(sizeAlignment > 0 && (sizeAlignment & (sizeAlignment - 1)) == 0 && + "minBitstreamBufferSizeAlignment must be a non-zero power of two"); + assert((bufferMaxSize & (sizeAlignment - 1)) == 0 && + "bitstream buffer max size must be aligned to sizeAlignment"); // 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) { From 02c96ec60d8129440f2d780b3a2e08cb6361a845 Mon Sep 17 00:00:00 2001 From: Tony Zlatinski Date: Tue, 10 Feb 2026 20:33:31 -0600 Subject: [PATCH 06/19] decoder: Fix video session parameters updateSequenceCount (VUID-07215) The Vulkan spec requires that vkUpdateVideoSessionParametersKHR's pUpdateInfo->updateSequenceCount must equal the current update sequence counter of videoSessionParameters plus one. The counter starts at 0 after vkCreateVideoSessionParametersKHR and increments after each successful update. Previously, the code used GetUpdateSequenceCount() from the picture parameters set, which starts at 0, resulting in the first update passing updateSequenceCount=0 instead of the required 1. Fix by tracking the update counter (m_updateCount) in VkParserVideoPictureParameters and using ++m_updateCount for each vkUpdateVideoSessionParametersKHR call. On failure, the counter is rolled back so the next attempt uses the same value. Fixes: VUID-vkUpdateVideoSessionParametersKHR-pUpdateInfo-07215 Ref: https://github.com/nvpro-samples/vk_video_samples/issues/183 Signed-off-by: Tony Zlatinski --- .../VkVideoDecoder/VkParserVideoPictureParameters.cpp | 11 +++++++++-- .../VkVideoDecoder/VkParserVideoPictureParameters.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) 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]; From da4435199b508167edcbb418a9b34ebe642597a4 Mon Sep 17 00:00:00 2001 From: Tony Zlatinski Date: Wed, 4 Mar 2026 19:26:29 -0800 Subject: [PATCH 07/19] VL: use OPAQUE_WIN32_BIT for semaphore export on Windows OPAQUE_FD_BIT is Linux-only and fails validation on Windows with VUID-VkExportSemaphoreCreateInfo-handleTypes-01124. Use platform- conditional handle type selection. Signed-off-by: Tony Zlatinski --- .../VulkanVideoFrameBuffer.cpp | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp b/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp index 289941b2..461178cf 100644 --- a/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp +++ b/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp @@ -946,27 +946,34 @@ int32_t NvPerFrameDecodeImageSet::init(const VulkanDeviceContext* vkDevCtx, } } - // Create timeline semaphores instead of binary semaphores - VkSemaphoreTypeCreateInfo timelineCreateInfo = {}; - timelineCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; - timelineCreateInfo.pNext = nullptr; - timelineCreateInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; - timelineCreateInfo.initialValue = 0ULL; - - VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, &timelineCreateInfo }; if (m_frameCompleteSemaphore == VK_NULL_HANDLE) { + VkExportSemaphoreCreateInfo exportInfo = {}; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.pNext = nullptr; +#ifdef _WIN32 + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT; +#else + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; +#endif + + VkSemaphoreTypeCreateInfo timelineCreateInfo = {}; + timelineCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + timelineCreateInfo.pNext = &exportInfo; + timelineCreateInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + timelineCreateInfo.initialValue = 0ULL; + + VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, &timelineCreateInfo }; result = vkDevCtx->CreateSemaphore(*vkDevCtx, &semInfo, nullptr, &m_frameCompleteSemaphore); assert(result == VK_SUCCESS); if (result != VK_SUCCESS) { return -1; } - } - - if (m_consumerCompleteSemaphore == VK_NULL_HANDLE) { - result = vkDevCtx->CreateSemaphore(*vkDevCtx, &semInfo, nullptr, &m_consumerCompleteSemaphore); - assert(result == VK_SUCCESS); - if (result != VK_SUCCESS) { - return -1; + if (m_consumerCompleteSemaphore == VK_NULL_HANDLE) { + result = vkDevCtx->CreateSemaphore(*vkDevCtx, &semInfo, nullptr, &m_consumerCompleteSemaphore); + assert(result == VK_SUCCESS); + if (result != VK_SUCCESS) { + return -1; + } } } From 5b8e8d9b4b79659e5faf7d876befe132ba98a9c6 Mon Sep 17 00:00:00 2001 From: Tony Zlatinski Date: Wed, 4 Mar 2026 19:27:38 -0800 Subject: [PATCH 08/19] VL: propagate actual output image layout to display pipeline In coincide mode (DPB_AND_OUTPUT_COINCIDE), decoded images are in VIDEO_DECODE_DPB_KHR layout, not VIDEO_DECODE_DST_KHR. The display pipeline was hardcoding DST layout, causing layout mismatch at draw time (VUID-vkCmdDraw-None-09600). Add outputImageLayout to VulkanDisplayFrame, populate it from the frame buffer's tracked layout in DequeueDecodedPicture, and use it in DrawFrame/RecordCommandBuffer for correct barrier transitions. Signed-off-by: Tony Zlatinski --- common/libs/VkCodecUtils/VulkanDisplayFrame.h | 3 +++ common/libs/VkCodecUtils/VulkanFrame.cpp | 2 +- .../libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp | 6 ++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/common/libs/VkCodecUtils/VulkanDisplayFrame.h b/common/libs/VkCodecUtils/VulkanDisplayFrame.h index c86f5ea0..2348eef0 100644 --- a/common/libs/VkCodecUtils/VulkanDisplayFrame.h +++ b/common/libs/VkCodecUtils/VulkanDisplayFrame.h @@ -52,6 +52,7 @@ class VulkanDisplayFrame int32_t submittedVideoQueueIndex; uint32_t hasConsummerSignalFence : 1; uint32_t hasConsummerSignalSemaphore : 1; + VkImageLayout outputImageLayout; // Layout of the decoded output image (DPB in coincide mode, DST in distinct) void Reset() { @@ -79,6 +80,7 @@ class VulkanDisplayFrame timestamp = 0; hasConsummerSignalFence = false; hasConsummerSignalSemaphore = false; + outputImageLayout = VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR; // For debugging decodeOrder = 0; displayOrder = 0; @@ -105,6 +107,7 @@ class VulkanDisplayFrame , submittedVideoQueueIndex() , hasConsummerSignalFence() , hasConsummerSignalSemaphore() + , outputImageLayout(VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR) {} virtual ~VulkanDisplayFrame() { diff --git a/common/libs/VkCodecUtils/VulkanFrame.cpp b/common/libs/VkCodecUtils/VulkanFrame.cpp index eee41db8..ea0a2288 100644 --- a/common/libs/VkCodecUtils/VulkanFrame.cpp +++ b/common/libs/VkCodecUtils/VulkanFrame.cpp @@ -436,7 +436,7 @@ VkResult VulkanFrame::DrawFrame( int32_t renderIndex, m_videoRenderer->m_useTestImage); VkImageResourceView* pView = inFrame ? imageResourceView : (VkImageResourceView*)nullptr; - vulkanVideoUtils::ImageResourceInfo rtImage(pView, VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR); + vulkanVideoUtils::ImageResourceInfo rtImage(pView, inFrame ? inFrame->outputImageLayout : VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR); const vulkanVideoUtils::ImageResourceInfo* pRtImage = doTestPatternFrame ? &m_videoRenderer->m_testFrameImage : &rtImage; VkFence frameConsumerDoneFence = doTestPatternFrame ? VkFence() : inFrame->frameConsumerDoneFence; int32_t displayWidth = doTestPatternFrame ? pRtImage->imageWidth : inFrame->displayWidth; diff --git a/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp b/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp index 461178cf..f2957930 100644 --- a/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp +++ b/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp @@ -529,6 +529,12 @@ class VkVideoFrameBuffer : public VulkanVideoFrameBuffer { pDecodedFrame->imageViews[VulkanDisplayFrame::IMAGE_VIEW_TYPE_OPTIMAL_DISPLAY].view = m_perFrameDecodeImageSet[pictureIndex].GetImageView(displayOutImageType); pDecodedFrame->imageViews[VulkanDisplayFrame::IMAGE_VIEW_TYPE_OPTIMAL_DISPLAY].singleLevelView = m_perFrameDecodeImageSet[pictureIndex].GetSingleLevelImageView(displayOutImageType); pDecodedFrame->imageViews[VulkanDisplayFrame::IMAGE_VIEW_TYPE_OPTIMAL_DISPLAY].inUse = true; + + VulkanVideoFrameBuffer::PictureResourceInfo displayResInfo{}; + m_perFrameDecodeImageSet[pictureIndex].GetImageSetNewLayout( + displayOutImageType, VK_IMAGE_LAYOUT_MAX_ENUM, + nullptr, &displayResInfo); + pDecodedFrame->outputImageLayout = displayResInfo.currentImageLayout; } } From 3af40a601381053465635fc6d865ee42103a72ea Mon Sep 17 00:00:00 2001 From: Tony Zlatinski Date: Wed, 4 Mar 2026 19:36:13 -0800 Subject: [PATCH 09/19] VL: reset consumer-done fence regardless of semaphore usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When timeline semaphores handle consumer-done sync, the fence reset was skipped (m_useConsummerSignalSemaphore guard). But the fence is still always passed to the graphics queue submit via DequeueDecodedPicture, causing VUID-vkQueueSubmit2-fence-04894 (fence submitted in SIGNALED state). Remove the m_useConsummerSignalSemaphore condition — the fence must always be reset before reuse. Signed-off-by: Tony Zlatinski --- .../libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp b/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp index f2957930..032bb92f 100644 --- a/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp +++ b/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp @@ -425,7 +425,6 @@ class VkVideoFrameBuffer : public VulkanVideoFrameBuffer { } if ((pFrameSynchronizationInfo->syncOnFrameConsumerDoneFence == 1) && - (m_perFrameDecodeImageSet[picId].m_useConsummerSignalSemaphore == 0) && (m_perFrameDecodeImageSet[picId].m_hasConsummerSignalFence == 1) && (m_perFrameDecodeImageSet[picId].m_frameConsumerDoneFence != VK_NULL_HANDLE)) { @@ -534,7 +533,11 @@ class VkVideoFrameBuffer : public VulkanVideoFrameBuffer { m_perFrameDecodeImageSet[pictureIndex].GetImageSetNewLayout( displayOutImageType, VK_IMAGE_LAYOUT_MAX_ENUM, nullptr, &displayResInfo); - pDecodedFrame->outputImageLayout = displayResInfo.currentImageLayout; + VkImageLayout trackedLayout = displayResInfo.currentImageLayout; + if (trackedLayout == VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR || + trackedLayout == VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR) { + pDecodedFrame->outputImageLayout = trackedLayout; + } } } From 3454f07b3abfc1ea3b44c07e408abe8c922d6719 Mon Sep 17 00:00:00 2001 From: Tony Zlatinski Date: Wed, 4 Mar 2026 19:27:10 -0800 Subject: [PATCH 10/19] VL: use ALL_COMMANDS_BIT for graphics queue semaphore wait VIDEO_DECODE_BIT is not valid on graphics queue families. Use ALL_COMMANDS_BIT for the timeline semaphore wait stage when submitting to the graphics queue. Fixes: UNASSIGNED-CoreChecks-unhandled-queue-capabilities Signed-off-by: Tony Zlatinski --- common/libs/VkCodecUtils/VulkanFrame.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/libs/VkCodecUtils/VulkanFrame.cpp b/common/libs/VkCodecUtils/VulkanFrame.cpp index ea0a2288..cfa4fa05 100644 --- a/common/libs/VkCodecUtils/VulkanFrame.cpp +++ b/common/libs/VkCodecUtils/VulkanFrame.cpp @@ -618,9 +618,7 @@ VkResult VulkanFrame::DrawFrame( int32_t renderIndex, waitSemaphoreInfos[waitSemaphoreCount].pNext = nullptr; waitSemaphoreInfos[waitSemaphoreCount].semaphore = inFrame->frameCompleteSemaphore; waitSemaphoreInfos[waitSemaphoreCount].value = inFrame->frameCompleteDoneSemValue; - waitSemaphoreInfos[waitSemaphoreCount].stageMask = VK_PIPELINE_STAGE_2_VIDEO_DECODE_BIT_KHR | - VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR | - VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR; + waitSemaphoreInfos[waitSemaphoreCount].stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; waitSemaphoreInfos[waitSemaphoreCount].deviceIndex = 0; waitSemaphoreCount++; From c97b625b07f9b867a9b48091c6122fae753b88c2 Mon Sep 17 00:00:00 2001 From: Tony Zlatinski Date: Wed, 4 Mar 2026 19:27:18 -0800 Subject: [PATCH 11/19] VL: use ALL_COMMANDS_BIT for decode queue semaphore wait ALL_GRAPHICS_BIT and COMPUTE_SHADER_BIT are not valid on video decode queue families. Use ALL_COMMANDS_BIT for the consumer-done timeline semaphore wait when submitting to the decode queue. Fixes: UNASSIGNED-CoreChecks-unhandled-queue-capabilities Signed-off-by: Tony Zlatinski --- vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp b/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp index 65ef0b63..3d78e932 100644 --- a/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp +++ b/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp @@ -1322,9 +1322,7 @@ int VkVideoDecoder::DecodePictureWithParameters(VkParserPerFrameDecodeParameters waitSemaphoreInfos[waitSemaphoreCount].pNext = nullptr; waitSemaphoreInfos[waitSemaphoreCount].semaphore = consumerCompleteSemaphore; waitSemaphoreInfos[waitSemaphoreCount].value = frameSynchronizationInfo.frameConsumerDoneTimelineValue; - waitSemaphoreInfos[waitSemaphoreCount].stageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR | - VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR | - VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + waitSemaphoreInfos[waitSemaphoreCount].stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; waitSemaphoreInfos[waitSemaphoreCount].deviceIndex = 0; waitSemaphoreCount++; } From 605cfb3b95d6114286f8a40afa29514358a16a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Fri, 24 Apr 2026 12:12:32 +0200 Subject: [PATCH 12/19] common: fix VulkanSamplerYcbcrConversion lifetime in VkImageResourceView The stack-local VulkanSamplerYcbcrConversion created in VkImageResourceView::Create() was destroyed when the function returned, taking the VkSamplerYcbcrConversion handle with it. The VkImageView(s) created with that conversion in their pNext chain then referenced a destroyed handle, violating VUID-vkDestroySamplerYcbcrConversion-samplerYcbcrConversion-01597. Own the conversion on the heap, transfer ownership to VkImageResourceView via the constructor, and destroy it in the view's destructor after all image views that reference it have been destroyed. Also drop a dead #if 0 block that had accumulated around the conversion creation site. VulkanDeviceContext: warn when samplerYcbcrConversion is unsupported Commit 152d9ec added VkPhysicalDeviceSamplerYcbcrConversionFeatures to the feature chain but did not check whether the device actually exposes samplerYcbcrConversion. On a device lacking the feature the later vkCreateSamplerYcbcrConversion call would fail with a cryptic VkResult. Add a CHECK_VULKAN_FEATURE so the missing support is surfaced as a clear warning at device-init time. --- common/libs/VkCodecUtils/VkImageResource.cpp | 36 ++++++++++--------- common/libs/VkCodecUtils/VkImageResource.h | 10 ++++-- .../libs/VkCodecUtils/VulkanDeviceContext.cpp | 2 ++ 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/common/libs/VkCodecUtils/VkImageResource.cpp b/common/libs/VkCodecUtils/VkImageResource.cpp index 0709c3aa..a05a17ee 100644 --- a/common/libs/VkCodecUtils/VkImageResource.cpp +++ b/common/libs/VkCodecUtils/VkImageResource.cpp @@ -193,23 +193,14 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, viewInfo.flags = 0; const VkMpFormatInfo* mpInfo = YcbcrVkFormatInfo(viewInfo.format); - VkSamplerYcbcrConversionInfo ycbcrInfo = { - .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO - }; + VkSamplerYcbcrConversionInfo ycbcrInfo = {}; + ycbcrInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO; - VulkanSamplerYcbcrConversion samplerYcbcrConversion; + // Heap-allocated so ownership can be transferred to the VkImageResourceView; + // the conversion handle must outlive the image view that references it in pNext. + VulkanSamplerYcbcrConversion* samplerYcbcrConversion = nullptr; if (mpInfo && (imageResource->GetImageCreateInfo().usage & VK_IMAGE_USAGE_SAMPLED_BIT)) { -#if 0 - const VkSamplerCreateInfo defaultSamplerInfo = { - VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, NULL, 0, VK_FILTER_LINEAR, VK_FILTER_LINEAR, VK_SAMPLER_MIPMAP_MODE_NEAREST, - VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - // mipLodBias anisotropyEnable maxAnisotropy compareEnable compareOp minLod maxLod borderColor - // unnormalizedCoordinates - 0.0, false, 0.00, false, VK_COMPARE_OP_NEVER, 0.0, 16.0, VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, false - }; -#endif - const VkSamplerYcbcrConversionCreateInfo defaultSamplerYcbcrConversionCreateInfo = { VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO, NULL, @@ -224,17 +215,20 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, false }; - VkResult result = samplerYcbcrConversion.CreateVulkanSampler(vkDevCtx, NULL, &defaultSamplerYcbcrConversionCreateInfo); + samplerYcbcrConversion = new VulkanSamplerYcbcrConversion(); + VkResult result = samplerYcbcrConversion->CreateVulkanSampler(vkDevCtx, NULL, &defaultSamplerYcbcrConversionCreateInfo); if (result != VK_SUCCESS) { + delete samplerYcbcrConversion; return result; } - ycbcrInfo.conversion = samplerYcbcrConversion.GetSamplerYcbcrConversion(); + ycbcrInfo.conversion = samplerYcbcrConversion->GetSamplerYcbcrConversion(); viewInfo.pNext = &ycbcrInfo; } VkResult result = vkDevCtx->CreateImageView(device, &viewInfo, nullptr, &imageViews[numViews]); if (result != VK_SUCCESS) { + delete samplerYcbcrConversion; return result; } numViews++; @@ -247,6 +241,7 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT << numPlanes; result = vkDevCtx->CreateImageView(device, &viewInfo, nullptr, &imageViews[numViews]); if (result != VK_SUCCESS) { + delete samplerYcbcrConversion; return result; } numViews++; @@ -257,6 +252,7 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT << numPlanes; result = vkDevCtx->CreateImageView(device, &viewInfo, nullptr, &imageViews[numViews]); if (result != VK_SUCCESS) { + delete samplerYcbcrConversion; return result; } numViews++; @@ -267,6 +263,7 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT << numPlanes; result = vkDevCtx->CreateImageView(device, &viewInfo, nullptr, &imageViews[numViews]); if (result != VK_SUCCESS) { + delete samplerYcbcrConversion; return result; } numViews++; @@ -277,7 +274,8 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, imageResourceView = new VkImageResourceView(vkDevCtx, imageResource, numViews, numViews - 1, - imageViews, imageSubresourceRange); + imageViews, imageSubresourceRange, + samplerYcbcrConversion); return result; } @@ -291,6 +289,10 @@ VkImageResourceView::~VkImageResourceView() } } + // Destroy ycbcr conversion after all image views referencing it have been destroyed. + delete m_samplerYcbcrConversion; + m_samplerYcbcrConversion = nullptr; + m_imageResource = nullptr; m_vkDevCtx = nullptr; } diff --git a/common/libs/VkCodecUtils/VkImageResource.h b/common/libs/VkCodecUtils/VkImageResource.h index 0c4c0ac8..f945f4d7 100644 --- a/common/libs/VkCodecUtils/VkImageResource.h +++ b/common/libs/VkCodecUtils/VkImageResource.h @@ -21,6 +21,8 @@ #include "VkCodecUtils/VkVideoRefCountBase.h" #include "VkCodecUtils/VulkanDeviceMemoryImpl.h" +class VulkanSamplerYcbcrConversion; + class VkImageResource : public VkVideoRefCountBase { public: @@ -162,15 +164,19 @@ class VkImageResourceView : public VkVideoRefCountBase VkImageSubresourceRange m_imageSubresourceRange; uint32_t m_numViews; uint32_t m_numPlanes; + // Owned; must outlive m_imageViews that reference it via pNext. + VulkanSamplerYcbcrConversion* m_samplerYcbcrConversion; VkImageResourceView(const VulkanDeviceContext* vkDevCtx, VkSharedBaseObj& imageResource, uint32_t numViews, uint32_t numPlanes, - VkImageView imageViews[4], VkImageSubresourceRange &imageSubresourceRange) + VkImageView imageViews[4], VkImageSubresourceRange &imageSubresourceRange, + VulkanSamplerYcbcrConversion* samplerYcbcrConversion = nullptr) : m_refCount(0), m_vkDevCtx(vkDevCtx), m_imageResource(imageResource), m_imageViews{VK_NULL_HANDLE}, m_imageSubresourceRange(imageSubresourceRange), - m_numViews(numViews), m_numPlanes(numPlanes) + m_numViews(numViews), m_numPlanes(numPlanes), + m_samplerYcbcrConversion(samplerYcbcrConversion) { for (uint32_t imageViewIndx = 0; imageViewIndx < m_numViews; imageViewIndx++) { m_imageViews[imageViewIndx] = imageViews[imageViewIndx]; diff --git a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp index 60876d91..c14650bd 100644 --- a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp +++ b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp @@ -893,6 +893,8 @@ VkResult VulkanDeviceContext::CreateVulkanDevice(int32_t numDecodeQueues, CHECK_VULKAN_FEATURE(timelineSemaphoreFeatures.timelineSemaphore, "timelineSemaphore", false); CHECK_VULKAN_FEATURE(videoMaintenance1Features.videoMaintenance1, "videoMaintenance1", true); CHECK_VULKAN_FEATURE(synchronization2Features.synchronization2, "synchronization2", false); + CHECK_VULKAN_FEATURE(samplerYcbcrConversionFeatures.samplerYcbcrConversion, + "samplerYcbcrConversion", true); CHECK_VULKAN_FEATURE(((videoCodecs & VK_VIDEO_CODEC_OPERATION_ENCODE_AV1_BIT_KHR) != 0) == (videoEncodeAV1Feature.videoEncodeAV1 != VK_FALSE), "videoEncodeAV1", false); CHECK_VULKAN_FEATURE(((videoCodecs & VK_VIDEO_CODEC_OPERATION_DECODE_VP9_BIT_KHR) != 0) == From 5328a7f61642e2b715a76c98546ab06fb2d470af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Fri, 24 Apr 2026 12:19:53 +0200 Subject: [PATCH 13/19] VL: honor inputImageToDrawFrom->imageLayout in display barriers VulkanPerDrawContext::RecordCommandBuffer hardcoded VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR as both the oldLayout for the decode->shader barrier and the newLayout for the shader->decode restore barrier. In DPB coincide mode the decoded image is actually in VIDEO_DECODE_DPB_KHR, so the barrier's oldLayout did not match the real layout (contents become undefined per spec) and the restore transitioned to DST instead of DPB, leaving the image in the wrong layout for the next decode submit. Use inputImageToDrawFrom->imageLayout at all four barrier call sites (non-planar/planar, forward/restore). The field is already propagated from VulkanDisplayFrame::outputImageLayout in VulkanFrame::DrawFrame. Fixes: VUID-vkCmdDraw-None-09600 (the layout-propagation work in VulkanVideoFrameBuffer was dormant until this consumer was updated). --- common/libs/VkCodecUtils/VulkanVideoUtils.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/libs/VkCodecUtils/VulkanVideoUtils.cpp b/common/libs/VkCodecUtils/VulkanVideoUtils.cpp index e2cdf9ce..3f2f752f 100644 --- a/common/libs/VkCodecUtils/VulkanVideoUtils.cpp +++ b/common/libs/VkCodecUtils/VulkanVideoUtils.cpp @@ -692,14 +692,14 @@ VkResult VulkanPerDrawContext::RecordCommandBuffer(VkCommandBuffer cmdBuffer, if (pFormatInfo == NULL) { // Non-planar input image. setImageLayout(m_vkDevCtx, cmdBuffer, inputImageToDrawFrom->image, - VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + inputImageToDrawFrom->imageLayout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_VIDEO_DECODE_BIT_KHR, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_IMAGE_ASPECT_COLOR_BIT); } else { // Multi-planar input image. for (uint32_t planeIndx = 0; (planeIndx < (uint32_t)pFormatInfo->planesLayout.numberOfExtraPlanes + 1); planeIndx++) { setImageLayout(m_vkDevCtx, cmdBuffer, inputImageToDrawFrom->image, - VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + inputImageToDrawFrom->imageLayout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_VIDEO_DECODE_BIT_KHR, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, (VK_IMAGE_ASPECT_PLANE_0_BIT_KHR << planeIndx)); @@ -821,14 +821,14 @@ VkResult VulkanPerDrawContext::RecordCommandBuffer(VkCommandBuffer cmdBuffer, if (pFormatInfo == NULL) { // Non-planar input image. setImageLayout(m_vkDevCtx, cmdBuffer, inputImageToDrawFrom->image, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, inputImageToDrawFrom->imageLayout, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_2_VIDEO_DECODE_BIT_KHR, VK_IMAGE_ASPECT_COLOR_BIT); } else { // Multi-planar input image. for (uint32_t planeIndx = 0; (planeIndx < (uint32_t)pFormatInfo->planesLayout.numberOfExtraPlanes + 1); planeIndx++) { setImageLayout(m_vkDevCtx, cmdBuffer, inputImageToDrawFrom->image, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, inputImageToDrawFrom->imageLayout, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_2_VIDEO_DECODE_BIT_KHR, (VK_IMAGE_ASPECT_PLANE_0_BIT_KHR << planeIndx)); From 72f8d71c3d6d0a2041d3162122444eea7dec363c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Fri, 24 Apr 2026 12:28:46 +0200 Subject: [PATCH 14/19] VulkanDeviceContext: log first occurrence of suppressed VVL messages g_ignoredValidationMessageIds[] silently discarded matching debug report messages, so a legitimate regression that happened to hit one of the suppressed VUIDs (notably VUID-07139 for H.264 range alignment) would stay completely invisible. Keep the suppression but print each suppressed message id once, guarded by a mutex so it is safe against concurrent driver threads. Further occurrences of the same id remain silent, so the signal-to- noise ratio is unchanged for normal runs. --- .../libs/VkCodecUtils/VulkanDeviceContext.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp index c14650bd..cdaf8ff6 100644 --- a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp +++ b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include // std::find_if @@ -443,10 +444,24 @@ bool VulkanDeviceContext::DebugReportCallback(VkDebugReportFlagsEXT flags, VkDeb uint64_t, size_t, int32_t msg_code, const char *layer_prefix, const char *msg) { - // Suppress known validation layer false positives (see explanations above) + // Suppress known validation layer false positives (see explanations above). + // Print the first occurrence of each suppressed id so developers retain + // visibility that a suppression is active; subsequent occurrences stay silent. for (uint32_t ignoredId : g_ignoredValidationMessageIds) { if (static_cast(msg_code) == ignoredId) { - return false; // Silently ignore this message + static std::mutex s_suppressMutex; + static std::unordered_set s_firstSeen; + bool firstOccurrence = false; + { + std::lock_guard lock(s_suppressMutex); + firstOccurrence = s_firstSeen.insert(ignoredId).second; + } + if (firstOccurrence) { + fprintf(stderr, + "[VVL-suppress] %s: %s (messageId=0x%08x, suppressing further occurrences)\n", + layer_prefix, msg, ignoredId); + } + return false; } } From f46925f0a6bb4088809cc167c0af3eccdd8480b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Mon, 27 Apr 2026 14:41:21 +0200 Subject: [PATCH 15/19] VulkanVideoFrameBuffer: decouple consumer-semaphore creation from frame-semaphore Commit da44351 ("VL: use OPAQUE_WIN32_BIT for semaphore export on Windows") restructured NvPerFrameDecodeImageSet::init() to add a VkExportSemaphoreCreateInfo pNext chain, but in doing so nested the m_consumerCompleteSemaphore creation inside the m_frameCompleteSemaphore == VK_NULL_HANDLE block. If init() runs again with m_frameCompleteSemaphore already non-NULL while m_consumerCompleteSemaphore is NULL (partial-failure / partial-teardown path), the consumer semaphore is silently never created. Downstream QueuePictureForDecode then hands VK_NULL_HANDLE to VkSemaphoreSubmitInfo. Restore the original two-sibling create structure and hoist the export / timeline / sem create-info structs to the outer scope so both Create calls share the same configuration. --- .../VulkanVideoFrameBuffer.cpp | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp b/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp index 032bb92f..93f29bc0 100644 --- a/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp +++ b/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp @@ -955,34 +955,36 @@ int32_t NvPerFrameDecodeImageSet::init(const VulkanDeviceContext* vkDevCtx, } } - if (m_frameCompleteSemaphore == VK_NULL_HANDLE) { - VkExportSemaphoreCreateInfo exportInfo = {}; - exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; - exportInfo.pNext = nullptr; + VkExportSemaphoreCreateInfo exportInfo = {}; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.pNext = nullptr; #ifdef _WIN32 - exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT; #else - exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; #endif - VkSemaphoreTypeCreateInfo timelineCreateInfo = {}; - timelineCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; - timelineCreateInfo.pNext = &exportInfo; - timelineCreateInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; - timelineCreateInfo.initialValue = 0ULL; + VkSemaphoreTypeCreateInfo timelineCreateInfo = {}; + timelineCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + timelineCreateInfo.pNext = &exportInfo; + timelineCreateInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + timelineCreateInfo.initialValue = 0ULL; + + VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, &timelineCreateInfo }; - VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, &timelineCreateInfo }; + if (m_frameCompleteSemaphore == VK_NULL_HANDLE) { result = vkDevCtx->CreateSemaphore(*vkDevCtx, &semInfo, nullptr, &m_frameCompleteSemaphore); assert(result == VK_SUCCESS); if (result != VK_SUCCESS) { return -1; } - if (m_consumerCompleteSemaphore == VK_NULL_HANDLE) { - result = vkDevCtx->CreateSemaphore(*vkDevCtx, &semInfo, nullptr, &m_consumerCompleteSemaphore); - assert(result == VK_SUCCESS); - if (result != VK_SUCCESS) { - return -1; - } + } + + if (m_consumerCompleteSemaphore == VK_NULL_HANDLE) { + result = vkDevCtx->CreateSemaphore(*vkDevCtx, &semInfo, nullptr, &m_consumerCompleteSemaphore); + assert(result == VK_SUCCESS); + if (result != VK_SUCCESS) { + return -1; } } From 96a244db86db6a6941d0bbe26f73e5868fb6ca96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Mon, 27 Apr 2026 16:26:39 +0200 Subject: [PATCH 16/19] fixup! common: fix VulkanSamplerYcbcrConversion lifetime in VkImageResourceView --- common/libs/VkCodecUtils/VkImageResource.cpp | 16 ++++++---------- common/libs/VkCodecUtils/VulkanDeviceContext.cpp | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/common/libs/VkCodecUtils/VkImageResource.cpp b/common/libs/VkCodecUtils/VkImageResource.cpp index a05a17ee..aa7d17ff 100644 --- a/common/libs/VkCodecUtils/VkImageResource.cpp +++ b/common/libs/VkCodecUtils/VkImageResource.cpp @@ -15,6 +15,7 @@ */ #include +#include #include "VkCodecUtils/HelpersDispatchTable.h" #include "VkCodecUtils/Helpers.h" #include "VkCodecUtils/VulkanDeviceContext.h" @@ -196,9 +197,9 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, VkSamplerYcbcrConversionInfo ycbcrInfo = {}; ycbcrInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO; - // Heap-allocated so ownership can be transferred to the VkImageResourceView; - // the conversion handle must outlive the image view that references it in pNext. - VulkanSamplerYcbcrConversion* samplerYcbcrConversion = nullptr; + // Owned locally until handed off to VkImageResourceView at the end. + // The conversion handle must outlive the image view that references it in pNext. + std::unique_ptr samplerYcbcrConversion; if (mpInfo && (imageResource->GetImageCreateInfo().usage & VK_IMAGE_USAGE_SAMPLED_BIT)) { const VkSamplerYcbcrConversionCreateInfo defaultSamplerYcbcrConversionCreateInfo = { @@ -215,10 +216,9 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, false }; - samplerYcbcrConversion = new VulkanSamplerYcbcrConversion(); + samplerYcbcrConversion = std::make_unique(); VkResult result = samplerYcbcrConversion->CreateVulkanSampler(vkDevCtx, NULL, &defaultSamplerYcbcrConversionCreateInfo); if (result != VK_SUCCESS) { - delete samplerYcbcrConversion; return result; } @@ -228,7 +228,6 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, VkResult result = vkDevCtx->CreateImageView(device, &viewInfo, nullptr, &imageViews[numViews]); if (result != VK_SUCCESS) { - delete samplerYcbcrConversion; return result; } numViews++; @@ -241,7 +240,6 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT << numPlanes; result = vkDevCtx->CreateImageView(device, &viewInfo, nullptr, &imageViews[numViews]); if (result != VK_SUCCESS) { - delete samplerYcbcrConversion; return result; } numViews++; @@ -252,7 +250,6 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT << numPlanes; result = vkDevCtx->CreateImageView(device, &viewInfo, nullptr, &imageViews[numViews]); if (result != VK_SUCCESS) { - delete samplerYcbcrConversion; return result; } numViews++; @@ -263,7 +260,6 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT << numPlanes; result = vkDevCtx->CreateImageView(device, &viewInfo, nullptr, &imageViews[numViews]); if (result != VK_SUCCESS) { - delete samplerYcbcrConversion; return result; } numViews++; @@ -275,7 +271,7 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx, imageResourceView = new VkImageResourceView(vkDevCtx, imageResource, numViews, numViews - 1, imageViews, imageSubresourceRange, - samplerYcbcrConversion); + samplerYcbcrConversion.release()); return result; } diff --git a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp index cdaf8ff6..9edd32a7 100644 --- a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp +++ b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp @@ -909,7 +909,7 @@ VkResult VulkanDeviceContext::CreateVulkanDevice(int32_t numDecodeQueues, CHECK_VULKAN_FEATURE(videoMaintenance1Features.videoMaintenance1, "videoMaintenance1", true); CHECK_VULKAN_FEATURE(synchronization2Features.synchronization2, "synchronization2", false); CHECK_VULKAN_FEATURE(samplerYcbcrConversionFeatures.samplerYcbcrConversion, - "samplerYcbcrConversion", true); + "samplerYcbcrConversion", false); CHECK_VULKAN_FEATURE(((videoCodecs & VK_VIDEO_CODEC_OPERATION_ENCODE_AV1_BIT_KHR) != 0) == (videoEncodeAV1Feature.videoEncodeAV1 != VK_FALSE), "videoEncodeAV1", false); CHECK_VULKAN_FEATURE(((videoCodecs & VK_VIDEO_CODEC_OPERATION_DECODE_VP9_BIT_KHR) != 0) == From d4a9f1d7e3eaee4db7e4ab19ef92c33707ea5153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Mon, 27 Apr 2026 16:26:52 +0200 Subject: [PATCH 17/19] fixup! VulkanDeviceContext: log first occurrence of suppressed VVL messages --- .../libs/VkCodecUtils/VulkanDeviceContext.cpp | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp index 9edd32a7..a9e9427e 100644 --- a/common/libs/VkCodecUtils/VulkanDeviceContext.cpp +++ b/common/libs/VkCodecUtils/VulkanDeviceContext.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -444,24 +445,31 @@ bool VulkanDeviceContext::DebugReportCallback(VkDebugReportFlagsEXT flags, VkDeb uint64_t, size_t, int32_t msg_code, const char *layer_prefix, const char *msg) { + // Allow developers to bypass all VVL suppressions for regression hunts. + static const bool s_suppressionsDisabled = + (std::getenv("VKVS_DISABLE_VVL_SUPPRESSION") != nullptr); + + static std::mutex s_suppressMutex; + static std::unordered_set s_firstSeen; + // Suppress known validation layer false positives (see explanations above). // Print the first occurrence of each suppressed id so developers retain // visibility that a suppression is active; subsequent occurrences stay silent. - for (uint32_t ignoredId : g_ignoredValidationMessageIds) { - if (static_cast(msg_code) == ignoredId) { - static std::mutex s_suppressMutex; - static std::unordered_set s_firstSeen; - bool firstOccurrence = false; - { - std::lock_guard lock(s_suppressMutex); - firstOccurrence = s_firstSeen.insert(ignoredId).second; - } - if (firstOccurrence) { - fprintf(stderr, - "[VVL-suppress] %s: %s (messageId=0x%08x, suppressing further occurrences)\n", - layer_prefix, msg, ignoredId); + if (!s_suppressionsDisabled) { + for (uint32_t ignoredId : g_ignoredValidationMessageIds) { + if (static_cast(msg_code) == ignoredId) { + bool firstOccurrence = false; + { + std::lock_guard lock(s_suppressMutex); + firstOccurrence = s_firstSeen.insert(ignoredId).second; + } + if (firstOccurrence) { + fprintf(stderr, + "[VVL-suppress] %s: %s (messageId=0x%08x, suppressing further occurrences)\n", + layer_prefix, msg, ignoredId); + } + return false; } - return false; } } From 68632e736054296daefd88bae66ecea9ba2efcd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Mon, 27 Apr 2026 16:38:11 +0200 Subject: [PATCH 18/19] VulkanVideoFrameBuffer: warn on unexpected output image layout at dequeue Commit 3af40a6 ("VL: reset consumer-done fence regardless of semaphore usage") restricted output-layout propagation to VIDEO_DECODE_DPB_KHR / DST_KHR. Any other tracked layout is silently swallowed and outputImageLayout falls back to the constructor-default DST_KHR. If the tracker ever reports e.g. GENERAL or UNDEFINED, the downstream display barrier issues an oldLayout=DST_KHR transition on an image actually in another layout - undefined behaviour. --- .../VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp b/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp index 93f29bc0..f5075ca7 100644 --- a/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp +++ b/vk_video_decoder/libs/VulkanVideoFrameBuffer/VulkanVideoFrameBuffer.cpp @@ -537,6 +537,15 @@ class VkVideoFrameBuffer : public VulkanVideoFrameBuffer { if (trackedLayout == VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR || trackedLayout == VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR) { pDecodedFrame->outputImageLayout = trackedLayout; + } else { + // The tracker should always report DPB_KHR (coincided mode) + // or DST_KHR (distinct mode) at dequeue time. Anything else + // would mean a previous pass left the image in an unexpected + // state and the default DST_KHR transition would be wrong. + fprintf(stderr, + "WARNING: unexpected output image layout %d at dequeue, " + "defaulting to VIDEO_DECODE_DST_KHR\n", + (int)trackedLayout); } } } From f908816dac71f253395f91a82b18bc4d46b7b9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Mon, 27 Apr 2026 16:52:02 +0200 Subject: [PATCH 19/19] VkVideoDecoder: assert bitstreamDataOffset is zero for non-VP9 codecs Commit 8ffb8b0 ("decoder: Fix bitstream buffer alignment for all codecs") added a runtime warning + force-to-zero fallback when bitstreamDataOffset is non-zero for H.264 / H.265 / AV1. The fallback rescues release builds, but the parser invariant says this can never legitimately happen, so silently rewriting the offset would mask a real parser regression. Add an assert() on top of the warning so debug builds fail loudly the first time the invariant is violated, while release builds keep the safe recovery path. --- vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp b/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp index 3d78e932..ba6568e9 100644 --- a/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp +++ b/vk_video_decoder/libs/VkVideoDecoder/VkVideoDecoder.cpp @@ -830,6 +830,7 @@ int VkVideoDecoder::DecodePictureWithParameters(VkParserPerFrameDecodeParameters VkDeviceSize srcOffset = pCurrFrameDecParams->bitstreamDataOffset; // 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) { + assert(srcOffset == 0 && "non-zero bitstreamDataOffset for non-VP9 codec"); if (srcOffset != 0) { fprintf(stderr, "WARNING: bitstreamDataOffset=%zu is non-zero for non-VP9 codec, forcing to 0\n", (size_t)srcOffset);