Commit 05acd5cd25 for aom
commit 05acd5cd25af0d4de6d41ba143f1a29d5ec89dd7
Author: Ihor Buhaiov <freedbrt@gmail.com>
Date: Mon Nov 2 12:17:18 2026 +0100
Hash IntraBC blocks up to 8x8 in one pass
The IntraBC hash table build made one pass over the tile per block
size. The 2x2 pass wrote a value for every pixel into a frame sized
buffer, the 4x4 pass read it and wrote a second one, and the 8x8 pass
read that and wrote the first one again. Each buffer has 4 bytes per
pixel, 15 MB at 2560x1440, so most of the time went into memory
traffic.
Do the 2x2, 4x4 and 8x8 steps in one pass, row by row. A 4x4 hash only
needs the 2x2 rows 0 and 2 below it, and an 8x8 hash the 4x4 rows 0
and 4 below it. Keep only the last 3 rows of 2x2 values and 5 rows of
4x4 hashes, 80 KB at 2560 wide, and write only the 8x8 hashes to the
frame buffer. When the 4x4 table is built too, the 4x4 hashes go to
the second frame buffer instead of the ring. The sizes above 8x8 keep
their passes, which start from the 8x8 hashes. The second buffer is
now only allocated when the 4x4 table or the sizes above 8x8 need it,
so RT speed 11 allocates one instead of two.
av1_combine_block_hashes() now hashes one row and takes the two source
rows as separate pointers, since the rows of a ring buffer are not a
fixed stride apart.
The hashes, the tables and the stream don't change. Packet hashes
match before and after for RT speed 11 and all intra speed 6 and 2, 8
bit and 10 bit, 4:2:0 and 4:4:4, 1 and 4 threads with 2x2 tiles, and
the encoder and decoder reconstructions match. A new unit test checks
the pass against av1_get_block_hash_value() on regions of odd sizes
and offsets.
Timing on an Apple M3 Pro at RT speed 11 with IntraBC on, every frame
a key frame, 4 screen recordings from 1920x1080 to 2560x1440, 10
interleaved rounds of 10 frames, medians per clip. The table build is
the hash passes plus the fill. Key frames are CPU time at 1 thread and
wall time at 4 threads with 2x2 tiles. Less time than before, as
ranges over the clips:
4:2:0 4:4:4
table build 1 thread 7.4 to 14.1% 8.4 to 11.0%
table build 4 threads 8.8 to 20.0% 8.4 to 21.3%
key frame 4 threads 0.2 to 5.8% 0.1 to 3.8%
The build saves 0.8 to 1.6 ms per key frame at 1 thread. The key frame
CPU time at 1 thread is within the noise of the runs, between 1.3%
less and 0.9% more for 4:2:0 and up to 1.7% less for 4:4:4.
Bug: 576320193
Change-Id: I1b640ab0c42e7643f6b38d569263e14f2218d6d3
diff --git a/av1/common/av1_rtcd_defs.pl b/av1/common/av1_rtcd_defs.pl
index b71c347764..b0851bb0da 100644
--- a/av1/common/av1_rtcd_defs.pl
+++ b/av1/common/av1_rtcd_defs.pl
@@ -467,7 +467,7 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
# hash
add_proto qw/uint32_t av1_get_crc32c_value/, "void *crc_calculator, const uint8_t *p, size_t length";
specialize qw/av1_get_crc32c_value sse4_2 arm_crc32/;
- add_proto qw/void av1_combine_block_hashes/, "void *crc_calculator, const uint32_t *src, uint32_t *dst, int stride, int width, int height, int sub_size";
+ add_proto qw/void av1_combine_block_hashes/, "void *crc_calculator, const uint32_t *top, const uint32_t *bottom, uint32_t *dst, int width, int sub_size";
specialize qw/av1_combine_block_hashes sse4_2 arm_crc32/;
if (aom_config("CONFIG_REALTIME_ONLY") ne "yes") {
diff --git a/av1/encoder/arm/hash_arm_crc32.c b/av1/encoder/arm/hash_arm_crc32.c
index 6b2baec2dd..18fc79229f 100644
--- a/av1/encoder/arm/hash_arm_crc32.c
+++ b/av1/encoder/arm/hash_arm_crc32.c
@@ -62,21 +62,16 @@ uint32_t av1_get_crc32c_value_arm_crc32(void *crc_calculator, const uint8_t *p,
}
void av1_combine_block_hashes_arm_crc32(void *crc_calculator,
- const uint32_t *src, uint32_t *dst,
- int stride, int width, int height,
- int sub_size) {
+ const uint32_t *top,
+ const uint32_t *bottom, uint32_t *dst,
+ int width, int sub_size) {
(void)crc_calculator;
- for (int y = 0; y < height; y++) {
- const uint32_t *top = src + y * stride;
- const uint32_t *bottom = top + sub_size * stride;
- uint32_t *out = dst + y * stride;
- for (int x = 0; x < width; x++) {
- // Hash the 4 values from registers, in the same byte order as the C
- // version reads them from memory.
- uint32_t crc = 0xFFFFFFFF;
- crc = __crc32cd(crc, top[x] | (uint64_t)top[x + sub_size] << 32);
- crc = __crc32cd(crc, bottom[x] | (uint64_t)bottom[x + sub_size] << 32);
- out[x] = ~crc;
- }
+ for (int x = 0; x < width; x++) {
+ // Hash the 4 values from registers, in the same byte order as the C
+ // version reads them from memory.
+ uint32_t crc = 0xFFFFFFFF;
+ crc = __crc32cd(crc, top[x] | (uint64_t)top[x + sub_size] << 32);
+ crc = __crc32cd(crc, bottom[x] | (uint64_t)bottom[x + sub_size] << 32);
+ dst[x] = ~crc;
}
}
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c
index 072d730c87..8b0a1addec 100644
--- a/av1/encoder/encodeframe.c
+++ b/av1/encoder/encodeframe.c
@@ -2131,6 +2131,19 @@ static void set_intrabc_hash_table_grid(const CommonTileParams *tiles,
(tiles->rows + tile_rows_per_table - 1) / tile_rows_per_table;
}
+// Returns the largest block size that is hashed for intrabc.
+static int get_max_intrabc_hash_size(const AV1_COMP *cpi) {
+ const int sb_size =
+ 1 << (cpi->common.seq_params->mib_size_log2 + MI_SIZE_LOG2);
+ return cpi->sf.mv_sf.hash_max_8x8_intrabc_blocks ? AOMMIN(8, sb_size)
+ : sb_size;
+}
+
+// Returns true if the 4x4 hashes go in a hash table.
+static bool has_intrabc_hash_table_4x4(const AV1_COMMON *cm) {
+ return block_size_wide[cm->mi_params.mi_alloc_bsize] <= 4;
+}
+
bool av1_build_intrabc_hash_table(AV1_COMP *cpi, int table_idx) {
const AV1_COMMON *const cm = &cpi->common;
IntraBCHashInfo *const intrabc_hash_info = &cpi->td.mb.intrabc_hash_info;
@@ -2147,27 +2160,34 @@ bool av1_build_intrabc_hash_table(AV1_COMP *cpi, int table_idx) {
IntraBCHashRegion region;
get_intrabc_hash_region(cpi->source, cm, intrabc_hash_info, table_idx,
®ion);
+ hash_table *const table = &intrabc_hash_info->tile_tables[table_idx];
- av1_generate_block_2x2_hash_value(cpi->source, ®ion, block_hash_values[0],
- hash_stride);
// Hash data generated for screen contents is used for intraBC ME
- const int min_alloc_size = block_size_wide[cm->mi_params.mi_alloc_bsize];
- int max_sb_size = 1 << (cm->seq_params->mib_size_log2 + MI_SIZE_LOG2);
-
- if (cpi->sf.mv_sf.hash_max_8x8_intrabc_blocks) {
- max_sb_size = AOMMIN(8, max_sb_size);
+ uint32_t *const hash_4x4 =
+ has_intrabc_hash_table_4x4(cm) ? block_hash_values[1] : NULL;
+ if (!av1_generate_block_8x8_hash_value(intrabc_hash_info, cpi->source,
+ ®ion, hash_4x4,
+ block_hash_values[0], hash_stride)) {
+ return false;
+ }
+ if (hash_4x4 != NULL && !av1_add_to_hash_map_by_row_with_precal_data(
+ table, hash_4x4, hash_stride, ®ion, 4)) {
+ return false;
}
+ const int min_alloc_size = block_size_wide[cm->mi_params.mi_alloc_bsize];
+ const int max_size = get_max_intrabc_hash_size(cpi);
int src_idx = 0;
- for (int size = 4; size <= max_sb_size; size *= 2, src_idx = !src_idx) {
- const int dst_idx = !src_idx;
- av1_generate_block_hash_value(intrabc_hash_info, ®ion, size,
- block_hash_values[src_idx],
- block_hash_values[dst_idx], hash_stride);
+ for (int size = 8; size <= max_size; size *= 2) {
+ if (size > 8) {
+ av1_generate_block_hash_value(intrabc_hash_info, ®ion, size,
+ block_hash_values[src_idx],
+ block_hash_values[!src_idx], hash_stride);
+ src_idx = !src_idx;
+ }
if (size >= min_alloc_size &&
!av1_add_to_hash_map_by_row_with_precal_data(
- &intrabc_hash_info->tile_tables[table_idx],
- block_hash_values[dst_idx], hash_stride, ®ion, size)) {
+ table, block_hash_values[src_idx], hash_stride, ®ion, size)) {
return false;
}
}
@@ -2192,8 +2212,14 @@ static void build_intrabc_hash_tables(AV1_COMP *cpi) {
intrabc_hash_info->table_cols * intrabc_hash_info->table_rows;
bool error = false;
+ // The second buffer is only needed for the 4x4 hashes and the sizes above
+ // 8x8.
+ const bool needs_second_buffer =
+ has_intrabc_hash_table_4x4(cm) || get_max_intrabc_hash_size(cpi) > 8;
+ const int num_buffers = needs_second_buffer ? 2 : 1;
+
free_block_hash_buffers(intrabc_hash_info);
- for (int j = 0; j < 2; ++j) {
+ for (int j = 0; j < num_buffers; ++j) {
intrabc_hash_info->frame_hash_buffer[j] = (uint32_t *)aom_malloc(
sizeof(*intrabc_hash_info->frame_hash_buffer[j]) * hash_stride *
pic_height);
diff --git a/av1/encoder/hash.c b/av1/encoder/hash.c
index 6fae8465c7..fe911dae68 100644
--- a/av1/encoder/hash.c
+++ b/av1/encoder/hash.c
@@ -72,25 +72,20 @@ uint32_t av1_get_crc32c_value_c(void *c, const uint8_t *buf, size_t len) {
return (uint32_t)crc ^ 0xffffffff;
}
-// For each of the width x height positions, hashes the 4 sub-block hashes at
-// that position and at the offsets sub_size right, sub_size down and both.
-// src and dst use the same stride.
-void av1_combine_block_hashes_c(void *crc_calculator, const uint32_t *src,
- uint32_t *dst, int stride, int width,
- int height, int sub_size) {
- for (int y = 0; y < height; y++) {
- const uint32_t *top = src + y * stride;
- const uint32_t *bottom = top + sub_size * stride;
- uint32_t *out = dst + y * stride;
- for (int x = 0; x < width; x++) {
- const uint32_t v[4] = { top[x], top[x + sub_size], bottom[x],
- bottom[x + sub_size] };
- // Hash a byte copy so the static analyzer can follow the byte reads.
- // TODO: bug aomedia:433531610 - serialize input values in a way that's
- // independent of the computer architecture's endianness
- uint8_t p[sizeof(v)];
- memcpy(p, v, sizeof(v));
- out[x] = av1_get_crc32c_value_c(crc_calculator, p, sizeof(p));
- }
+// For each of the width positions in a row, hashes the 4 sub-block hashes at
+// that position and sub_size to the right of it, in top and then in bottom.
+// The two rows are separate pointers so they can come from a ring buffer.
+void av1_combine_block_hashes_c(void *crc_calculator, const uint32_t *top,
+ const uint32_t *bottom, uint32_t *dst,
+ int width, int sub_size) {
+ for (int x = 0; x < width; x++) {
+ const uint32_t v[4] = { top[x], top[x + sub_size], bottom[x],
+ bottom[x + sub_size] };
+ // Hash a byte copy so the static analyzer can follow the byte reads.
+ // TODO: bug aomedia:433531610 - serialize input values in a way that's
+ // independent of the computer architecture's endianness
+ uint8_t p[sizeof(v)];
+ memcpy(p, v, sizeof(v));
+ dst[x] = av1_get_crc32c_value_c(crc_calculator, p, sizeof(p));
}
}
diff --git a/av1/encoder/hash_motion.c b/av1/encoder/hash_motion.c
index eaf9633e8f..f20c221305 100644
--- a/av1/encoder/hash_motion.c
+++ b/av1/encoder/hash_motion.c
@@ -204,48 +204,97 @@ const block_hash *av1_hash_get_first_block_hash(const hash_table *p_hash_table,
return &block_hashes[p_hash_table->bucket_start[size_index][bucket]];
}
-void av1_generate_block_2x2_hash_value(const YV12_BUFFER_CONFIG *picture,
- const IntraBCHashRegion *region,
- uint32_t *pic_block_hash,
- int pic_block_hash_stride) {
- const int width = 2;
- const int height = 2;
- const int x_end = region->x_end - width + 1;
- const int y_end = region->y_end - height + 1;
-
+// Hashes width 2x2 blocks in a row, the first one at (x, y).
+static void hash_2x2_row(const YV12_BUFFER_CONFIG *picture, int x, int y,
+ int width, uint32_t *dst) {
+ const int stride = picture->y_stride;
if (picture->flags & YV12_FLAG_HIGHBITDEPTH) {
- uint16_t p[4];
- for (int y_pos = region->y_start; y_pos < y_end; y_pos++) {
- uint32_t *const row_hash = pic_block_hash + y_pos * pic_block_hash_stride;
- for (int x_pos = region->x_start; x_pos < x_end; x_pos++) {
- get_pixels_in_1D_short_array_by_block_2x2(
- CONVERT_TO_SHORTPTR(picture->y_buffer) + y_pos * picture->y_stride +
- x_pos,
- picture->y_stride, p);
- // For HBD, we either have 40 or 48 bits of input data that the xor hash
- // reduce to 32 bits. We intentionally don't want to "discard" bits to
- // avoid any kind of biasing.
- row_hash[x_pos] = get_xor_hash_value_hbd(p[0], p[1], p[2], p[3]);
- }
+ const uint16_t *const top =
+ CONVERT_TO_SHORTPTR(picture->y_buffer) + y * stride + x;
+ const uint16_t *const bottom = top + stride;
+ for (int i = 0; i < width; i++) {
+ // For HBD, we either have 40 or 48 bits of input data that the xor hash
+ // reduce to 32 bits. We intentionally don't want to "discard" bits to
+ // avoid any kind of biasing.
+ dst[i] =
+ get_xor_hash_value_hbd(top[i], top[i + 1], bottom[i], bottom[i + 1]);
}
} else {
- uint8_t p[4];
- for (int y_pos = region->y_start; y_pos < y_end; y_pos++) {
- uint32_t *const row_hash = pic_block_hash + y_pos * pic_block_hash_stride;
- for (int x_pos = region->x_start; x_pos < x_end; x_pos++) {
- get_pixels_in_1D_char_array_by_block_2x2(
- picture->y_buffer + y_pos * picture->y_stride + x_pos,
- picture->y_stride, p);
- // This 2x2 hash isn't used directly as a "key" for the hash table, so
- // we can afford to just copy the 4 8-bit pixel values as a single
- // 32-bit value directly. (i.e. there are no concerns of a lack of
- // uniform distribution)
- row_hash[x_pos] = get_identity_hash_value(p[0], p[1], p[2], p[3]);
- }
+ const uint8_t *const top = picture->y_buffer + y * stride + x;
+ const uint8_t *const bottom = top + stride;
+ for (int i = 0; i < width; i++) {
+ // This 2x2 hash isn't used directly as a "key" for the hash table, so
+ // we can afford to just copy the 4 8-bit pixel values as a single
+ // 32-bit value directly. (i.e. there are no concerns of a lack of
+ // uniform distribution)
+ dst[i] =
+ get_identity_hash_value(top[i], top[i + 1], bottom[i], bottom[i + 1]);
}
}
}
+// A 4x4 hash at row y reads the 2x2 rows y and y + 2, and an 8x8 hash the 4x4
+// rows y and y + 4. The pass keeps only the rows it still needs.
+#define kRingRows2x2 3
+#define kRingRows4x4 5
+
+bool av1_generate_block_8x8_hash_value(IntraBCHashInfo *intrabc_hash_info,
+ const YV12_BUFFER_CONFIG *picture,
+ const IntraBCHashRegion *region,
+ uint32_t *hash_4x4, uint32_t *hash_8x8,
+ int pic_block_hash_stride) {
+ const int x_start = region->x_start;
+ const int y_start = region->y_start;
+ const int width = region->x_end - x_start;
+ const int height = region->y_end - y_start;
+ // Source blocks at the right and bottom borders cannot be part of larger
+ // blocks, so each block size has fewer positions than the one below it.
+ const int width_4x4 = width - 3;
+ const int height_4x4 = height - 3;
+ const int width_8x8 = width - 7;
+ // Without the 4x4 table only 8x8 hashes are written.
+ const int min_size = hash_4x4 != NULL ? 4 : 8;
+ if (width < min_size || height < min_size) return true;
+
+ // The 4x4 rows only need the ring when hash_4x4 does not hold them.
+ const int ring_rows = kRingRows2x2 + (hash_4x4 != NULL ? 0 : kRingRows4x4);
+ uint32_t *const ring =
+ (uint32_t *)aom_malloc(sizeof(*ring) * width * ring_rows);
+ if (ring == NULL) return false;
+ uint32_t *const rows_2x2 = ring;
+ // The 4x4 rows go to hash_4x4 instead when the caller needs all of them.
+ uint32_t *rows_4x4 = ring + kRingRows2x2 * width;
+ int rows_4x4_stride = width;
+ int num_rows_4x4 = kRingRows4x4;
+ if (hash_4x4 != NULL) {
+ rows_4x4 = hash_4x4 + y_start * pic_block_hash_stride + x_start;
+ rows_4x4_stride = pic_block_hash_stride;
+ num_rows_4x4 = height_4x4;
+ }
+ uint32_t *const out_8x8 =
+ hash_8x8 + y_start * pic_block_hash_stride + x_start;
+ CRC32C *const calc = &intrabc_hash_info->crc_calculator;
+
+ hash_2x2_row(picture, x_start, y_start, width - 1, rows_2x2);
+ hash_2x2_row(picture, x_start, y_start + 1, width - 1, rows_2x2 + width);
+ for (int y = 0; y < height_4x4; y++) {
+ uint32_t *const next_2x2 = rows_2x2 + (y + 2) % kRingRows2x2 * width;
+ hash_2x2_row(picture, x_start, y_start + y + 2, width - 1, next_2x2);
+ uint32_t *const row_4x4 = rows_4x4 + y % num_rows_4x4 * rows_4x4_stride;
+ av1_combine_block_hashes(calc, rows_2x2 + y % kRingRows2x2 * width,
+ next_2x2, row_4x4, width_4x4, 2);
+ if (y >= 4 && width_8x8 > 0) {
+ const uint32_t *const top_4x4 =
+ rows_4x4 + (y - 4) % num_rows_4x4 * rows_4x4_stride;
+ av1_combine_block_hashes(calc, top_4x4, row_4x4,
+ out_8x8 + (y - 4) * pic_block_hash_stride,
+ width_8x8, 4);
+ }
+ }
+ aom_free(ring);
+ return true;
+}
+
void av1_generate_block_hash_value(IntraBCHashInfo *intrabc_hash_info,
const IntraBCHashRegion *region,
int block_size,
@@ -255,16 +304,19 @@ void av1_generate_block_hash_value(IntraBCHashInfo *intrabc_hash_info,
const int x_end = region->x_end - block_size + 1;
const int y_end = region->y_end - block_size + 1;
if (x_end <= region->x_start || y_end <= region->y_start) return;
- const int offset = region->y_start * pic_block_hash_stride + region->x_start;
+ const int sub_size = block_size >> 1;
// Build up a bigger block from 4 smaller, non-overlapping source block
// hashes, and compute its hash. Note: source blocks at the right and
// bottom borders cannot be part of larger blocks, therefore they won't be
// considered into the block hash value generation process.
- av1_combine_block_hashes(
- &intrabc_hash_info->crc_calculator, src_pic_block_hash + offset,
- dst_pic_block_hash + offset, pic_block_hash_stride,
- x_end - region->x_start, y_end - region->y_start, block_size >> 1);
+ for (int y_pos = region->y_start; y_pos < y_end; y_pos++) {
+ const int pos = y_pos * pic_block_hash_stride + region->x_start;
+ av1_combine_block_hashes(
+ &intrabc_hash_info->crc_calculator, src_pic_block_hash + pos,
+ src_pic_block_hash + pos + sub_size * pic_block_hash_stride,
+ dst_pic_block_hash + pos, x_end - region->x_start, sub_size);
+ }
}
// Adds the blocks of one block size that lie inside region to the table. The
diff --git a/av1/encoder/hash_motion.h b/av1/encoder/hash_motion.h
index c7b14593e0..1c2af68abc 100644
--- a/av1/encoder/hash_motion.h
+++ b/av1/encoder/hash_motion.h
@@ -82,9 +82,12 @@ typedef struct intrabc_hash_info {
int tile_cols_per_table;
int tile_rows_per_table;
// Hash values of every position of the frame, only needed while the tables
- // are built (two buffers, used alternately). Each buffer has
- // frame_hash_width * frame_hash_height elements, the luma size of the source
- // frame they were allocated for. Both sizes are 0 when the buffers are freed.
+ // are built. The first buffer holds the 8x8 hashes. The second one holds the
+ // 4x4 hashes when the 4x4 table is built, and the two are used alternately
+ // for the sizes above 8x8. It is NULL when neither is needed. Both cover
+ // the luma size of the source frame they were allocated for,
+ // frame_hash_width x frame_hash_height. Both sizes are 0 when the buffers
+ // are freed.
uint32_t *frame_hash_buffer[2];
int frame_hash_width;
int frame_hash_height;
@@ -115,10 +118,15 @@ const block_hash *av1_hash_get_first_block_hash(const hash_table *p_hash_table,
uint32_t hash_value);
// These fill the hash values of the blocks that lie inside region. The stride
// parameters give the distance in values between rows of the hash buffers.
-void av1_generate_block_2x2_hash_value(const YV12_BUFFER_CONFIG *picture,
+// av1_generate_block_8x8_hash_value() hashes the picture into hash_8x8, and
+// into hash_4x4 too unless it is NULL. It returns false if it runs out of
+// memory.
+bool av1_generate_block_8x8_hash_value(IntraBCHashInfo *intrabc_hash_info,
+ const YV12_BUFFER_CONFIG *picture,
const IntraBCHashRegion *region,
- uint32_t *pic_block_hash,
+ uint32_t *hash_4x4, uint32_t *hash_8x8,
int pic_block_hash_stride);
+// Hashes block_size blocks from the block_size / 2 hashes in src.
void av1_generate_block_hash_value(IntraBCHashInfo *intra_bc_hash_info,
const IntraBCHashRegion *region,
int block_size,
diff --git a/av1/encoder/x86/hash_sse42.c b/av1/encoder/x86/hash_sse42.c
index 1f7d3181a3..31551e3417 100644
--- a/av1/encoder/x86/hash_sse42.c
+++ b/av1/encoder/x86/hash_sse42.c
@@ -53,31 +53,25 @@ uint32_t av1_get_crc32c_value_sse4_2(void *crc_calculator, const uint8_t *p,
return (crc ^ 0xFFFFFFFF);
}
-void av1_combine_block_hashes_sse4_2(void *crc_calculator, const uint32_t *src,
- uint32_t *dst, int stride, int width,
- int height, int sub_size) {
+void av1_combine_block_hashes_sse4_2(void *crc_calculator, const uint32_t *top,
+ const uint32_t *bottom, uint32_t *dst,
+ int width, int sub_size) {
(void)crc_calculator;
- for (int y = 0; y < height; y++) {
- const uint32_t *top = src + y * stride;
- const uint32_t *bottom = top + sub_size * stride;
- uint32_t *out = dst + y * stride;
- for (int x = 0; x < width; x++) {
- // Hash the 4 values from registers, in the same byte order as the C
- // version reads them from memory.
+ for (int x = 0; x < width; x++) {
+ // Hash the 4 values from registers, in the same byte order as the C
+ // version reads them from memory.
#if AOM_ARCH_X86_64
- uint64_t crc = 0xFFFFFFFF;
- crc = _mm_crc32_u64(crc, top[x] | (uint64_t)top[x + sub_size] << 32);
- crc =
- _mm_crc32_u64(crc, bottom[x] | (uint64_t)bottom[x + sub_size] << 32);
- out[x] = (uint32_t)crc ^ 0xFFFFFFFF;
+ uint64_t crc = 0xFFFFFFFF;
+ crc = _mm_crc32_u64(crc, top[x] | (uint64_t)top[x + sub_size] << 32);
+ crc = _mm_crc32_u64(crc, bottom[x] | (uint64_t)bottom[x + sub_size] << 32);
+ dst[x] = (uint32_t)crc ^ 0xFFFFFFFF;
#else
- uint32_t crc = 0xFFFFFFFF;
- crc = _mm_crc32_u32(crc, top[x]);
- crc = _mm_crc32_u32(crc, top[x + sub_size]);
- crc = _mm_crc32_u32(crc, bottom[x]);
- crc = _mm_crc32_u32(crc, bottom[x + sub_size]);
- out[x] = crc ^ 0xFFFFFFFF;
+ uint32_t crc = 0xFFFFFFFF;
+ crc = _mm_crc32_u32(crc, top[x]);
+ crc = _mm_crc32_u32(crc, top[x + sub_size]);
+ crc = _mm_crc32_u32(crc, bottom[x]);
+ crc = _mm_crc32_u32(crc, bottom[x + sub_size]);
+ dst[x] = crc ^ 0xFFFFFFFF;
#endif
- }
}
}
diff --git a/test/hash_test.cc b/test/hash_test.cc
index e481203145..1f50f06afc 100644
--- a/test/hash_test.cc
+++ b/test/hash_test.cc
@@ -18,7 +18,9 @@
#include "config/av1_rtcd.h"
#include "aom_ports/aom_timer.h"
+#include "aom_ports/mem.h"
#include "av1/encoder/hash.h"
+#include "av1/encoder/hash_motion.h"
#include "gtest/gtest.h"
#include "test/acm_random.h"
#include "test/util.h"
@@ -140,8 +142,9 @@ INSTANTIATE_TEST_SUITE_P(
#endif
using combine_block_hashes_func = void (*)(void *crc_calculator,
- const uint32_t *src, uint32_t *dst,
- int stride, int width, int height,
+ const uint32_t *top,
+ const uint32_t *bottom,
+ uint32_t *dst, int width,
int sub_size);
class AV1CombineBlockHashesTest
@@ -156,23 +159,25 @@ class AV1CombineBlockHashesTest
return static_cast<uint32_t>(rnd_.Rand16()) << 16 | rnd_.Rand16();
}
- // Compares the whole output buffer with the C version, so writes outside
- // the width x height area show up too. flat fills src with few values, like
- // a screen with large flat areas.
- void RunCheckOutput(int width, int height, int sub_size, bool flat) {
- const int stride = width + 2 * sub_size + 3;
- const int rows = height + 2 * sub_size;
- std::vector<uint32_t> src(stride * rows);
- for (uint32_t &v : src) {
- v = flat ? rnd_.Rand8() % 2 : Rand32();
+ // Compares the whole output row with the C version, so writes past width
+ // show up too. The two source rows are separate buffers, like rows of a ring
+ // buffer. flat fills them with few values, like a screen with large flat
+ // areas.
+ void RunCheckOutput(int width, int sub_size, bool flat) {
+ const int size = width + sub_size + 3;
+ std::vector<uint32_t> top(size);
+ std::vector<uint32_t> bottom(size);
+ for (int i = 0; i < size; ++i) {
+ top[i] = flat ? rnd_.Rand8() % 2 : Rand32();
+ bottom[i] = flat ? rnd_.Rand8() % 2 : Rand32();
}
- std::vector<uint32_t> ref(stride * rows, 0xDEADBEEF);
- std::vector<uint32_t> out(stride * rows, 0xDEADBEEF);
- av1_combine_block_hashes_c(&calc_, src.data(), ref.data(), stride, width,
- height, sub_size);
- GetParam()(&calc_, src.data(), out.data(), stride, width, height, sub_size);
- ASSERT_EQ(ref, out) << "width " << width << " height " << height
- << " sub_size " << sub_size << " flat " << flat;
+ std::vector<uint32_t> ref(size, 0xDEADBEEF);
+ std::vector<uint32_t> out(size, 0xDEADBEEF);
+ av1_combine_block_hashes_c(&calc_, top.data(), bottom.data(), ref.data(),
+ width, sub_size);
+ GetParam()(&calc_, top.data(), bottom.data(), out.data(), width, sub_size);
+ ASSERT_EQ(ref, out) << "width " << width << " sub_size " << sub_size
+ << " flat " << flat;
}
libaom_test::ACMRandom rnd_;
@@ -182,12 +187,11 @@ class AV1CombineBlockHashesTest
TEST_P(AV1CombineBlockHashesTest, CheckOutput) {
const int kSubSizes[] = { 2, 4, 8, 16, 32, 64 };
const int kWidths[] = { 0, 1, 7, 64, 129 };
- const int kHeights[] = { 1, 5, 33 };
for (int sub_size : kSubSizes) {
for (int width : kWidths) {
- for (int height : kHeights) {
- RunCheckOutput(width, height, sub_size, false);
- RunCheckOutput(width, height, sub_size, true);
+ for (int i = 0; i < 5; ++i) {
+ RunCheckOutput(width, sub_size, false);
+ RunCheckOutput(width, sub_size, true);
}
}
}
@@ -212,7 +216,11 @@ TEST_P(AV1CombineBlockHashesTest, DISABLED_Speed) {
aom_usec_timer timer;
aom_usec_timer_start(&timer);
for (int j = 0; j < repeat; ++j) {
- impls[i](&calc_, src.data(), dst.data(), stride, width, rows, sub_size);
+ for (int y = 0; y < rows; ++y) {
+ const uint32_t *top = src.data() + y * stride;
+ impls[i](&calc_, top, top + sub_size * stride, dst.data() + y * stride,
+ width, sub_size);
+ }
}
aom_usec_timer_mark(&timer);
time[i] = static_cast<double>(aom_usec_timer_elapsed(&timer));
@@ -235,4 +243,85 @@ INSTANTIATE_TEST_SUITE_P(
::testing::Values(&av1_combine_block_hashes_arm_crc32));
#endif
+// Checks the frame pass for 4x4 and 8x8 blocks against
+// av1_get_block_hash_value(), which hashes one block from its pixels, the way
+// the search does. The regions cover the right and bottom borders, offsets and
+// regions too small for any block.
+TEST(AV1BlockHashValueTest, MatchesSingleBlockHash) {
+ libaom_test::ACMRandom rnd(libaom_test::ACMRandom::DeterministicSeed());
+ const int kWidth = 75;
+ const int kHeight = 41;
+ const int kStride = kWidth + 5;
+ const int kHashStride = kWidth + 9;
+ const uint32_t kUnset = 0xDEADBEEF;
+ const IntraBCHashRegion kRegions[] = {
+ { 0, 0, kWidth, kHeight },
+ { 3, 5, 10, 12 },
+ { 0, 0, 4, 4 },
+ { 1, 2, 8, 8 },
+ { 5, 7, 12, 40 },
+ { 64, 30, kWidth, kHeight },
+ { 0, 0, 7, kHeight },
+ { 2, 0, kWidth, 3 },
+ { 9, 9, 9, 20 },
+ };
+
+ IntraBCHashInfo info = {};
+ av1_hash_table_init(&info);
+ std::vector<uint32_t> buf0(AOM_BUFFER_SIZE_FOR_BLOCK_HASH);
+ std::vector<uint32_t> buf1(AOM_BUFFER_SIZE_FOR_BLOCK_HASH);
+ info.hash_value_buffer[0] = buf0.data();
+ info.hash_value_buffer[1] = buf1.data();
+
+ std::vector<uint8_t> pixels8(kStride * kHeight);
+ std::vector<uint16_t> pixels16(kStride * kHeight);
+ for (int hbd = 0; hbd <= 1; ++hbd) {
+ // Half of the pixels share one value, like the flat areas of a screen.
+ for (int i = 0; i < kStride * kHeight; ++i) {
+ const bool flat = rnd.Rand8() < 128;
+ pixels8[i] = flat ? 7 : rnd.Rand8();
+ pixels16[i] = flat ? 7 : rnd.Rand16() & 0x3ff;
+ }
+ YV12_BUFFER_CONFIG pic = {};
+ pic.y_stride = kStride;
+ if (hbd) {
+ pic.y_buffer = CONVERT_TO_BYTEPTR(pixels16.data());
+ pic.flags = YV12_FLAG_HIGHBITDEPTH;
+ } else {
+ pic.y_buffer = pixels8.data();
+ }
+
+ for (const IntraBCHashRegion ®ion : kRegions) {
+ for (int with_4x4 = 0; with_4x4 <= 1; ++with_4x4) {
+ std::vector<uint32_t> hash_4x4(kHashStride * kHeight, kUnset);
+ std::vector<uint32_t> hash_8x8(kHashStride * kHeight, kUnset);
+ ASSERT_TRUE(av1_generate_block_8x8_hash_value(
+ &info, &pic, ®ion, with_4x4 ? hash_4x4.data() : nullptr,
+ hash_8x8.data(), kHashStride));
+ for (int size = 4; size <= 8; size *= 2) {
+ const std::vector<uint32_t> &hashes = size == 4 ? hash_4x4 : hash_8x8;
+ for (int y = 0; y < kHeight; ++y) {
+ for (int x = 0; x < kWidth; ++x) {
+ const bool inside = x >= region.x_start && y >= region.y_start &&
+ x + size <= region.x_end &&
+ y + size <= region.y_end;
+ uint32_t expected = kUnset;
+ if (inside && (size == 8 || with_4x4)) {
+ uint32_t hash_value1;
+ av1_get_block_hash_value(&info, pic.y_buffer + y * kStride + x,
+ kStride, size, &hash_value1, &expected,
+ hbd);
+ }
+ ASSERT_EQ(expected, hashes[y * kHashStride + x])
+ << "hbd " << hbd << " size " << size << " x " << x << " y "
+ << y << " region " << region.x_start << "," << region.y_start
+ << " " << region.x_end << "," << region.y_end;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
} // namespace