Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d27f54a
tests: add --validate to enable the Vulkan validation layer
dabrain34 May 25, 2026
45bc077
VulkanDeviceContext: enable samplerYcbcrConversion feature
dabrain34 Dec 17, 2025
fccd1a7
decoder: Suppress known VVL false positives in debug callback
zlatinski Feb 11, 2026
c1ccad5
common: Add VkSamplerYcbcrConversionInfo to the VkImageViewCreateInfo…
zzoon May 16, 2025
82170b9
decoder: Fix bitstream buffer alignment for all codecs
zlatinski Feb 11, 2026
534f1c9
decoder: Fix bitstream buffer alignment for all codecs - build fix
dabrain34 Apr 15, 2026
62bb8b0
decoder: Fix video session parameters updateSequenceCount (VUID-07215)
zlatinski Feb 11, 2026
c1e7069
VL: use OPAQUE_WIN32_BIT for semaphore export on Windows
zlatinski Mar 5, 2026
6cc67cf
VL: propagate actual output image layout to display pipeline
zlatinski Mar 5, 2026
d421347
VL: reset consumer-done fence regardless of semaphore usage
zlatinski Mar 5, 2026
99f10fe
VL: use ALL_COMMANDS_BIT for graphics queue semaphore wait
zlatinski Mar 5, 2026
e0e09b1
VL: use ALL_COMMANDS_BIT for decode queue semaphore wait
zlatinski Mar 5, 2026
d8991e2
common: fix VulkanSamplerYcbcrConversion lifetime in VkImageResourceView
dabrain34 Apr 24, 2026
b28ab53
VL: honor inputImageToDrawFrom->imageLayout in display barriers
dabrain34 Apr 24, 2026
4ce24c2
VulkanDeviceContext: log first occurrence of suppressed VVL messages
dabrain34 Apr 24, 2026
873bdea
VulkanVideoFrameBuffer: decouple consumer-semaphore creation from fra…
dabrain34 Apr 27, 2026
4f7fb48
VulkanVideoFrameBuffer: warn on unexpected output image layout at deq…
dabrain34 Apr 27, 2026
9c44998
VkVideoDecoder: assert bitstreamDataOffset is zero for non-VP9 codecs
dabrain34 Apr 27, 2026
fe99316
decoder: remove a known VVL issue
dabrain34 May 20, 2026
f687752
common: strip STORAGE_BIT from multi-planar combined view
dabrain34 May 25, 2026
3a65a51
common: use GENERAL layout for storage image descriptors
dabrain34 May 25, 2026
8c0cdb1
common: create per-plane image views as 2D_ARRAY
dabrain34 May 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 71 additions & 2 deletions common/libs/VkCodecUtils/VkImageResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
*/

#include <atomic>
#include <memory>
#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"

Expand Down Expand Up @@ -190,16 +192,78 @@ 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 = {};
ycbcrInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO;

// A combined view over a multi-planar format cannot be used as a STORAGE
// image (the format lacks the STORAGE_IMAGE feature); storage access goes
// through the per-plane views below. Strip STORAGE_BIT from the combined
// view's usage to avoid VUID-VkImageViewCreateInfo-usage-02275.
//
// NOTE: This is a minimal local fix. The nvpro upstream solves this (and
// the related 06415/08333) in commit c76f219 ("Fix VL: create YCbCr
// conversion at decoder setup for combined views"), which also strips
// SAMPLED_BIT and skips the combined view when usage collapses to zero.
// That commit is hard to cherry-pick here: it is a delta on top of the
// restructured multi-overload Create() introduced by the 10.8k-line
// commit 4845b69 ("Add VulkanFilterYuvCompute RGBA2YCBCR filter ..."),
// which this tree does not have. Supersede this with c76f219 once that
// restructure is ported.
VkImageViewUsageCreateInfo combinedViewUsage = { VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO };
if (mpInfo) {
combinedViewUsage.usage = imageResource->GetImageCreateInfo().usage &
~VK_IMAGE_USAGE_STORAGE_BIT;
viewInfo.pNext = &combinedViewUsage;
}

// 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<VulkanSamplerYcbcrConversion> samplerYcbcrConversion;

if (mpInfo && (imageResource->GetImageCreateInfo().usage & VK_IMAGE_USAGE_SAMPLED_BIT)) {
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
};

samplerYcbcrConversion = std::make_unique<VulkanSamplerYcbcrConversion>();
VkResult result = samplerYcbcrConversion->CreateVulkanSampler(vkDevCtx, NULL, &defaultSamplerYcbcrConversionCreateInfo);
if (result != VK_SUCCESS) {
return result;
}

ycbcrInfo.conversion = samplerYcbcrConversion->GetSamplerYcbcrConversion();
// Chain after the usage-restriction struct so both reach the driver.
ycbcrInfo.pNext = viewInfo.pNext;
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;
// The per-plane views are bound as compute storage images by the YCbCr
// filter, whose shaders declare image2DArray and store with a layer
// index. Use a 2D_ARRAY view (valid even for a single layer) so the
// view type matches the shader, avoiding VUID-vkCmdDispatch-viewType-07752.
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
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]);
Expand Down Expand Up @@ -234,7 +298,8 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx,

