use crate::{
error::{Error, Result},
DType, PixelFormat,
};
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub(crate) struct AHardwareBufferDesc {
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) layers: u32,
pub(crate) format: u32,
pub(crate) usage: u64,
pub(crate) stride: u32,
pub(crate) rfu0: u32,
pub(crate) rfu1: u64,
}
pub(crate) const USAGE_CPU_READ_OFTEN: u64 = 0x3;
pub(crate) const USAGE_CPU_READ_MASK: u64 = 0xF;
pub(crate) const USAGE_CPU_WRITE_OFTEN: u64 = 0x30;
pub(crate) const USAGE_CPU_WRITE_MASK: u64 = 0xF0;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LockDecision {
Refuse,
Covered(u64),
Unplanned(u64),
}
pub(crate) fn lock_usage_for(cpu_usage: u64, reads: bool, writes: bool) -> LockDecision {
let declared = cpu_usage & (USAGE_CPU_READ_MASK | USAGE_CPU_WRITE_MASK);
if declared == 0 {
return LockDecision::Refuse;
}
let declared_reads = cpu_usage & USAGE_CPU_READ_MASK != 0;
let declared_writes = cpu_usage & USAGE_CPU_WRITE_MASK != 0;
if (!reads || declared_reads) && (!writes || declared_writes) {
let mut bits = 0;
if reads {
bits |= cpu_usage & USAGE_CPU_READ_MASK;
}
if writes {
bits |= cpu_usage & USAGE_CPU_WRITE_MASK;
}
LockDecision::Covered(bits)
} else {
LockDecision::Unplanned(declared)
}
}
pub(crate) fn scheme_for_egl_vendor(vendor: &str) -> Option<crate::CompressionScheme> {
use crate::CompressionScheme::*;
let v = vendor.trim().to_ascii_lowercase();
if v.is_empty() || v.contains("emulation") || v.contains("angle") || v.contains("swiftshader") {
return None;
}
if v.contains("adreno") {
Some(Ubwc)
} else if v.contains("mali") || v.contains("immortalis") {
Some(Afbc)
} else if v.contains("powervr") {
Some(Pvric)
} else if v.contains("xclipse") {
Some(Dcc)
} else {
None
}
}
pub(crate) fn compression_eligible(format: PixelFormat, dtype: DType) -> bool {
matches!(format, PixelFormat::Rgba) && matches!(dtype, DType::U8 | DType::I8)
}
pub(crate) struct IdentityInternTable {
map: std::collections::HashMap<u64, (u64, std::sync::Weak<()>)>,
}
impl IdentityInternTable {
pub(crate) fn new() -> Self {
Self {
map: std::collections::HashMap::new(),
}
}
pub(crate) fn resolve(
&mut self,
key: u64,
mint: impl FnOnce() -> (u64, std::sync::Arc<()>),
) -> (u64, std::sync::Arc<()>, bool) {
if let Some((id, weak)) = self.map.get(&key) {
if let Some(guard) = weak.upgrade() {
return (*id, guard, true);
}
}
self.map.retain(|_, (_, weak)| weak.strong_count() > 0);
let (id, guard) = mint();
self.map
.insert(key, (id, std::sync::Arc::downgrade(&guard)));
(id, guard, false)
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.map.len()
}
}
pub(crate) const FORMAT_R8G8B8A8_UNORM: u32 = 1;
pub(crate) const FORMAT_R16G16B16A16_FLOAT: u32 = 0x16;
pub(crate) const FORMAT_BLOB: u32 = 0x21;
pub(crate) fn format_bpe(format: u32) -> Option<usize> {
match format {
FORMAT_R8G8B8A8_UNORM => Some(4),
FORMAT_R16G16B16A16_FLOAT => Some(8),
FORMAT_BLOB => Some(1),
_ => None,
}
}
pub(crate) fn desc_layout(desc: &AHardwareBufferDesc) -> Option<(usize, usize)> {
let bpe = format_bpe(desc.format)?;
if desc.format == FORMAT_BLOB {
let len = desc.width as usize;
return Some((len, len));
}
let bytes_per_row = (desc.stride as usize).checked_mul(bpe)?;
let buf_size = bytes_per_row.checked_mul(desc.height as usize)?;
Some((bytes_per_row, buf_size))
}
pub(crate) fn image_format_and_bpe(format: PixelFormat, dtype: DType) -> Option<(u32, usize)> {
match (format, dtype) {
(PixelFormat::Rgba, DType::U8 | DType::I8) => Some((FORMAT_R8G8B8A8_UNORM, 4)),
(PixelFormat::Rgb, DType::U8 | DType::I8) => Some((FORMAT_R8G8B8A8_UNORM, 4)),
(PixelFormat::Rgba | PixelFormat::PlanarRgb | PixelFormat::PlanarRgba, DType::F16) => {
Some((FORMAT_R16G16B16A16_FLOAT, 8))
}
_ => None,
}
}
pub(crate) fn checked_shape_bytes<T>(shape: &[usize]) -> Result<usize> {
shape
.iter()
.try_fold(1usize, |acc, &d| acc.checked_mul(d))
.and_then(|n| n.checked_mul(std::mem::size_of::<T>()))
.ok_or_else(|| {
Error::InvalidShape(format!(
"shape footprint overflows usize (shape={shape:?}, sizeof T={})",
std::mem::size_of::<T>()
))
})
}
#[cfg(test)]
mod tests {
use super::*;
fn desc(width: u32, height: u32, format: u32, stride: u32) -> AHardwareBufferDesc {
AHardwareBufferDesc {
width,
height,
layers: 1,
format,
usage: 0,
stride,
rfu0: 0,
rfu1: 0,
}
}
#[test]
fn identity_intern_reuses_live_and_remints_dead() {
use std::sync::Arc;
let mut table = IdentityInternTable::new();
let mint = |id: u64| move || (id, Arc::new(()));
let (id1, guard1, reused) = table.resolve(42, mint(100));
assert_eq!((id1, reused), (100, false));
let (id2, _guard2, reused) = table.resolve(42, mint(101));
assert_eq!((id2, reused), (100, true));
drop(guard1);
drop(_guard2);
let (id3, _guard3, reused) = table.resolve(42, mint(102));
assert_eq!((id3, reused), (102, false));
}
#[test]
fn identity_intern_prunes_dead_entries_on_insert() {
use std::sync::Arc;
let mut table = IdentityInternTable::new();
for key in 0..8u64 {
let (_, guard, _) = table.resolve(key, || (key + 100, Arc::new(())));
drop(guard);
assert_eq!(table.len(), 1);
}
let guards: Vec<_> = (100..104u64)
.map(|key| table.resolve(key, || (key, Arc::new(()))).1)
.collect();
assert_eq!(table.len(), 4);
drop(guards);
let (_, _live, _) = table.resolve(999, || (999, Arc::new(())));
assert_eq!(table.len(), 1);
}
#[test]
fn egl_vendor_classifier_table() {
use crate::CompressionScheme::*;
assert_eq!(scheme_for_egl_vendor("adreno"), Some(Ubwc));
assert_eq!(scheme_for_egl_vendor("Adreno735"), Some(Ubwc));
assert_eq!(scheme_for_egl_vendor("mali"), Some(Afbc));
assert_eq!(scheme_for_egl_vendor("immortalis-g925"), Some(Afbc));
assert_eq!(scheme_for_egl_vendor("powervr"), Some(Pvric));
assert_eq!(scheme_for_egl_vendor("xclipse"), Some(Dcc));
assert_eq!(scheme_for_egl_vendor("emulation"), None);
assert_eq!(scheme_for_egl_vendor("angle"), None);
assert_eq!(scheme_for_egl_vendor("swiftshader"), None);
assert_eq!(scheme_for_egl_vendor(""), None);
assert_eq!(scheme_for_egl_vendor("llvmpipe"), None);
}
#[test]
fn compression_eligibility_table() {
assert!(compression_eligible(PixelFormat::Rgba, DType::U8));
assert!(compression_eligible(PixelFormat::Rgba, DType::I8));
assert!(!compression_eligible(PixelFormat::Rgba, DType::F16));
assert!(!compression_eligible(PixelFormat::Rgb, DType::U8));
assert!(!compression_eligible(PixelFormat::Nv12, DType::U8));
assert!(!compression_eligible(PixelFormat::PlanarRgb, DType::U8));
}
#[test]
fn lock_usage_decision_table() {
use LockDecision::*;
const R: u64 = USAGE_CPU_READ_OFTEN;
const W: u64 = USAGE_CPU_WRITE_OFTEN;
const GPU: u64 = 0x300;
assert_eq!(lock_usage_for(GPU, true, false), Refuse);
assert_eq!(lock_usage_for(GPU, false, true), Refuse);
assert_eq!(lock_usage_for(0, true, true), Refuse);
assert_eq!(lock_usage_for(R | W | GPU, true, false), Covered(R));
assert_eq!(lock_usage_for(R | W | GPU, false, true), Covered(W));
assert_eq!(lock_usage_for(R | W | GPU, true, true), Covered(R | W));
assert_eq!(lock_usage_for(R | GPU, true, false), Covered(R));
assert_eq!(lock_usage_for(W | GPU, false, true), Covered(W));
assert_eq!(lock_usage_for(0x2 | GPU, true, false), Covered(0x2));
assert_eq!(lock_usage_for(0x20 | GPU, false, true), Covered(0x20));
assert_eq!(lock_usage_for(R | GPU, false, true), Unplanned(R));
assert_eq!(lock_usage_for(R | GPU, true, true), Unplanned(R));
assert_eq!(lock_usage_for(W | GPU, true, false), Unplanned(W));
assert_eq!(lock_usage_for(W | GPU, true, true), Unplanned(W));
}
#[test]
fn desc_matches_ndk_layout() {
assert_eq!(std::mem::size_of::<AHardwareBufferDesc>(), 40);
assert_eq!(std::mem::align_of::<AHardwareBufferDesc>(), 8);
}
#[test]
fn format_table_is_stable() {
assert_eq!(
image_format_and_bpe(PixelFormat::Rgba, DType::U8),
Some((1, 4))
);
assert_eq!(
image_format_and_bpe(PixelFormat::Rgb, DType::U8),
Some((1, 4))
);
assert_eq!(
image_format_and_bpe(PixelFormat::Rgba, DType::F16),
Some((0x16, 8))
);
assert_eq!(
image_format_and_bpe(PixelFormat::PlanarRgb, DType::F16),
Some((0x16, 8))
);
assert_eq!(
image_format_and_bpe(PixelFormat::PlanarRgba, DType::F16),
Some((0x16, 8))
);
assert_eq!(image_format_and_bpe(PixelFormat::Grey, DType::U8), None);
assert_eq!(image_format_and_bpe(PixelFormat::Nv12, DType::U8), None);
assert_eq!(image_format_and_bpe(PixelFormat::Bgra, DType::U8), None);
assert_eq!(image_format_and_bpe(PixelFormat::Yuyv, DType::U8), None);
assert_eq!(
image_format_and_bpe(PixelFormat::PlanarRgb, DType::U8),
None
);
assert_eq!(image_format_and_bpe(PixelFormat::Rgba, DType::F32), None);
}
#[test]
fn blob_layout_is_single_row() {
let (row, size) = desc_layout(&desc(4096, 1, FORMAT_BLOB, 0)).unwrap();
assert_eq!((row, size), (4096, 4096));
}
#[test]
fn image_layout_uses_allocator_stride() {
let (row, size) = desc_layout(&desc(640, 480, FORMAT_R8G8B8A8_UNORM, 704)).unwrap();
assert_eq!(row, 704 * 4);
assert_eq!(size, 704 * 4 * 480);
let (row, size) = desc_layout(&desc(160, 1920, FORMAT_R16G16B16A16_FLOAT, 160)).unwrap();
assert_eq!(row, 160 * 8);
assert_eq!(size, 160 * 8 * 1920);
}
#[test]
fn desc_layout_rejects_unknown_and_overflow() {
assert!(desc_layout(&desc(64, 64, 0x23, 64)).is_none());
assert!(desc_layout(&desc(
u32::MAX,
u32::MAX,
FORMAT_R16G16B16A16_FLOAT,
u32::MAX
))
.is_none());
}
#[test]
fn checked_shape_bytes_accepts_and_rejects() {
assert_eq!(
checked_shape_bytes::<u8>(&[480, 640, 4]).unwrap(),
1_228_800
);
assert_eq!(checked_shape_bytes::<u16>(&[2, 3]).unwrap(), 12);
assert_eq!(checked_shape_bytes::<u8>(&[]).unwrap(), 1);
assert!(checked_shape_bytes::<u8>(&[usize::MAX, 2]).is_err());
assert!(checked_shape_bytes::<u64>(&[usize::MAX / 4]).is_err());
}
}