一 svt_ext_all_sad_calculation_8x8_16x16_avx2函数的作用
是SVT-AV1编码其中计算运动估计绝对值差值和SAD计算,这个函数对8x8和16x16大小的块进行SAD计算,并且利用了AVX2指令集来加速计算。
函数参数解释:
src 指向原图像数据的指针
src_stride 源图像的步长,即美航像素之间的字节偏移量。
ref: 指向参考图像数据的指针
ref_stride: 参考图像的步长
mv: 运动矢量,表示当前块相对于参考块的位移
p_best_sad_8x8:指向存储最佳8x8块的SAD值的指针
p_best_sad_16x16:指向存储最佳16x16块的SAD值的指针
p_best_mv8x8 指向存储最佳8x8块对应运动矢量的指针
p_best_mv16x16 一个二维数组,用于存储多个16x16块的SAD值
p_eight_sad16x16 一个二维数组,用于存储多个16x16块的SAD值
p_eight_sad8x8 一个二维数组,用于存储多个8x8块的SAD值
sub_sad 一个布尔值,用于表示是否进行子块SAD计算。
函数的作用,在运动估计过程中,计算当前块与参考块之间的SAD值,找到最佳匹配块。对于16x16的块,会计算多个子块(8x8)的SAD值,并选择具有最小SAD值的块作为最佳匹配块。同时,它还会记录对应的最佳运动矢量。通过这种方式,编码器可以有效的确定如何在参考帧中找到当前块的最佳匹配,从而提高编码效率和压缩性能。
二 函数逐行注释
void svt_ext_all_sad_calculation_8x8_16x16_avx2(uint8_t *src, uint32_t src_stride, uint8_t *ref, uint32_t ref_stride,
uint32_t mv, uint32_t *p_best_sad_8x8, uint32_t *p_best_sad_16x16,
uint32_t *p_best_mv8x8, uint32_t *p_best_mv16x16,
uint32_t p_eight_sad16x16[16][8], uint32_t p_eight_sad8x8[64][8],
Bool sub_sad)
{
// 定义一个静态数组,用于存储16x16块的偏移量,这些偏移量用于确定16x16块在更大图像中的位置
static const char offsets[16] = {0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15};
// 外层循环,遍历16x16块的y轴方向,共4行
for (int y = 0; y < 4; y++) {
// 内层循环,遍历16x16块的x轴方向,共4列
for (int x = 0; x < 4; x++) {
// 根据当前的y和x,计算16x16块的起始位置索引
const uint32_t start_16x16_pos = offsets[4 * y + x];
// 计算对应8x8块的起始位置索引,因为一个16x16块包含4个8x8块
const uint32_t start_8x8_pos = 4 * start_16x16_pos;
// 计算当前16x16块在源图像中的起始地址
const uint8_t *s = src + 16 * y * src_stride + 16 * x;
// 计算当前16x16块在参考图像中的起始地址
const uint8_t *r = ref + 16 * y * ref_stride + 16 * x;
// 初始化两个256位的AVX寄存器,用于存储SAD计算的中间结果
__m256i sad02 = _mm256_setzero_si256();
__m256i sad13 = _mm256_setzero_si256();
// 如果sub_sad为真,表示需要进行子块的SAD计算
if (sub_sad) {
// 循环4次,处理16x16块中的每个4x4子块
for (int i = 0; i < 4; i++) {
// 从源图像中加载两个128位的16像素数据(两个8x8块的行数据)
const __m128i src01 = _mm_loadu_si128((__m128i *)(s + 0 * src_stride));
const __m128i src23 = _mm_loadu_si128((__m128i *)(s + 8 * src_stride));
// 从参考图像中加载四个128位的16像素数据,分别对应四个8x8块的行数据
const __m128i ref0 = _mm_loadu_si128((__m128i *)(r + 0 * ref_stride + 0));
const __m128i ref1 = _mm_loadu_si128((__m128i *)(r + 0 * ref_stride + 8));
const __m128i ref2 = _mm_loadu_si128((__m128i *)(r + 8 * ref_stride + 0));
const __m128i ref3 = _mm_loadu_si128((__m128i *)(r + 8 * ref_stride + 8));
// 将两个128位的源数据合并成一个256位的源数据
const __m256i src0123 = _mm256_insertf128_si256(_mm256_castsi128_si256(src01), src23, 1);
// 将两个128位的参考数据合并成一个256位的参考数据(ref0和ref2)
const __m256i ref02 = _mm256_insertf128_si256(_mm256_castsi128_si256(ref0), ref2, 1);
// 将两个128位的参考数据合并成一个256位的参考数据(ref1和ref3)
const __m256i ref13 = _mm256_insertf128_si256(_mm256_castsi128_si256(ref1), ref3, 1);
// 使用AVX指令计算ref02和src0123之间的SAD值,并累加到sad02中,模式为000 000
sad02 = _mm256_adds_epu16(sad02, _mm256_mpsadbw_epu8(ref02, src0123, 0));
// 再次计算ref02和src0123之间的SAD值,模式为101 101,并累加到sad02中
sad02 = _mm256_adds_epu16(sad02, _mm256_mpsadbw_epu8(ref02, src0123, 45));
// 计算ref13和src0123之间的SAD值,模式为010 010,并累加到sad13中
sad13 = _mm256_adds_epu16(sad13, _mm256_mpsadbw_epu8(ref13, src0123, 18));
// 再次计算ref13和src0123之间的SAD值,模式为111 111,并累加到sad13中
sad13 = _mm256_adds_epu16(sad13, _mm256_mpsadbw_epu8(ref13, src0123, 63));
// 更新源和参考图像的指针,移动到下一行的对应位置
s += 2 * src_stride;
r += 2 * ref_stride;
}
// 将sad02中的值左移1位,相当于乘以2,进行数据调整
sad02 = _mm256_slli_epi16(sad02, 1);
// 将sad13中的值左移1位,相当于乘以2,进行数据调整
sad13 = _mm256_slli_epi16(sad13, 1);
} else {
// 循环8次,处理16x16块中的每个8x8块
for (int i = 0; i < 8; i++) {
// 从源图像中加载两个128位的16像素数据(两个8x8块的行数据)
const __m128i src01 = _mm_loadu_si128((__m128i *)(s + 0 * src_stride));
const __m128i src23 = _mm_loadu_si128((__m128i *)(s + 8 * src_stride));
// 从参考图像中加载四个128位的16像素数据,分别对应四个8x8块的行数据
const __m128i ref0 = _mm_loadu_si128((__m128i *)(r + 0 * ref_stride + 0));
const __m128i ref1 = _mm_loadu_si128((__m128i *)(r + 0 * ref_stride + 8));
const __m128i ref2 = _mm_loadu_si128((__m128i *)(r + 8 * ref_stride + 0));
const __m128i ref3 = _mm_loadu_si128((__m128i *)(r + 8 * ref_stride + 8));
// 将两个128位的源数据合并成一个256位的源数据
const __m256i src0123 = _mm256_insertf128_si256(_mm256_castsi128_si256(src01), src23, 1);
// 将两个128位的参考数据合并成一个256位的参考数据(ref0和ref2)
const __m256i ref02 = _mm256_insertf128_si256(_mm256_castsi128_si256(ref0), ref2, 1);
// 将两个128位的参考数据合并成一个256位的参考数据(ref1和ref3)
const __m256i ref13 = _mm256_insertf128_si256(_mm256_castsi128_si256(ref1), ref3, 1);
// 使用AVX指令计算ref02和src0123之间的SAD值,并累加到sad02中,模式为000 000
sad02 = _mm256_adds_epu16(sad02, _mm256_mpsadbw_epu8(ref02, src0123, 0));
// 再次计算ref02和src0123之间的SAD值,模式为101 101,并累加到sad02中
sad02 = _mm256_adds_epu16(sad02, _mm256_mpsadbw_epu8(ref02, src0123, 45));
// 计算ref13和src0123之间的SAD值,模式为010 010,并累加到sad13中
sad13 = _mm256_adds_epu16(sad13, _mm256_mpsadbw_epu8(ref13, src0123, 18));
// 再次计算ref13和src0123之间的SAD值,模式为111 111,并累加到sad13中
sad13 = _mm256_adds_epu16(sad13, _mm256_mpsadbw_epu8(ref13, src0123, 63));
// 更新源和参考图像的指针,移动到下一行的对应位置
s += src_stride;
r += ref_stride;
}
}
// 忽略p_eight_sad8x8参数,可能用于其他功能
(void)p_eight_sad8x8;
// 将sad02的低128位部分提取出来,存储到sad0中
const __m128i sad0 = _mm256_castsi256_si128(sad02);
// 将sad13的低128位部分提取出来,存储到sad1中
const __m128i sad1 = _mm256_castsi256_si128(sad13);
// 将sad02的高128位部分提取出来,存储到sad2中
const __m128i sad2 = _mm256_extracti128_si256(sad02, 1);
// 将sad13的高128位部分提取出来,存储到sad3中
const __m128i sad3 = _mm256_extracti128_si256(sad13, 1);
// 对sad0中的每个16位元素,找到最小值及其位置索引,存储到minpos0中
const __m128i minpos0 = _mm_minpos_epu16(sad0);
// 对sad1中的每个16位元素,找到最小值及其位置索引,存储到minpos1中
const __m128i minpos1 = _mm_minpos_epu16(sad1);
// 对sad2中的每个16位元素,找到最小值及其位置索引,存储到minpos2中
const __m128i minpos2 = _mm_minpos_epu16(sad2);
// 对sad3中的每个16位元素,找到最小值及其位置索引,存储到minpos3中
const __m128i minpos3 = _mm_minpos_epu16(sad3);
// 将minpos0和minpos1的低16位元素组合成一个新的128位数据
const __m128i minpos01 = _mm_unpacklo_epi16(minpos0, minpos1);
// 将minpos2和minpos3的低16位元素组合成一个新的128位数据
const __m128i minpos23 = _mm_unpacklo_epi16(minpos2, minpos3);
// 将minpos01和minpos23的低32位元素组合成一个新的128位数据
const __m128i minpos0123 = _mm_unpacklo_epi32(minpos01, minpos23);
// 提取minpos0123中的SAD值部分,存储到sad8x8中
const __m128i sad8x8 = _mm_unpacklo_epi16(minpos0123, _mm_setzero_si128());
// 提取minpos0123中的位置索引部分,存储到pos8x8中
const __m128i pos8x8 = _mm_unpackhi_epi16(minpos0123, _mm_setzero_si128());
// 加载当前8x8块的最佳SAD值
__m128i best_sad8x8 = _mm_loadu_si128((__m128i *)(p_best_sad_8x8 + start_8x8_pos));
// 比较当前计算的sad8x8和已有的最佳SAD值,生成掩码
const __m128i mask = _mm_cmplt_epi32(sad8x8, best_sad8x8);
// 更新最佳SAD值,取当前计算值和已有值中的较小者
best_sad8x8 = _mm_min_epi32(best_sad8x8, sad8x8);
// 将更新后的最佳SAD值存储回p_best_sad_8x8数组中
_mm_storeu_si128((__m128i *)(p_best_sad_8x8 + start_8x8_pos), best_sad8x8);
// 设置运动矢量mv的128位数据
const __m128i mvs = _mm_set1_epi32(mv);
// 加载当前8x8块的最佳运动矢量
__m128i best_mv8x8 = _mm_loadu_si128((__m128i *)(p_best_mv8x8 + start_8x8_pos));
// 根据位置索引计算当前块的运动矢量
const __m128i mv8x8 = _mm_add_epi16(mvs, pos8x8);
// 根据掩码更新最佳运动矢量,当当前SAD更小时使用新的运动矢量
best_mv8x8 = _mm_blendv_epi8(best_mv8x8, mv8x8, mask);
// 将更新后的最佳运动矢量存储回p_best_mv8x8数组中
_mm_storeu_si128((__m128i *)(p_best_mv8x8 + start_8x8_pos), best_mv8x8);
// 计算sad0和sad1的和,得到sum01
const __m128i sum01 = _mm_add_epi16(sad0, sad1);
// 计算sad2和sad3的和,得到sum23
const __m128i sum23 = _mm_add_epi16(sad2, sad3);
// 计算sum01和sum23的和,得到16x16块的SAD值
const __m128i sad16x16_16 = _mm_add_epi16(sum01, sum23);
// 将16位的SAD值扩展为32位,便于后续处理
const __m256i sad16x16_32 = _mm256_cvtepu16_epi32(sad16x16_16);
// 将计算得到的16x16块的SAD值存储到p_eight_sad16x16数组中
_mm256_storeu_si256((__m256i *)(p_eight_sad16x16[start_16x16_pos]), sad16x16_32);
// 对sad16x16_16中的每个16位元素,找到最小值及其位置索引,存储到minpos16x16中
const __m128i minpos16x16 = _mm_minpos_epu16(sad16x16_16);
// 提取最小值
const uint32_t min16x16 = _mm_extract_epi16(minpos16x16, 0);
// 如果当前计算的最小SAD值小于已有的最佳SAD值,则更新
if (min16x16 < p_best_sad_16x16[start_16x16_pos]) {
// 更新最佳SAD值
p_best_sad_16x16[start_16x16_pos] = min16x16;
// 提取位置索引部分
const __m128i pos16x16 = _mm_srli_si128(minpos16x16, 2);
// 计算对应的运动矢量
const __m128i mv16x16 = _mm_add_epi16(mvs, pos16x16);
// 更新最佳运动矢量
p_best_mv16x16[start_16x16_pos] = _mm_extract_epi32(mv16x16, 0);
}
}
}
}