Skip to content
30 changes: 28 additions & 2 deletions common/libs/VkCodecUtils/VkImageResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -566,25 +566,51 @@ VkResult VkImageResourceView::Create(const VulkanDeviceContext* vkDevCtx,
viewInfo.flags = 0;

const VkMpFormatInfo* mpInfo = YcbcrVkFormatInfo(viewInfo.format);
const VkImageCreateInfo& imageCreateInfo = imageResource->GetImageCreateInfo();

// For multi-planar formats with planeUsageOverride, skip the combined view (index 0)
// as the combined format may not support the requested usage (e.g., STORAGE)
bool skipCombinedView = (mpInfo != nullptr) && (planeUsageOverride != 0);

// Setup VkImageViewUsageCreateInfo - required when using EXTENDED_USAGE_BIT per Khronos issue #4624:
// Combined views must restrict usage to what the multi-planar format supports (exclude STORAGE)
// Per-plane views must restrict usage to what the plane format supports (exclude VIDEO_ENCODE_SRC)
VkImageViewUsageCreateInfo usageCreateInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO};
usageCreateInfo.pNext = nullptr;
usageCreateInfo.usage = planeUsageOverride;

if (!skipCombinedView) {
// For multi-planar formats, combined views without YCbCr conversion cannot have:
// - STORAGE_BIT: multi-planar formats don't support storage (only per-plane views do)
// - SAMPLED_BIT: multi-planar formats require YCbCr conversion for sampling
// Must use VkImageViewUsageCreateInfo to restrict usage.
// Reference: https://gitlab.khronos.org/vulkan/vulkan/-/issues/4624
if (mpInfo) {
VkImageUsageFlags combinedUsage = imageCreateInfo.usage;
// Remove usages not supported by multi-planar base format without YCbCr conversion
VkImageUsageFlags unsupportedUsages = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
if (combinedUsage & unsupportedUsages) {
combinedUsage &= ~unsupportedUsages;
usageCreateInfo.usage = combinedUsage;
viewInfo.pNext = &usageCreateInfo;
}
}

VkResult result = vkDevCtx->CreateImageView(device, &viewInfo, nullptr, &imageViews[numViews]);
if (result != VK_SUCCESS) {
return result;
}
numViews++;

// Reset pNext for subsequent views
viewInfo.pNext = nullptr;
} else {
// Set placeholder for combined view
imageViews[numViews] = VK_NULL_HANDLE;
numViews++;
}

// Setup VkImageViewUsageCreateInfo for plane views when planeUsageOverride is set
VkImageViewUsageCreateInfo usageCreateInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO};
// Reset usage for plane views
usageCreateInfo.usage = planeUsageOverride;

if (mpInfo) { // Is this a YCbCr format
Expand Down
8 changes: 7 additions & 1 deletion common/libs/VkCodecUtils/VulkanDeviceContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,13 @@ VkResult VulkanDeviceContext::CreateVulkanDevice(int32_t numDecodeQueues,
VK_FALSE
};

VkPhysicalDeviceFeatures2 deviceFeatures { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &intraRefreshFeatures};
// Required for creating YCbCr samplers used with multi-planar video formats
VkPhysicalDeviceSamplerYcbcrConversionFeatures samplerYcbcrFeatures { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES,
&intraRefreshFeatures,
VK_FALSE
};

VkPhysicalDeviceFeatures2 deviceFeatures { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &samplerYcbcrFeatures};
GetPhysicalDeviceFeatures2(m_physDevice, &deviceFeatures);

assert(timelineSemaphoreFeatures.timelineSemaphore);
Expand Down
6 changes: 6 additions & 0 deletions vk_video_decoder/libs/NvVideoParser/src/VulkanAV1Decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2291,6 +2291,12 @@ bool VulkanAV1Decoder::ParseObuTileGroup(const AV1ObuHeader& hdr)
skip_bits((uint32_t)(tileSize * 8));
}

