blob: e49ee30a447405e7d9b0a04eb9f662447c8b3220 [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use rust_gtest_interop::prelude::*;
use std::simd::prelude::*;
chromium::import! {
"//ui/android:texture_compressor";
}
use texture_compressor::{compress_etc1, decompress_etc1, interleave_etc1, load_input_block};
#[gtest(TextureCompressorTest, InterleaveEtc1)]
fn test() {
let input =
[Simd::splat(0x1234), Simd::splat(0x5678), Simd::splat(0x9ABC), Simd::splat(0xDEF0)];
let expected = [Simd::splat(0x3412_7856_BC9A_F0DE); 4];
let result = interleave_etc1(input);
expect_eq!(result, expected);
}
#[gtest(TextureCompressorTest, LoadInputMirror)]
fn test() {
// Skip rustfmt to keep this formatted as a 6x2 image.
#[rustfmt::skip]
let input = [
0xFFFFFF, 0xEEEEEE, 0xDDDDDD, 0xCCCCCC, 0xBBBBBB, 0xAAAAAA,
0x999999, 0x888888, 0x777777, 0x666666, 0x555555, 0x444444,
];
let expected0 = [
[0xFF, 0xEE, 0xDD, 0xCC],
[0x99, 0x88, 0x77, 0x66],
[0x99, 0x88, 0x77, 0x66],
[0xFF, 0xEE, 0xDD, 0xCC],
];
let expected1 = [
[0xBB, 0xAA, 0xAA, 0xBB],
[0x55, 0x44, 0x44, 0x55],
[0x55, 0x44, 0x44, 0x55],
[0xBB, 0xAA, 0xAA, 0xBB],
];
let result = load_input_block(&input, 6, 2, 6, 0, 0);
for ch in 0..3 {
expect_eq!(result.map(|row| row.map(|x| x[ch].as_array()[0])), expected0);
expect_eq!(result.map(|row| row.map(|x| x[ch].as_array()[1])), expected1);
}
}
#[allow(clippy::needless_range_loop)] // Range loops seem more readable here.
#[gtest(TextureCompressorTest, LoadInputMirror1x1)]
fn test() {
let input = [0x999999];
let expected = 0x99;
let result = load_input_block(&input, 1, 1, 1, 0, 0);
for y in 0..4 {
for x in 0..4 {
for ch in 0..3 {
expect_eq!(result[y][x][ch].as_array()[0], expected);
}
}
}
}
#[gtest(TextureCompressorTest, CompressAndDecompressRoundTrip)]
fn test() {
// Test a 7x5 non-block-aligned image with padded row strides and distinct
// R, G, B values.
let width = 7u32;
let height = 5u32;
let src_row_width = 10u32;
let dst_blocks_row_width = 4u32; // padded beyond width.div_ceil(4) = 2
let decompressed_row_width = 9u32;
let mut src = vec![0u32; (src_row_width * height) as usize];
for y in 0..height {
for x in 0..width {
let luma = x * 12 + y * 8;
let r = 30 + luma;
let g = 70 + luma;
let b = 120 + luma;
src[(y * src_row_width + x) as usize] = 0xFF000000 | (b << 16) | (g << 8) | r;
}
}
let num_block_rows = height.div_ceil(4);
let mut compressed = vec![0u8; (dst_blocks_row_width * num_block_rows * 8) as usize];
compress_etc1(&src, &mut compressed, width, height, src_row_width, dst_blocks_row_width);
let sentinel = 0xDEADBEEFu32;
let mut decompressed = vec![sentinel; (decompressed_row_width * height) as usize];
decompress_etc1(
&compressed,
&mut decompressed,
width,
height,
dst_blocks_row_width,
decompressed_row_width,
);
for y in 0..height {
for x in 0..decompressed_row_width {
let actual = decompressed[(y * decompressed_row_width + x) as usize];
if x >= width {
// Out-of-bounds stride padding must not be overwritten.
expect_eq!(actual, sentinel);
} else {
let expected = src[(y * src_row_width + x) as usize];
// Alpha must be 0xFF.
expect_eq!(actual >> 24, 0xFF);
for shift in [0, 8, 16] {
let expected_ch = ((expected >> shift) & 0xFF) as i32;
let actual_ch = ((actual >> shift) & 0xFF) as i32;
expect_true!((expected_ch - actual_ch).abs() <= 20);
}
}
}
}
}
#[gtest(TextureCompressorTest, DecompressEtc1LargeStrideSingleRow)]
fn test() {
let src = [0u8; 8];
let mut dst = [0u32; 1];
decompress_etc1(&src, &mut dst, 1, 1, u32::MAX, 1);
expect_eq!(dst[0], 0xFF020202);
}
#[gtest(TextureCompressorTest, DecompressEtc1ZeroDimensions)]
fn test() {
// Zero-sized images must return cleanly without touching `dst`, even
// though `src` is empty.
let sentinel = 0xDEADBEEFu32;
let mut dst = [sentinel; 4];
decompress_etc1(&[], &mut dst, 0, 4, 1, 4);
expect_eq!(dst, [sentinel; 4]);
decompress_etc1(&[], &mut dst, 4, 0, 1, 4);
expect_eq!(dst, [sentinel; 4]);
}