blob: 94bfea5e550464e6003653f8be523bd13bf8c561 [file]
// Copyright 2020 Google LLC
//
// 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
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// Test color caches.
//
// Author: Maryla (maryla@google.com)
#include <cstdint>
#include <limits>
#include <numeric>
#include <vector>
#include "src/common/lossless/color_cache.h"
#include "src/utils/random.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "tests/include/helpers.h"
namespace WP2L {
namespace {
TEST(HashColorCache, Simple) {
HashColorCache cache;
ASSERT_TRUE(cache.Allocate(/*hash_bits=*/5));
const int16_t kBlack[] = {255, 0, 0, 0};
const int16_t kGreen[] = {255, 0, 225, 0};
const int16_t kRed[] = {255, 255, 0, 0};
uint32_t index;
EXPECT_FALSE(cache.Contains(kBlack, &index));
EXPECT_FALSE(cache.Contains(kGreen, &index));
EXPECT_FALSE(cache.Contains(kRed, &index));
// Insert black.
EXPECT_TRUE(cache.Insert(kBlack, &index));
const uint32_t black_index = index;
int16_t color[4];
cache.Lookup(black_index, color);
EXPECT_THAT(color, testing::ElementsAreArray(kBlack));
EXPECT_TRUE(cache.Contains(kBlack, &index));
EXPECT_FALSE(cache.Contains(kGreen, &index));
EXPECT_FALSE(cache.Contains(kRed, &index));
// Insert green.
EXPECT_TRUE(cache.Insert(kGreen, &index));
const uint32_t green_index = index;
const bool black_was_removed = (green_index == black_index);
cache.Lookup(green_index, color);
EXPECT_THAT(color, testing::ElementsAreArray(kGreen));
EXPECT_TRUE(cache.Contains(kGreen, &index));
EXPECT_EQ(cache.Contains(kBlack, &index), black_index != green_index);
if (!black_was_removed) {
cache.Lookup(black_index, color);
EXPECT_THAT(color, testing::ElementsAreArray(kBlack));
}
// Re insert black, check the index hasn't changed.
EXPECT_EQ(cache.Insert(kBlack, &index), black_was_removed);
EXPECT_EQ(black_index, index);
// Insert red.
EXPECT_TRUE(cache.Insert(kRed, &index));
const uint32_t red_index = index;
cache.Lookup(red_index, color);
EXPECT_THAT(color, testing::ElementsAreArray(kRed));
EXPECT_EQ(cache.Contains(kBlack, &index), red_index != black_index);
EXPECT_EQ(cache.Contains(kGreen, &index), red_index != green_index);
}
// Tests that the cache API is consistent: e.g. a color that is present
// can be retrieved, a color that was just inserted is present, etc.
static void TestColorCacheAPI(uint32_t num_colors, ColorCache* const cache,
WP2::UniformIntDistribution* const random) {
const uint32_t num_pixels = num_colors * 10;
std::vector<int16_t> colors(num_colors * 4);
for (uint32_t i = 0; i < colors.size(); ++i) {
colors[i] = random->Get(0, 255);
}
std::vector<bool> color_seen(num_colors, false);
std::vector<uint32_t> color_indices(num_pixels);
const uint32_t kNotInCache = std::numeric_limits<uint32_t>::max();
std::vector<uint32_t> cache_indices(num_pixels, kNotInCache);
// Simulates encoding stage.
for (uint32_t i = 0; i < num_pixels; ++i) {
const uint32_t color_index = random->Get<uint32_t>(0, num_colors - 1);
color_indices[i] = color_index;
const int16_t* const color = &colors[color_index * 4];
// If we've never seen this color, it shouldn't be in the cache
// (but it's not necessarily in the cache even if we've seen it before).
const bool present = cache->Contains(color, &cache_indices[i]);
if (!color_seen[color_index]) {
EXPECT_FALSE(present);
}
color_seen[color_index] = true;
int16_t lookup[4];
if (present) {
cache->Lookup(cache_indices[i], lookup);
EXPECT_THAT(lookup,
testing::ElementsAre(color[0], color[1], color[2], color[3]));
}
uint32_t insert_index;
const bool inserted = cache->Insert(color, &insert_index);
EXPECT_NE(inserted, present);
if (present) {
EXPECT_EQ(insert_index, cache_indices[i]);
}
// A color that was just inserted is in the cache.
uint32_t new_index;
EXPECT_TRUE(cache->Contains(color, &new_index));
cache->Lookup(new_index, lookup);
EXPECT_THAT(lookup,
testing::ElementsAre(color[0], color[1], color[2], color[3]));
}
cache->Reset();
// Simulates decoding stage.
for (uint32_t i = 0; i < num_pixels; ++i) {
const int16_t* const expected_color = &colors[color_indices[i] * 4];
if (cache_indices[i] == kNotInCache) {
uint32_t cache_index;
EXPECT_FALSE(cache->Contains(expected_color, &cache_index));
} else {
uint32_t cache_index;
EXPECT_TRUE(cache->Contains(expected_color, &cache_index));
int16_t lookup[4];
cache->Lookup(cache_indices[i], lookup);
EXPECT_THAT(lookup,
testing::ElementsAre(expected_color[0], expected_color[1],
expected_color[2], expected_color[3]));
}
cache->Insert(expected_color, nullptr);
}
}
TEST(HashColorCache, Random) {
for (uint32_t bits = 1; bits < 5; ++bits) {
WP2::UniformIntDistribution random;
HashColorCache cache;
ASSERT_TRUE(cache.Allocate(bits));
const uint32_t num_colors = random.Get(1 << (bits - 1), 1 << (bits + 1));
TestColorCacheAPI(num_colors, &cache, &random);
}
}
TEST(FifoColorCache, Simple) {
FifoColorCache cache;
ASSERT_TRUE(cache.Allocate(/*cache_bits=*/2));
int16_t kColors[5 * 4] = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2,
2, 2, 3, 3, 3, 3, 4, 4, 4, 4};
uint32_t index = 0;
EXPECT_FALSE(cache.Contains(&kColors[4 * 0], &index));
EXPECT_TRUE(cache.Insert(&kColors[4 * 0], &index));
EXPECT_EQ(cache.IndexRange(), 1u);
EXPECT_TRUE(cache.Insert(&kColors[4 * 1], &index));
EXPECT_TRUE(cache.Contains(&kColors[4 * 1], &index));
EXPECT_EQ(index, 0u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 0], &index));
EXPECT_EQ(index, 1u);
EXPECT_EQ(cache.IndexRange(), 2u);
EXPECT_FALSE(cache.Insert(&kColors[4 * 0], &index));
EXPECT_EQ(index, 1u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 0], &index));
EXPECT_EQ(index, 0u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 1], &index));
EXPECT_EQ(index, 1u);
EXPECT_EQ(cache.IndexRange(), 2u);
EXPECT_TRUE(cache.Insert(&kColors[4 * 2], &index));
EXPECT_TRUE(cache.Contains(&kColors[4 * 2], &index));
EXPECT_EQ(index, 0u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 0], &index));
EXPECT_EQ(index, 1u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 1], &index));
EXPECT_EQ(index, 2u);
EXPECT_EQ(cache.IndexRange(), 3u);
EXPECT_TRUE(cache.Insert(&kColors[4 * 3], &index));
EXPECT_TRUE(cache.Contains(&kColors[4 * 3], &index));
EXPECT_EQ(index, 0u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 2], &index));
EXPECT_EQ(index, 1u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 0], &index));
EXPECT_EQ(index, 2u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 1], &index));
EXPECT_EQ(index, 3u);
EXPECT_EQ(cache.IndexRange(), 4u);
EXPECT_FALSE(cache.Insert(&kColors[4 * 1], &index));
EXPECT_EQ(index, 3u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 1], &index));
EXPECT_EQ(index, 0u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 3], &index));
EXPECT_EQ(index, 1u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 2], &index));
EXPECT_EQ(index, 2u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 0], &index));
EXPECT_EQ(index, 3u);
EXPECT_EQ(cache.IndexRange(), 4u);
EXPECT_TRUE(cache.Insert(&kColors[4 * 4], &index));
EXPECT_TRUE(cache.Contains(&kColors[4 * 4], &index));
EXPECT_EQ(index, 0u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 1], &index));
EXPECT_EQ(index, 1u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 3], &index));
EXPECT_EQ(index, 2u);
EXPECT_TRUE(cache.Contains(&kColors[4 * 2], &index));
EXPECT_EQ(index, 3u);
EXPECT_FALSE(cache.Contains(&kColors[4 * 0], &index));
EXPECT_EQ(cache.IndexRange(), 4u);
}
TEST(FifoColorCache, Random) {
for (uint32_t bits = 1; bits < 5; ++bits) {
WP2::UniformIntDistribution random;
FifoColorCache cache;
ASSERT_TRUE(cache.Allocate(bits));
const uint32_t num_colors = random.Get(1 << (bits - 1), 1 << (bits + 1));
TestColorCacheAPI(num_colors, &cache, &random);
}
}
TEST(HybridColorCache, Random) {
FifoColorCache fifo_cache;
const uint32_t fifo_bits = 2;
ASSERT_TRUE(fifo_cache.Allocate(fifo_bits));
HashColorCache hash_cache;
const uint32_t hash_bits = 8;
ASSERT_TRUE(hash_cache.Allocate(hash_bits));
const uint32_t bits = fifo_bits + hash_bits;
HybridColorCache hybrid_cache;
hybrid_cache.Init(&fifo_cache, &hash_cache);
WP2::UniformIntDistribution random;
const uint32_t num_colors = random.Get(1 << (bits - 1), 1 << (bits + 1));
TestColorCacheAPI(num_colors, &hybrid_cache, &random);
}
TEST(MoveToFrontCache, Simple) {
MoveToFrontCache cache;
ASSERT_WP2_OK(cache.Init(/*enabled=*/true, /*num_colors=*/4));
EXPECT_EQ(cache.GetIndex(0), 0);
EXPECT_EQ(cache.GetIndex(1), 1);
EXPECT_EQ(cache.GetIndex(2), 2);
EXPECT_EQ(cache.GetIndex(3), 3);
EXPECT_EQ(cache.GetColor(0), 0);
EXPECT_EQ(cache.GetColor(1), 1);
EXPECT_EQ(cache.GetColor(2), 2);
EXPECT_EQ(cache.GetColor(3), 3);
cache.MoveToFront(2);
EXPECT_EQ(cache.GetIndex(2), 0);
EXPECT_EQ(cache.GetIndex(0), 1);
EXPECT_EQ(cache.GetIndex(1), 2);
EXPECT_EQ(cache.GetIndex(3), 3);
EXPECT_EQ(cache.GetColor(0), 2);
EXPECT_EQ(cache.GetColor(1), 0);
EXPECT_EQ(cache.GetColor(2), 1);
EXPECT_EQ(cache.GetColor(3), 3);
for (uint32_t i = 0; i < 2; ++i) {
cache.MoveToFront(3);
EXPECT_EQ(cache.GetIndex(3), 0);
EXPECT_EQ(cache.GetIndex(2), 1);
EXPECT_EQ(cache.GetIndex(0), 2);
EXPECT_EQ(cache.GetIndex(1), 3);
EXPECT_EQ(cache.GetColor(0), 3);
EXPECT_EQ(cache.GetColor(1), 2);
EXPECT_EQ(cache.GetColor(2), 0);
EXPECT_EQ(cache.GetColor(3), 1);
}
cache.MoveToFront(0);
EXPECT_EQ(cache.GetIndex(0), 0);
EXPECT_EQ(cache.GetIndex(3), 1);
EXPECT_EQ(cache.GetIndex(2), 2);
EXPECT_EQ(cache.GetIndex(1), 3);
EXPECT_EQ(cache.GetColor(0), 0);
EXPECT_EQ(cache.GetColor(1), 3);
EXPECT_EQ(cache.GetColor(2), 2);
EXPECT_EQ(cache.GetColor(3), 1);
}
TEST(MoveToFrontCache, RandomTest) {
constexpr uint32_t kNumColors = 100;
for (bool enabled : {true, false}) {
std::vector<uint16_t> colors(kNumColors);
std::iota(colors.begin(), colors.end(), 0);
WP2::Shuffle(colors.begin(), colors.end(), /*seed=*/0);
MoveToFrontCache cache;
ASSERT_WP2_OK(cache.Init(enabled, kNumColors));
for (uint32_t i = 0; i < kNumColors; ++i) {
cache.MoveToFront(colors[kNumColors - i - 1]);
}
for (uint32_t i = 0; i < kNumColors; ++i) {
if (enabled) {
EXPECT_EQ(cache.GetIndex(colors[i]), i);
EXPECT_EQ(cache.GetColor(i), colors[i]);
} else {
EXPECT_EQ(cache.GetIndex(i), i);
EXPECT_EQ(cache.GetColor(i), i);
}
}
}
}
} // namespace
} // namespace WP2L