// Bounds check to prevent buffer overflow into pStdSps and other fields
if (m_PicData.khr_info.tileCount >= 64) {
nvParserErrorLog("s", "\nError: Tile count exceeds maximum of 64 tiles\n");
return false;
}

m_PicData.tileSizes[m_PicData.khr_info.tileCount] = (uint32_t)tileSize;
m_PicData.khr_info.tileCount++;
}
Expand Down
9 changes: 9 additions & 0 deletions vk_video_decoder/libs/VkVideoParser/VulkanVideoParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,15 @@ int32_t VulkanVideoParser::BeginSequence(const VkParserSequenceInfo* pnvsi)
assert(!"m_pDecoderHandler is NULL");
}

// When starting a new sequence (not reconfigure), reset the picture-to-DPB slot mapping
// to avoid freeing slots from the old, larger DPB after it has been deinitialized.
if (!sequenceUpdate) {
m_dpbSlotsMask = 0;
for (uint32_t picId = 0; picId < MAX_FRM_CNT; picId++) {
m_pictureToDpbSlotMap[picId] = -1;
}
}

m_maxNumDpbSlots = m_dpb.Init(configDpbSlots, sequenceUpdate /* reconfigure the DPB size if true */);

return m_maxNumDecodeSurfaces;
Expand Down
7 changes: 7 additions & 0 deletions vk_video_encoder/libs/VkVideoEncoder/VkEncoderConfigH264.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -596,5 +596,12 @@ bool EncoderConfigH264::GetRateControlParameters(VkVideoEncodeRateControlInfoKHR
pRateControlInfoH264->gopFrameCount = (gopStructure.GetGopFrameCount() > 0) ? gopStructure.GetGopFrameCount() : (uint32_t)GOP_LENGTH_DEFAULT;
pRateControlInfoH264->idrPeriod = (gopStructure.GetIdrPeriod() > 0) ? gopStructure.GetIdrPeriod() : (uint32_t)IDR_PERIOD_DEFAULT;

// Validation: If idrPeriod is not 0, it must be >= gopFrameCount
// If gopFrameCount is very large (e.g., UINT32_MAX for infinite GOP), set idrPeriod to 0
if (pRateControlInfoH264->idrPeriod != 0 &&
pRateControlInfoH264->idrPeriod < pRateControlInfoH264->gopFrameCount) {
pRateControlInfoH264->idrPeriod = 0; // Disable IDR period when GOP is larger
}

return true;
}
12 changes: 10 additions & 2 deletions vk_video_encoder/libs/VkVideoEncoder/VkVideoEncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2088,8 +2088,16 @@ bool VkVideoEncoder::HandleCtrlCmd(VkSharedBaseObj<VkVideoEncodeFrameInfo>& enco
encodeFrameInfo->rateControlLayersInfo[layerIndx].sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_RATE_CONTROL_LAYER_INFO_KHR;
}

encodeFrameInfo->rateControlInfo.pLayers = encodeFrameInfo->rateControlLayersInfo;
encodeFrameInfo->rateControlInfo.layerCount = 1;
// layerCount must be 0 when rateControlMode is DEFAULT or DISABLED
VkVideoEncodeRateControlModeFlagBitsKHR rcMode = encodeFrameInfo->rateControlInfo.rateControlMode;
if (rcMode == VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DEFAULT_KHR ||
rcMode == VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR) {
encodeFrameInfo->rateControlInfo.pLayers = nullptr;
encodeFrameInfo->rateControlInfo.layerCount = 0;
} else {
encodeFrameInfo->rateControlInfo.pLayers = encodeFrameInfo->rateControlLayersInfo;
encodeFrameInfo->rateControlInfo.layerCount = 1;
}
m_beginRateControlInfo = encodeFrameInfo->rateControlInfo;

