blob: d05c98a9bc887337fc6a220f64f6f0df3bd2f27d [file]
/*
* Copyright (c) 2025 The Khronos Group Inc.
* Copyright (c) 2025 RasterGrid Kft.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
#include "../framework/video_objects.h"
class PositiveVideoEncodeIntraRefresh : public VkVideoLayerTest {
public:
void TestIntraRefreshCycle(VideoConfig& config, uint32_t duration, uint32_t partition_count = 1) {
// Use maximum extent to not run into other limitations related to granularity
config.UpdateMaxCodedExtent(config.Caps()->maxCodedExtent);
config.SessionCreateInfo()->maxDpbSlots = 2;
config.SessionCreateInfo()->maxActiveReferencePictures = 1;
VideoContext context(m_device, config);
context.CreateAndBindSessionMemory();
context.CreateResources();
vkt::CommandBuffer& cb = context.CmdBuffer();
cb.Begin();
cb.BeginVideoCoding(context.Begin().AddResource(0, 0).AddResource(1, 1));
uint32_t ref_slot = 1;
uint32_t target_slot = 0;
cb.EncodeVideo(context.EncodeReferenceFrame(target_slot));
for (uint32_t index = 0; index < duration; ++index) {
std::swap(target_slot, ref_slot);
if (index == 0) {
// We refer to a non-intra-refreshed frame
cb.EncodeVideo(context.EncodeReferenceFrame(target_slot)
.SetPartitionCount(partition_count)
.IntraRefresh(duration, index)
.AddReferenceFrame(ref_slot));
} else {
// We refer to an intra-refreshed frame with dirty regions
const uint32_t dirty_regions = duration - index;
cb.EncodeVideo(context.EncodeReferenceFrame(target_slot)
.SetPartitionCount(partition_count)
.IntraRefresh(duration, index)
.AddReferenceFrameWithDirtyRegions(ref_slot, dirty_regions));
}
}
cb.EncodeVideo(context.EncodeReferenceFrame(0));
cb.EndVideoCoding(context.End());
cb.End();
}
};
TEST_F(PositiveVideoEncodeIntraRefresh, BlockBased) {
TEST_DESCRIPTION("Test block-based partition intra refresh modes");
AddRequiredExtensions(VK_KHR_VIDEO_ENCODE_INTRA_REFRESH_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::videoEncodeIntraRefresh);
RETURN_IF_SKIP(Init());
VideoConfig config = GetConfig(GetConfigsWithBlockBasedIntraRefresh(GetConfigsEncode()));
if (!config) {
GTEST_SKIP() << "Test requires video encode support with block-based intra refresh modes";
}
config.SetVideoEncodeIntraRefreshMode(VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_BASED_BIT_KHR);
TestIntraRefreshCycle(config, config.EncodeIntraRefreshCaps()->maxIntraRefreshCycleDuration);
}
TEST_F(PositiveVideoEncodeIntraRefresh, BlockRowBased) {
TEST_DESCRIPTION("Test block-row-based partition intra refresh modes");
AddRequiredExtensions(VK_KHR_VIDEO_ENCODE_INTRA_REFRESH_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::videoEncodeIntraRefresh);
RETURN_IF_SKIP(Init());
const VkVideoEncodeIntraRefreshModeFlagBitsKHR mode = VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_ROW_BASED_BIT_KHR;
VideoConfig config = GetConfig(GetConfigsWithBlockBasedIntraRefresh(GetConfigsEncode(), mode));
if (!config) {
GTEST_SKIP() << "Test requires video encode support with block-row-based intra refresh modes";
}
config.SetVideoEncodeIntraRefreshMode(mode);
TestIntraRefreshCycle(config, config.EncodeIntraRefreshCaps()->maxIntraRefreshCycleDuration);
}
TEST_F(PositiveVideoEncodeIntraRefresh, BlockColumnBased) {
TEST_DESCRIPTION("Test block-column-based partition intra refresh modes");
AddRequiredExtensions(VK_KHR_VIDEO_ENCODE_INTRA_REFRESH_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::videoEncodeIntraRefresh);
RETURN_IF_SKIP(Init());
const VkVideoEncodeIntraRefreshModeFlagBitsKHR mode = VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_COLUMN_BASED_BIT_KHR;
VideoConfig config = GetConfig(GetConfigsWithBlockBasedIntraRefresh(GetConfigsEncode(), mode));
if (!config) {
GTEST_SKIP() << "Test requires video encode support with block-column-based intra refresh modes";
}
config.SetVideoEncodeIntraRefreshMode(mode);
TestIntraRefreshCycle(config, config.EncodeIntraRefreshCaps()->maxIntraRefreshCycleDuration);
}
TEST_F(PositiveVideoEncodeIntraRefresh, BlockBasedPartitionIndependentH264) {
TEST_DESCRIPTION("Test partition-independent block-based intra refresh with multiple H.264 slices");
AddRequiredExtensions(VK_KHR_VIDEO_ENCODE_INTRA_REFRESH_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::videoEncodeIntraRefresh);
RETURN_IF_SKIP(Init());
VideoConfig config =
GetConfig(FilterConfigs(GetConfigsWithBlockBasedIntraRefresh(GetConfigsEncodeH264()), [](const VideoConfig& config) {
return config.EncodeIntraRefreshCaps()->partitionIndependentIntraRefreshRegions == VK_TRUE &&
config.EncodeCapsH264()->maxSliceCount > 1;
}));
if (!config) {
GTEST_SKIP() << "Test requires H.264 encode support with partition-independent block-based intra refresh";
}
const uint32_t duration = config.EncodeIntraRefreshCaps()->maxIntraRefreshCycleDuration;
const uint32_t max_slice_count = config.EncodeCapsH264()->maxSliceCount;
const uint32_t slice_count = max_slice_count - ((duration == max_slice_count) ? 1 : 0);
config.SetVideoEncodeIntraRefreshMode(VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_BASED_BIT_KHR);
TestIntraRefreshCycle(config, duration, slice_count);
}
TEST_F(PositiveVideoEncodeIntraRefresh, BlockBasedPartitionIndependentH265) {
TEST_DESCRIPTION("Test partition-independent block-based intra refresh with multiple H.265 slice segments");
AddRequiredExtensions(VK_KHR_VIDEO_ENCODE_INTRA_REFRESH_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::videoEncodeIntraRefresh);
RETURN_IF_SKIP(Init());
VideoConfig config =
GetConfig(FilterConfigs(GetConfigsWithBlockBasedIntraRefresh(GetConfigsEncodeH265()), [](const VideoConfig& config) {
return config.EncodeIntraRefreshCaps()->partitionIndependentIntraRefreshRegions == VK_TRUE &&
config.EncodeCapsH265()->maxSliceSegmentCount > 1 &&
(config.EncodeCapsH265()->flags & VK_VIDEO_ENCODE_H265_CAPABILITY_MULTIPLE_SLICE_SEGMENTS_PER_TILE_BIT_KHR) != 0;
}));
if (!config) {
GTEST_SKIP() << "Test requires H.265 encode support with partition-independent block-based intra refresh";
}
const uint32_t duration = config.EncodeIntraRefreshCaps()->maxIntraRefreshCycleDuration;
const uint32_t max_slice_segment_count = config.EncodeCapsH265()->maxSliceSegmentCount;
const uint32_t slice_segment_count = max_slice_segment_count - ((duration == max_slice_segment_count) ? 1 : 0);
config.SetVideoEncodeIntraRefreshMode(VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_BASED_BIT_KHR);
TestIntraRefreshCycle(config, duration, slice_segment_count);
}
TEST_F(PositiveVideoEncodeIntraRefresh, PerPicturePartitionH264) {
TEST_DESCRIPTION("Test per-picture-partition intra refresh mode with H.264 encode profile");
AddRequiredExtensions(VK_KHR_VIDEO_ENCODE_INTRA_REFRESH_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::videoEncodeIntraRefresh);
RETURN_IF_SKIP(Init());
VideoConfig config = GetConfig(GetConfigsWithPerPartitionIntraRefresh(GetConfigsEncodeH264()));
if (!config) {
GTEST_SKIP() << "Test requires H.264 encode support with per-picture-partition intra refresh mode";
}
const uint32_t duration =
std::min(config.EncodeIntraRefreshCaps()->maxIntraRefreshCycleDuration, config.EncodeCapsH264()->maxSliceCount);
const uint32_t slice_count = duration;
config.SetVideoEncodeIntraRefreshMode(VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_PER_PICTURE_PARTITION_BIT_KHR);
TestIntraRefreshCycle(config, duration, slice_count);
}
TEST_F(PositiveVideoEncodeIntraRefresh, PerPicturePartitionH265) {
TEST_DESCRIPTION("Test per-picture-partition intra refresh mode with H.265 encode profile");
AddRequiredExtensions(VK_KHR_VIDEO_ENCODE_INTRA_REFRESH_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::videoEncodeIntraRefresh);
RETURN_IF_SKIP(Init());
VideoConfig config = GetConfig(GetConfigsWithPerPartitionIntraRefresh(GetConfigsEncodeH265()));
if (!config) {
GTEST_SKIP() << "Test requires H.265 encode support with per-picture-partition intra refresh mode";
}
const uint32_t duration =
std::min(config.EncodeIntraRefreshCaps()->maxIntraRefreshCycleDuration, config.EncodeCapsH265()->maxSliceSegmentCount);
const uint32_t slice_segment_count = duration;
config.SetVideoEncodeIntraRefreshMode(VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_PER_PICTURE_PARTITION_BIT_KHR);
TestIntraRefreshCycle(config, duration, slice_segment_count);
}
TEST_F(PositiveVideoEncodeIntraRefresh, EncodeCapsDifferentSliceTypesH264) {
TEST_DESCRIPTION("vkCmdEncodeVideoKHR - using different H.264 slice types is not supported, but slice is intra refreshed");
AddRequiredExtensions(VK_KHR_VIDEO_ENCODE_INTRA_REFRESH_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::videoEncodeIntraRefresh);
RETURN_IF_SKIP(Init());
VideoConfig config =
GetConfig(FilterConfigs(GetConfigsWithPerPartitionIntraRefresh(GetConfigsEncodeH264()), [](const VideoConfig& config) {
return (config.EncodeCapsH264()->flags & VK_VIDEO_ENCODE_H264_CAPABILITY_DIFFERENT_SLICE_TYPE_BIT_KHR) == 0 &&
config.EncodeCapsH264()->maxSliceCount > 1;
}));
if (!config) {
GTEST_SKIP() << "Test requires H.264 encode support with intra refresh but no different slice types";
}
config.SetVideoEncodeIntraRefreshMode(VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_PER_PICTURE_PARTITION_BIT_KHR);
VideoContext context(m_device, config);
context.CreateAndBindSessionMemory();
context.CreateResources();
vkt::CommandBuffer& cb = context.CmdBuffer();
cb.Begin();
cb.BeginVideoCoding(context.Begin());
VideoEncodeInfo encode_info = context.EncodeFrame().IntraRefresh(2, 0);
auto& slice_headers = encode_info.CodecInfo().encode_h264.std_slice_headers;
slice_headers[0].slice_type = STD_VIDEO_H264_SLICE_TYPE_I;
slice_headers[1].slice_type = STD_VIDEO_H264_SLICE_TYPE_P;
cb.EncodeVideo(encode_info);
cb.EndVideoCoding(context.End());
cb.End();
}
TEST_F(PositiveVideoEncodeIntraRefresh, EncodeCapsDifferentSliceTypesH265) {
TEST_DESCRIPTION("vkCmdEncodeVideoKHR - using different H.265 slice types is not supported, but slice is intra refreshed");
AddRequiredExtensions(VK_KHR_VIDEO_ENCODE_INTRA_REFRESH_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::videoEncodeIntraRefresh);
RETURN_IF_SKIP(Init());
VideoConfig config =
GetConfig(FilterConfigs(GetConfigsWithPerPartitionIntraRefresh(GetConfigsEncodeH265()), [](const VideoConfig& config) {
return (config.EncodeCapsH265()->flags & VK_VIDEO_ENCODE_H265_CAPABILITY_DIFFERENT_SLICE_SEGMENT_TYPE_BIT_KHR) == 0 &&
config.EncodeCapsH265()->maxSliceSegmentCount > 1;
}));
if (!config) {
GTEST_SKIP() << "Test requires H.265 encode support with multiple slice support but no different slice types";
}
config.SetVideoEncodeIntraRefreshMode(VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_PER_PICTURE_PARTITION_BIT_KHR);
VideoContext context(m_device, config);
context.CreateAndBindSessionMemory();
context.CreateResources();
vkt::CommandBuffer& cb = context.CmdBuffer();
cb.Begin();
cb.BeginVideoCoding(context.Begin());
VideoEncodeInfo encode_info = context.EncodeFrame().IntraRefresh(2, 0);
auto& slice_segment_headers = encode_info.CodecInfo().encode_h265.std_slice_segment_headers;
slice_segment_headers[0].slice_type = STD_VIDEO_H265_SLICE_TYPE_I;
slice_segment_headers[1].slice_type = STD_VIDEO_H265_SLICE_TYPE_P;
cb.EncodeVideo(encode_info);
cb.EndVideoCoding(context.End());
cb.End();
}