Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions av2/common/av2_rtcd_defs.pl
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ ()
# hash
add_proto qw/uint32_t av2_get_crc32c_value/, "void *crc_calculator, uint8_t *p, size_t length";
specialize qw/av2_get_crc32c_value sse4_2/;
add_proto qw/uint64_t av2_tx_cache_hash/, "const int16_t *residual, int stride, int tx_w, int tx_h, int qindex, int txb_skip_ctx, int dc_sign_ctx";
specialize qw/av2_tx_cache_hash sse4_2/;

add_proto qw/void av2_get_horver_correlation_full/, " const int16_t *diff, int stride, int w, int h, float *hcorr, float *vcorr";
specialize qw/av2_get_horver_correlation_full sse4_1 avx2 neon/;
Expand Down
3 changes: 1 addition & 2 deletions av2/encoder/speed_features.c
Original file line number Diff line number Diff line change
Expand Up @@ -1553,8 +1553,7 @@ void av2_set_speed_features_qindex_dependent(AV2_COMP *cpi, int speed) {

// Speed 1 and fine quantizers only; assigned, not cleared, because this
// function runs per frame while the framesize-independent pass does not.
sf->tx_sf.use_tx_result_cache = (cpi->oxcf.mode == GOOD && speed == 1 &&
cpi->oxcf.rc_cfg.qp < 210 + qindex_offset);
sf->tx_sf.use_tx_result_cache = cpi->oxcf.mode == GOOD && speed >= 1;

if (cpi->oxcf.mode == GOOD && speed == 0) {
const int qindex_thresh = 124 + qindex_offset;
Expand Down
65 changes: 49 additions & 16 deletions av2/encoder/tx_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,72 @@
*/

#include <assert.h>
#include <string.h>

#include "avm/avm_integer.h"
#include "av2/encoder/tx_cache.h"

static AVM_INLINE uint64_t tx_cache_fold(uint64_t h, uint64_t v) {
#if defined(__clang__) && defined(__has_attribute)
#if __has_attribute(no_sanitize)
#define AVM_NO_UNSIGNED_OVERFLOW_CHECK \
__attribute__((no_sanitize("unsigned-integer-overflow")))
#endif
#endif

#ifndef AVM_NO_UNSIGNED_OVERFLOW_CHECK
#define AVM_NO_UNSIGNED_OVERFLOW_CHECK
#endif

AVM_NO_UNSIGNED_OVERFLOW_CHECK static AVM_INLINE uint64_t
tx_cache_fold(uint64_t h, uint64_t v) {
h ^= v;
h *= 1099511628211ULL;
return h;
}

// FNV-1a over the residual, the quantizer and the entropy contexts.
uint64_t av2_tx_cache_hash(const int16_t *residual, int stride, int tx_w,
int tx_h, int qindex, int txb_skip_ctx,
int dc_sign_ctx) {
static AVM_INLINE uint32_t crc32c_u64_c(uint32_t crc, uint64_t v) {
for (int i = 0; i < 64; ++i) {
const uint32_t bit = (crc ^ (uint32_t)v) & 1u;
crc >>= 1;
if (bit) crc ^= 0x82F63B78u;
v >>= 1;
}
return crc;
}

// Dual-stream CRC-32C over the residual, the quantizer and the entropy
// contexts.
uint64_t av2_tx_cache_hash_c(const int16_t *residual, int stride, int tx_w,
int tx_h, int qindex, int txb_skip_ctx,
int dc_sign_ctx) {
assert(stride >= tx_w); // else rows overlap and distinct residuals alias
uint64_t h = 1469598103934665603ULL;
assert((tx_w & 3) == 0);
uint32_t crc_lo = 0xFFFFFFFFu;
uint32_t crc_hi = 0x9E3779B9u;
for (int r = 0; r < tx_h; ++r) {
const int16_t *row = residual + (size_t)r * stride;
for (int c = 0; c < tx_w; ++c) {
// uint16_t first: same width, but stops sign extension smearing bits.
h = tx_cache_fold(h, (uint64_t)(uint16_t)row[c]);
for (int c = 0; c < tx_w; c += 4) {
uint64_t v;
memcpy(&v, &row[c], sizeof(v));
crc_lo = crc32c_u64_c(crc_lo, v);
crc_hi = crc32c_u64_c(crc_hi, v ^ 0xA5A5A5A5A5A5A5A5ULL);
}
}
h = tx_cache_fold(h, ((uint64_t)qindex << 32) |
((uint64_t)txb_skip_ctx << 16) | dc_sign_ctx);
return tx_cache_fold(h, ((uint64_t)tx_w << 32) | tx_h);
const uint64_t meta0 = ((uint64_t)qindex << 32) |
((uint64_t)txb_skip_ctx << 16) | (uint32_t)dc_sign_ctx;
const uint64_t meta1 = ((uint64_t)tx_w << 32) | (uint32_t)tx_h;
crc_lo = crc32c_u64_c(crc_lo, meta0);
crc_hi = crc32c_u64_c(crc_hi, meta1);
return ((uint64_t)crc_hi << 32) | crc_lo;
}

// Fold in the rest of the state that can flip which candidate wins. Only
// bounded fields are bit-packed; unbounded ones are folded separately so they
// cannot alias. The superblock coordinates are zero: the key is frame-scoped.
uint64_t av2_tx_cache_mix(uint64_t h, uint32_t sb_row, uint32_t sb_col,
int is_inter, int is_fsc, int intra_mode,
int rd_model, int skip_trellis, int tx_set_type,
int use_qmatrix, int rdmult) {
AVM_NO_UNSIGNED_OVERFLOW_CHECK uint64_t
av2_tx_cache_mix(uint64_t h, uint32_t sb_row, uint32_t sb_col, int is_inter,
int is_fsc, int intra_mode, int rd_model, int skip_trellis,
int tx_set_type, int use_qmatrix, int rdmult) {
const uint32_t flags = ((is_inter != 0) << 0) | ((is_fsc != 0) << 1) |
((skip_trellis != 0) << 2) |
((use_qmatrix != 0) << 3) | ((rd_model & 0xF) << 4) |
Expand All @@ -58,6 +89,8 @@ uint64_t av2_tx_cache_mix(uint64_t h, uint32_t sb_row, uint32_t sb_col,
return h ? h : 1;
}

#undef AVM_NO_UNSIGNED_OVERFLOW_CHECK

// Cached winning tx_type for this frame, or -1 on a miss. A never-written slot
// (tag 0) ends the probe: no store for this frame can have walked past it.
int av2_tx_cache_lookup(const TxCache *cache, uint64_t key, uint32_t frame) {
Expand Down
9 changes: 2 additions & 7 deletions av2/encoder/tx_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

#include <stdint.h>

#include "config/av2_rtcd.h"

#ifdef __cplusplus
extern "C" {
#endif
Expand All @@ -45,13 +47,6 @@ typedef struct {
TxCacheEntry entries[TX_CACHE_SIZE];
} TxCache;

/*! Hashes the residual, the quantizer and the entropy contexts into the seed
* of a cache key.
*/
uint64_t av2_tx_cache_hash(const int16_t *residual, int stride, int tx_w,
int tx_h, int qindex, int txb_skip_ctx,
int dc_sign_ctx);

/*! Folds the remaining state that can flip which candidate wins into the seed
* \c h and returns the final, never-zero cache key.
*/
Expand Down
38 changes: 38 additions & 0 deletions av2/encoder/x86/hash_sse42.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@
* aomedia.org/license/patent-license/.
*/

#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <smmintrin.h>

#include "avm/avm_integer.h"
#include "config/av2_rtcd.h"

// Byte-boundary alignment issues
#define ALIGN_SIZE 8
#define ALIGN_MASK (ALIGN_SIZE - 1)
Expand Down Expand Up @@ -50,3 +55,36 @@ uint32_t av2_get_crc32c_value_sse4_2(void *crc_calculator, uint8_t *p,
CALC_CRC(_mm_crc32_u8, crc, uint8_t, buf, len);
return (crc ^ 0xFFFFFFFF);
}

static AVM_INLINE uint32_t crc32c_u64_sse42(uint32_t crc, uint64_t v) {
#ifdef __x86_64__
return (uint32_t)_mm_crc32_u64(crc, v);
#else
crc = _mm_crc32_u32(crc, (uint32_t)v);
return _mm_crc32_u32(crc, (uint32_t)(v >> 32));
#endif
}

uint64_t av2_tx_cache_hash_sse4_2(const int16_t *residual, int stride, int tx_w,
int tx_h, int qindex, int txb_skip_ctx,
int dc_sign_ctx) {
assert(stride >= tx_w);
assert((tx_w & 3) == 0);
uint32_t crc_lo = 0xFFFFFFFFu;
uint32_t crc_hi = 0x9E3779B9u;
for (int r = 0; r < tx_h; ++r) {
const int16_t *row = residual + (size_t)r * stride;
for (int c = 0; c < tx_w; c += 4) {
uint64_t v;
memcpy(&v, &row[c], sizeof(v));
crc_lo = crc32c_u64_sse42(crc_lo, v);
crc_hi = crc32c_u64_sse42(crc_hi, v ^ 0xA5A5A5A5A5A5A5A5ULL);
}
}
const uint64_t meta0 = ((uint64_t)qindex << 32) |
((uint64_t)txb_skip_ctx << 16) | (uint32_t)dc_sign_ctx;
const uint64_t meta1 = ((uint64_t)tx_w << 32) | (uint32_t)tx_h;
crc_lo = crc32c_u64_sse42(crc_lo, meta0);
crc_hi = crc32c_u64_sse42(crc_hi, meta1);
return ((uint64_t)crc_hi << 32) | crc_lo;
}
18 changes: 18 additions & 0 deletions test/hash_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,24 @@ INSTANTIATE_TEST_SUITE_P(
SSE4_2, AV2Crc32cHashTest,
::testing::Combine(::testing::Values(&av2_get_crc32c_value_sse4_2),
::testing::ValuesIn(kValidBlockSize)));

TEST(TxCacheHashTest, CheckOutputSSE42MatchesC) {
libavm_test::ACMRandom rnd(libavm_test::ACMRandom::DeterministicSeed());
int16_t residual[64 * 64];
for (int i = 0; i < 64 * 64; ++i) {
residual[i] = static_cast<int16_t>(rnd.Rand16());
}
const int sizes[] = { 4, 8, 16, 32, 64 };
for (int tx_w : sizes) {
for (int tx_h : sizes) {
const uint64_t h_c =
av2_tx_cache_hash_c(residual, 64, tx_w, tx_h, 135, 2, 1);
const uint64_t h_sse42 =
av2_tx_cache_hash_sse4_2(residual, 64, tx_w, tx_h, 135, 2, 1);
ASSERT_EQ(h_c, h_sse42) << "Mismatch at size " << tx_w << "x" << tx_h;
}
}
}
#endif

} // namespace