if (pNext != nullptr) {
Expand Down
11 changes: 8 additions & 3 deletions vk_video_encoder/libs/VkVideoEncoder/VkVideoEncoderAV1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,12 @@ VkResult VkVideoEncoderAV1::ProcessDpb(VkSharedBaseObj<VkVideoEncodeFrameInfo>&
assert(dpbIndx >= 0);

m_dpbAV1->ConfigureRefBufUpdate(pFrameInfo->bShownKeyFrameOrSwitch, pFrameInfo->bShowExistingFrame, frameUpdateType);
assert(pFrameInfo->frameEncodeEncodeOrderNum <= std::numeric_limits<uint32_t>::max());
m_dpbAV1->InvalidateStaleReferenceFrames(static_cast<uint32_t>(pFrameInfo->frameEncodeEncodeOrderNum), pFrameInfo->picOrderCntVal, &m_stateAV1.m_sequenceHeader);
pFrameInfo->stdPictureInfo.refresh_frame_flags = (uint8_t)m_dpbAV1->GetRefreshFrameFlags(pFrameInfo->bShownKeyFrameOrSwitch, pFrameInfo->bShowExistingFrame);

// For show existing frame, skip stale reference invalidation and return early.
// Show existing frames don't have a valid frameEncodeEncodeOrderNum since they
// don't represent a new encoded frame - they just display a previously encoded one.
Copy link
Contributor

@krajunv krajunv Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a new problem. I did not see any issue when encoding. Either it was holding correct state or initialized to some non-negative value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed that the assert() was added with commit#1a883255 and I think this check uncovered the issue in debug mode.

Your change looks good to me.

if (pFrameInfo->bShowExistingFrame) {
pFrameInfo->stdPictureInfo.refresh_frame_flags = (uint8_t)m_dpbAV1->GetRefreshFrameFlags(pFrameInfo->bShownKeyFrameOrSwitch, pFrameInfo->bShowExistingFrame);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this line is fine, but updating refresh_frame_flags isn’t required because it isn’t encoded when show_existing_frame is 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for looking at the changes, @krajunv! Can you please be more specific on the change you want me to make?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line doesn’t affect the show_existing_frame=1 frame header encoding and can be safely removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the refresh_frame_flags is not present in the bitstream when show_existing_frame=1. But we would still want to keep this line because:

Vulkan API validation - The driver may still validate the struct fields even if it doesn't encode them. Setting it to 0 ensures no validation warnings about unexpected non-zero values.

Internal DPB tracking - While the driver won't write it to the bitstream, it may still use the value internally to verify no reference buffer updates are being requested.

Defensive coding - If a future Vulkan implementation or validation layer starts checking this field, having it correctly set to 0 prevents issues.

Bottom line: It's technically unnecessary for bitstream generation, but harmless and follows the principle of keeping the struct consistent with what the encode operation semantically means (no reference updates).

Do you have a strong objection on this, @krajunv?

m_dpbAV1->DpbPictureEnd(dpbIndx, encodeFrameInfo->setupImageResource/*nullptr*/, &m_stateAV1.m_sequenceHeader,
pFrameInfo->bShowExistingFrame, pFrameInfo->bShownKeyFrameOrSwitch,
pFrameInfo->stdPictureInfo.flags.error_resilient_mode,
Expand All @@ -240,6 +241,10 @@ VkResult VkVideoEncoderAV1::ProcessDpb(VkSharedBaseObj<VkVideoEncodeFrameInfo>&
return VK_SUCCESS;
}

assert(pFrameInfo->frameEncodeEncodeOrderNum <= std::numeric_limits<uint32_t>::max());
m_dpbAV1->InvalidateStaleReferenceFrames(static_cast<uint32_t>(pFrameInfo->frameEncodeEncodeOrderNum), pFrameInfo->picOrderCntVal, &m_stateAV1.m_sequenceHeader);
pFrameInfo->stdPictureInfo.refresh_frame_flags = (uint8_t)m_dpbAV1->GetRefreshFrameFlags(pFrameInfo->bShownKeyFrameOrSwitch, pFrameInfo->bShowExistingFrame);

// setup recon picture (pSetupReferenceSlot)
bool success = m_dpbImagePool->GetAvailableImage(encodeFrameInfo->setupImageResource,
VK_IMAGE_LAYOUT_VIDEO_ENCODE_DPB_KHR);
Expand Down
Loading