imageResourceView = new VkImageResourceView(vkDevCtx, imageResource,
numViews, numViews - 1,
imageViews, imageSubresourceRange);
imageViews, imageSubresourceRange,
samplerYcbcrConversion.release());

return result;
}
Expand All @@ -248,6 +313,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;
}
10 changes: 8 additions & 2 deletions common/libs/VkCodecUtils/VkImageResource.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include "VkCodecUtils/VkVideoRefCountBase.h"
#include "VkCodecUtils/VulkanDeviceMemoryImpl.h"

class VulkanSamplerYcbcrConversion;

class VkImageResource : public VkVideoRefCountBase
{
public:
Expand Down Expand Up @@ -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<VkImageResource>& 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];
Expand Down
2 changes: 1 addition & 1 deletion common/libs/VkCodecUtils/VulkanBistreamBufferImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
75 changes: 73 additions & 2 deletions common/libs/VkCodecUtils/VulkanDeviceContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@

#include <cassert>
#include <assert.h>
#include <cstdlib>
#include <string.h>
#include <array>
#include <iostream>
#include <string>
#include <sstream>
#include <mutex>
#include <set>
#include <unordered_set>
#include <algorithm> // std::find_if
Expand Down Expand Up @@ -392,10 +394,72 @@ 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-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-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,
uint64_t, size_t,
int32_t, const char *layer_prefix, const char *msg)
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<uint32_t> 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.
if (!s_suppressionsDisabled) {
for (uint32_t ignoredId : g_ignoredValidationMessageIds) {
if (static_cast<uint32_t>(msg_code) == ignoredId) {
bool firstOccurrence = false;
{
std::lock_guard<std::mutex> 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;
}
}
}

LogPriority prio = LOG_WARN;
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT)
prio = LOG_ERR;
Expand Down Expand Up @@ -808,8 +872,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
};

Expand All @@ -834,6 +903,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", 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) ==
Expand Down
3 changes: 3 additions & 0 deletions common/libs/VkCodecUtils/VulkanDisplayFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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;
Expand All @@ -105,6 +107,7 @@ class VulkanDisplayFrame
, submittedVideoQueueIndex()
, hasConsummerSignalFence()
, hasConsummerSignalSemaphore()
, outputImageLayout(VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR)
{}

virtual ~VulkanDisplayFrame() {
Expand Down
20 changes: 16 additions & 4 deletions common/libs/VkCodecUtils/VulkanFilterYuvCompute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2361,15 +2361,27 @@ uint32_t VulkanFilterYuvCompute::UpdateImageDescriptorSets(
writeDescriptorSets[descrIndex].dstSet = VK_NULL_HANDLE;
writeDescriptorSets[descrIndex].dstBinding = dstBinding;
writeDescriptorSets[descrIndex].descriptorCount = 1;
writeDescriptorSets[descrIndex].descriptorType = (ccSampler != VK_NULL_HANDLE) ?
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER :
descriptorType;
const VkDescriptorType resolvedType = (ccSampler != VK_NULL_HANDLE) ?
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER :
descriptorType;
writeDescriptorSets[descrIndex].descriptorType = resolvedType;
imageDescriptors[descrIndex].sampler = ccSampler;
imageDescriptors[descrIndex].imageView = (curImageAspect == 0) ?
imageView->GetImageView() :
imageView->GetPlaneImageView(planeNum);
assert(imageDescriptors[descrIndex].imageView);
imageDescriptors[descrIndex].imageLayout = imageLayout;
// A STORAGE_IMAGE descriptor must be in GENERAL layout (VUID-04152);
// only sampled descriptors may use SHADER_READ_ONLY_OPTIMAL.
//
// NOTE: minimal local fix. nvpro upstream decides the layout at the
// call site instead (storage -> GENERAL, sampled -> READ_ONLY) in
// commit 4845b69 ("Add VulkanFilterYuvCompute RGBA2YCBCR filter and
// comprehensive test suite", ~10.8k lines). That commit is the hard
// dependency for the c76f219 view fix and is impractical to
// cherry-pick here; this override is equivalent until it is ported.
imageDescriptors[descrIndex].imageLayout =
(resolvedType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) ?
VK_IMAGE_LAYOUT_GENERAL : imageLayout;
writeDescriptorSets[descrIndex].pImageInfo = &imageDescriptors[descrIndex]; // Y (0) plane
descrIndex++;
validImageAspects &= ~(VK_IMAGE_ASPECT_COLOR_BIT << curImageAspect);
Expand Down
6 changes: 2 additions & 4 deletions common/libs/VkCodecUtils/VulkanFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ VkResult VulkanFrame<FrameDataType>::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;
Expand Down Expand Up @@ -617,9 +617,7 @@ VkResult VulkanFrame<FrameDataType>::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++;

Expand Down
4 changes: 4 additions & 0 deletions common/libs/VkCodecUtils/VulkanSamplerYcbcrConversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ class VulkanSamplerYcbcrConversion {
return m_sampler;
}

VkSamplerYcbcrConversion GetSamplerYcbcrConversion() {
return m_samplerYcbcrConversion;
}

const VkSamplerYcbcrConversionCreateInfo& GetSamplerYcbcrConversionCreateInfo() const
{
return m_samplerYcbcrConversionCreateInfo;
Expand Down
Loading
Loading