use crate::PixelFormat;
use crate::Rational;
use std::sync::Arc;
use crate::backend::{BackendKind, GpuContextLike, YuvTransform};
use crate::error::{Error, Result};
use crate::frame::VideoParamsPod;
#[derive(Clone, Debug, PartialEq)]
pub struct Frame {
pub width: i32,
pub height: i32,
pub format: PixelFormat,
pub channels: i32,
pub timestamp: Rational,
pub data: Vec<u8>,
pub params: VideoParamsPod,
}
impl Default for Frame {
fn default() -> Self {
Self {
width: 0,
height: 0,
format: PixelFormat::Invalid,
channels: 0,
timestamp: Rational::NULL,
data: Vec::new(),
params: VideoParamsPod::default(),
}
}
}
impl Frame {
pub fn new() -> Self {
Self::default()
}
pub fn dummy() -> Self {
Self {
width: 0,
height: 0,
format: PixelFormat::F32,
channels: VideoParamsPod::INTERNAL_CHANNEL_COUNT,
timestamp: Rational::new(0, 1),
data: Vec::new(),
params: VideoParamsPod::default(),
}
}
pub fn bytes_per_channel(&self) -> usize {
self.format.bytes_per_channel()
}
pub fn linesize_bytes(&self) -> usize {
(self.width as usize)
.saturating_mul(self.channels as usize)
.saturating_mul(self.bytes_per_channel())
}
pub fn allocated_size(&self) -> usize {
(self.height as usize).saturating_mul(self.linesize_bytes())
}
pub fn is_allocated(&self) -> bool {
!self.data.is_empty()
}
pub fn set_video_params(&mut self, pod: VideoParamsPod) {
self.params = pod;
self.width = pod.effective_width();
self.height = pod.effective_height();
self.format = match pod.format {
f if f == PixelFormat::U8 as i32 => PixelFormat::U8,
f if f == PixelFormat::U10 as i32 => PixelFormat::U10,
f if f == PixelFormat::U16 as i32 => PixelFormat::U16,
f if f == PixelFormat::F16 as i32 => PixelFormat::F16,
f if f == PixelFormat::F32 as i32 => PixelFormat::F32,
_ => PixelFormat::Invalid,
};
self.channels = VideoParamsPod::INTERNAL_CHANNEL_COUNT;
}
pub fn video_params(&self) -> VideoParamsPod {
let mut p = self.params;
p.width = self.width;
p.height = self.height;
p.format = self.format as i32;
p
}
pub fn allocate(&mut self) -> bool {
if self.width <= 0 || self.height <= 0 || self.channels <= 0 {
return false;
}
let size = self.allocated_size();
if size == 0 {
return false;
}
if self.data.len() != size {
self.data = vec![0u8; size];
} else {
self.data.fill(0);
}
true
}
pub fn data(&self) -> *const u8 {
self.data.as_ptr()
}
pub fn data_mut(&mut self) -> *mut u8 {
self.data.as_mut_ptr()
}
pub fn is_dummy(&self) -> bool {
self.width == 0 && self.height == 0 && self.data.is_empty()
}
pub fn is_float(&self) -> bool {
matches!(self.format, PixelFormat::F16 | PixelFormat::F32)
}
pub fn pixel_count(&self) -> usize {
(self.width as usize).saturating_mul(self.height as usize)
}
}
pub struct GpuLease {
token: u64,
ctx: Arc<dyn GpuContextLike>,
owned: bool,
}
impl GpuLease {
pub fn new(ctx: Arc<dyn GpuContextLike>, token: u64) -> Arc<Self> {
Arc::new(Self { token, ctx, owned: true })
}
pub fn borrowed(ctx: Arc<dyn GpuContextLike>, token: u64) -> Arc<Self> {
Arc::new(Self { token, ctx, owned: false })
}
pub fn token(&self) -> u64 {
self.token
}
}
impl Drop for GpuLease {
fn drop(&mut self) {
if self.owned {
self.ctx.destroy_texture(self.token);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ColorModel {
YuvPlanar,
YuvSemiPlanar,
Rgb,
Rgba,
}
impl ColorModel {
pub fn is_yuv(self) -> bool {
matches!(self, ColorModel::YuvPlanar | ColorModel::YuvSemiPlanar)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Subsampling {
Cs420,
Cs422,
Cs444,
}
impl Subsampling {
pub fn chroma_size(self, width: i32, height: i32) -> (i32, i32) {
if width <= 0 || height <= 0 {
return (0, 0);
}
match self {
Subsampling::Cs420 => ((width + 1) / 2, (height + 1) / 2),
Subsampling::Cs422 => ((width + 1) / 2, height),
Subsampling::Cs444 => (width, height),
}
}
pub fn uv_scale(self) -> (f32, f32) {
match self {
Subsampling::Cs420 => (0.5, 0.5),
Subsampling::Cs422 => (0.5, 1.0),
Subsampling::Cs444 => (1.0, 1.0),
}
}
pub fn from_chroma_size(
uv_width: i32,
uv_height: i32,
width: i32,
height: i32,
) -> Option<Self> {
if width <= 0 || height <= 0 {
return None;
}
if (uv_width, uv_height) == (width, height) {
Some(Subsampling::Cs444)
} else if (uv_width, uv_height) == ((width + 1) / 2, height) {
Some(Subsampling::Cs422)
} else if (uv_width, uv_height) == ((width + 1) / 2, (height + 1) / 2) {
Some(Subsampling::Cs420)
} else {
None
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PlaneStorage {
U8,
U16,
F16,
F32,
}
impl PlaneStorage {
pub fn bytes_per_component(self) -> usize {
match self {
PlaneStorage::U8 => 1,
PlaneStorage::U16 | PlaneStorage::F16 => 2,
PlaneStorage::F32 => 4,
}
}
pub fn bit_depth(self) -> u32 {
match self {
PlaneStorage::U8 => 8,
PlaneStorage::U16 | PlaneStorage::F16 => 16,
PlaneStorage::F32 => 32,
}
}
pub fn name(self) -> &'static str {
match self {
PlaneStorage::U8 => "u8",
PlaneStorage::U16 => "u16",
PlaneStorage::F16 => "f16",
PlaneStorage::F32 => "f32",
}
}
pub fn texture_format(self) -> wgpu::TextureFormat {
match self {
PlaneStorage::U8 => wgpu::TextureFormat::R8Unorm,
PlaneStorage::U16 => wgpu::TextureFormat::R16Unorm,
PlaneStorage::F16 => wgpu::TextureFormat::R16Float,
PlaneStorage::F32 => wgpu::TextureFormat::R32Float,
}
}
pub fn from_texture_format(format: wgpu::TextureFormat) -> Option<Self> {
use wgpu::TextureFormat as F;
match format {
F::R8Unorm | F::Rg8Unorm | F::Rgba8Unorm => Some(PlaneStorage::U8),
F::R16Unorm | F::Rg16Unorm | F::Rgba16Unorm => Some(PlaneStorage::U16),
F::R16Float | F::Rg16Float | F::Rgba16Float => Some(PlaneStorage::F16),
F::R32Float | F::Rg32Float | F::Rgba32Float => Some(PlaneStorage::F32),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PlanarDescriptor {
pub model: ColorModel,
pub subsampling: Subsampling,
pub storage: PlaneStorage,
}
impl PlanarDescriptor {
pub const fn new(model: ColorModel, subsampling: Subsampling, storage: PlaneStorage) -> Self {
Self {
model,
subsampling,
storage,
}
}
pub const fn yuv_planar(subsampling: Subsampling, storage: PlaneStorage) -> Self {
Self::new(ColorModel::YuvPlanar, subsampling, storage)
}
pub const fn yuv_semi_planar(subsampling: Subsampling, storage: PlaneStorage) -> Self {
Self::new(ColorModel::YuvSemiPlanar, subsampling, storage)
}
pub const fn rgb(storage: PlaneStorage) -> Self {
Self::new(ColorModel::Rgb, Subsampling::Cs444, storage)
}
pub const fn rgba(storage: PlaneStorage) -> Self {
Self::new(ColorModel::Rgba, Subsampling::Cs444, storage)
}
pub fn plane_count(self) -> usize {
match self.model {
ColorModel::YuvPlanar => 3,
ColorModel::YuvSemiPlanar => 2,
ColorModel::Rgb | ColorModel::Rgba => 1,
}
}
pub fn plane_source_components(self, plane: usize) -> Option<usize> {
match self.model {
ColorModel::YuvPlanar => (plane < 3).then_some(1),
ColorModel::YuvSemiPlanar => match plane {
0 => Some(1),
1 => Some(2),
_ => None,
},
ColorModel::Rgb => (plane == 0).then_some(3),
ColorModel::Rgba => (plane == 0).then_some(4),
}
}
pub fn plane_texture_components(self, plane: usize) -> Option<usize> {
match self.model {
ColorModel::Rgb | ColorModel::Rgba => (plane == 0).then_some(4),
_ => self.plane_source_components(plane),
}
}
pub fn plane_texture_format(self, plane: usize) -> Option<wgpu::TextureFormat> {
let components = self.plane_texture_components(plane)?;
Some(texture_format_for(self.storage, components))
}
pub fn plane_size(self, plane: usize, width: i32, height: i32) -> Option<(i32, i32)> {
if width <= 0 || height <= 0 || plane >= self.plane_count() {
return None;
}
match self.model {
ColorModel::YuvPlanar | ColorModel::YuvSemiPlanar if plane > 0 => {
Some(self.subsampling.chroma_size(width, height))
}
_ => Some((width, height)),
}
}
pub fn bit_depth(self) -> u32 {
self.storage.bit_depth()
}
pub fn uv_scale(self) -> (f32, f32) {
if self.model.is_yuv() {
self.subsampling.uv_scale()
} else {
(1.0, 1.0)
}
}
pub fn chroma_size(self, width: i32, height: i32) -> (i32, i32) {
if self.model.is_yuv() {
self.subsampling.chroma_size(width, height)
} else if width > 0 && height > 0 {
(width, height)
} else {
(0, 0)
}
}
pub fn is_yuv(self) -> bool {
self.model.is_yuv()
}
pub fn legacy_format(self) -> Option<PlanarFormat> {
[
PlanarFormat::Nv12,
PlanarFormat::P010,
PlanarFormat::Yuv422P10,
]
.into_iter()
.find(|format| format.descriptor() == self)
}
pub fn name(self) -> String {
let model = match self.model {
ColorModel::YuvPlanar => "yuv-planar",
ColorModel::YuvSemiPlanar => "yuv-semi-planar",
ColorModel::Rgb => "rgb",
ColorModel::Rgba => "rgba",
};
let sub = match self.model {
ColorModel::YuvPlanar | ColorModel::YuvSemiPlanar => match self.subsampling {
Subsampling::Cs420 => "-420",
Subsampling::Cs422 => "-422",
Subsampling::Cs444 => "-444",
},
_ => "",
};
format!("{model}{sub}-{}", self.storage.name())
}
}
fn texture_format_for(storage: PlaneStorage, components: usize) -> wgpu::TextureFormat {
use wgpu::TextureFormat as F;
match (components, storage) {
(1, PlaneStorage::U8) => F::R8Unorm,
(1, PlaneStorage::U16) => F::R16Unorm,
(1, PlaneStorage::F16) => F::R16Float,
(1, PlaneStorage::F32) => F::R32Float,
(2, PlaneStorage::U8) => F::Rg8Unorm,
(2, PlaneStorage::U16) => F::Rg16Unorm,
(2, PlaneStorage::F16) => F::Rg16Float,
(2, PlaneStorage::F32) => F::Rg32Float,
(4, PlaneStorage::U8) => F::Rgba8Unorm,
(4, PlaneStorage::U16) => F::Rgba16Unorm,
(4, PlaneStorage::F16) => F::Rgba16Float,
(4, PlaneStorage::F32) => F::Rgba32Float,
_ => unreachable!("unsupported planar component count {components}"),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PlanarFormat {
Nv12,
P010,
Yuv422P10,
}
impl PlanarFormat {
pub fn descriptor(self) -> PlanarDescriptor {
match self {
PlanarFormat::Nv12 => {
PlanarDescriptor::yuv_semi_planar(Subsampling::Cs420, PlaneStorage::U8)
}
PlanarFormat::P010 => {
PlanarDescriptor::yuv_semi_planar(Subsampling::Cs420, PlaneStorage::U16)
}
PlanarFormat::Yuv422P10 => {
PlanarDescriptor::yuv_semi_planar(Subsampling::Cs422, PlaneStorage::U16)
}
}
}
pub fn y_format(self) -> wgpu::TextureFormat {
self.descriptor()
.plane_texture_format(0)
.expect("a planar format always has a luma plane")
}
pub fn uv_format(self) -> wgpu::TextureFormat {
self.descriptor()
.plane_texture_format(1)
.expect("a legacy planar format always has a chroma plane")
}
pub fn name(self) -> &'static str {
match self {
PlanarFormat::Nv12 => "nv12",
PlanarFormat::P010 => "p010",
PlanarFormat::Yuv422P10 => "yuv422p10",
}
}
pub fn bit_depth(self) -> u32 {
self.descriptor().bit_depth()
}
pub fn bytes_per_component(self) -> usize {
self.descriptor().storage.bytes_per_component()
}
pub fn chroma_size(self, width: i32, height: i32) -> (i32, i32) {
self.descriptor().chroma_size(width, height)
}
pub fn uv_scale(self) -> (f32, f32) {
self.descriptor().uv_scale()
}
pub fn is_422(self) -> bool {
matches!(self, PlanarFormat::Yuv422P10)
}
}
impl From<PlanarFormat> for PlanarDescriptor {
fn from(format: PlanarFormat) -> Self {
format.descriptor()
}
}
#[derive(Clone)]
pub struct PlanarTexture {
pub planes: [u64; 3],
pub plane_count: usize,
pub descriptor: PlanarDescriptor,
pub y: u64,
pub uv: u64,
pub width: i32,
pub height: i32,
pub transform: YuvTransform,
pub color_primaries: i32,
pub color_trc: i32,
pub ctx: Arc<dyn GpuContextLike>,
#[allow(dead_code)]
y_lease: Arc<GpuLease>,
#[allow(dead_code)]
uv_lease: Option<Arc<GpuLease>>,
#[allow(dead_code)]
v_lease: Option<Arc<GpuLease>>,
#[allow(dead_code)]
pub keep_alive: Option<Arc<dyn std::any::Any + Send + Sync>>,
}
impl PlanarTexture {
pub fn new(
ctx: Arc<dyn GpuContextLike>,
format: PlanarFormat,
size: (i32, i32),
planes: (u64, u64),
transform: YuvTransform,
color: (i32, i32),
) -> Self {
let (y, uv) = planes;
let descriptor = format.descriptor();
Self::build(ctx, descriptor, size, vec![y, uv], transform, color)
.expect("a legacy planar format always has two planes")
}
pub fn from_descriptor(
ctx: Arc<dyn GpuContextLike>,
descriptor: PlanarDescriptor,
size: (i32, i32),
planes: &[u64],
transform: YuvTransform,
color: (i32, i32),
) -> Result<Self> {
if planes.len() != descriptor.plane_count() || planes.is_empty() || planes.len() > 3 {
return Err(Error::Invalid);
}
if size.0 <= 0 || size.1 <= 0 {
return Err(Error::Invalid);
}
Self::build(ctx, descriptor, size, planes.to_vec(), transform, color)
}
pub fn from_descriptor_borrowed(
ctx: Arc<dyn GpuContextLike>,
descriptor: PlanarDescriptor,
size: (i32, i32),
planes: &[u64],
transform: YuvTransform,
color: (i32, i32),
keep_alive: Arc<dyn std::any::Any + Send + Sync>,
) -> Result<Self> {
if planes.len() != descriptor.plane_count() || planes.is_empty() || planes.len() > 3 {
return Err(Error::Invalid);
}
if size.0 <= 0 || size.1 <= 0 {
return Err(Error::Invalid);
}
let (width, height) = size;
let (color_primaries, color_trc) = color;
let count = planes.len();
let mut tokens = [0u64; 3];
tokens[..count].copy_from_slice(planes);
let y_lease = GpuLease::borrowed(ctx.clone(), tokens[0]);
let uv_lease = (count > 1).then(|| GpuLease::borrowed(ctx.clone(), tokens[1]));
let v_lease = (count > 2).then(|| GpuLease::borrowed(ctx.clone(), tokens[2]));
let y = tokens[0];
let uv = if count > 1 { tokens[1] } else { tokens[0] };
Ok(Self {
planes: tokens,
plane_count: count,
descriptor,
y,
uv,
width,
height,
transform,
color_primaries,
color_trc,
y_lease,
uv_lease,
v_lease,
keep_alive: Some(keep_alive),
ctx,
})
}
fn build(
ctx: Arc<dyn GpuContextLike>,
descriptor: PlanarDescriptor,
size: (i32, i32),
planes: Vec<u64>,
transform: YuvTransform,
color: (i32, i32),
) -> Result<Self> {
if planes.len() != descriptor.plane_count() || planes.is_empty() {
return Err(Error::Invalid);
}
let (width, height) = size;
let (color_primaries, color_trc) = color;
let count = planes.len();
let mut tokens = [0u64; 3];
tokens[..count].copy_from_slice(&planes);
let y_lease = GpuLease::new(ctx.clone(), tokens[0]);
let uv_lease = (count > 1).then(|| GpuLease::new(ctx.clone(), tokens[1]));
let v_lease = (count > 2).then(|| GpuLease::new(ctx.clone(), tokens[2]));
let y = tokens[0];
let uv = if count > 1 { tokens[1] } else { tokens[0] };
Ok(Self {
planes: tokens,
plane_count: count,
descriptor,
y,
uv,
width,
height,
transform,
color_primaries,
color_trc,
y_lease,
uv_lease,
v_lease,
keep_alive: None,
ctx,
})
}
pub fn plane_tokens(&self) -> &[u64] {
&self.planes[..self.plane_count]
}
pub fn uv_size(&self) -> (i32, i32) {
self.descriptor.chroma_size(self.width, self.height)
}
pub fn uv_scale(&self) -> (f32, f32) {
self.descriptor.uv_scale()
}
pub fn bit_depth(&self) -> u32 {
self.descriptor.bit_depth()
}
pub fn legacy_format(&self) -> Option<PlanarFormat> {
self.descriptor.legacy_format()
}
pub fn set_keep_alive(&mut self, guard: Arc<dyn std::any::Any + Send + Sync>) {
self.keep_alive = Some(guard);
}
}
impl std::fmt::Debug for PlanarTexture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PlanarTexture")
.field("planes", &self.plane_tokens())
.field("descriptor", &self.descriptor)
.field("width", &self.width)
.field("height", &self.height)
.finish_non_exhaustive()
}
}
#[derive(Clone)]
pub enum Texture {
Gpu {
token: u64,
backend: BackendKind,
width: i32,
height: i32,
format: PixelFormat,
ctx: Arc<dyn GpuContextLike>,
lease: Arc<GpuLease>,
timestamp: Rational,
},
Planar(PlanarTexture),
Cpu(Frame),
}
impl std::fmt::Debug for Texture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Texture::Gpu {
token,
backend,
width,
height,
format,
..
} => f
.debug_struct("Texture::Gpu")
.field("token", token)
.field("backend", backend)
.field("width", width)
.field("height", height)
.field("format", format)
.finish(),
Texture::Planar(p) => f.debug_tuple("Texture::Planar").field(p).finish(),
Texture::Cpu(frame) => f.debug_tuple("Texture::Cpu").field(frame).finish(),
}
}
}
impl Texture {
pub fn gpu(
ctx: Arc<dyn GpuContextLike>,
token: u64,
width: i32,
height: i32,
format: PixelFormat,
) -> Self {
let lease = GpuLease::new(ctx.clone(), token);
Texture::Gpu {
token,
backend: ctx.kind(),
width,
height,
format,
ctx,
lease,
timestamp: Rational::default(),
}
}
pub fn with_timestamp(mut self, timestamp: Rational) -> Self {
match &mut self {
Texture::Gpu { timestamp: current, .. } => *current = timestamp,
Texture::Cpu(frame) => frame.timestamp = timestamp,
Texture::Planar(_) => {}
}
self
}
pub fn dummy() -> Self {
Texture::Cpu(Frame::dummy())
}
pub fn is_dummy(&self) -> bool {
match self {
Texture::Gpu { .. } | Texture::Planar(_) => false,
Texture::Cpu(f) => f.is_dummy(),
}
}
pub fn wrap_frame(frame: Frame) -> Self {
Texture::Cpu(frame)
}
pub fn wrap_planar(planar: PlanarTexture) -> Self {
Texture::Planar(planar)
}
pub fn as_planar(&self) -> Option<&PlanarTexture> {
match self {
Texture::Planar(p) => Some(p),
_ => None,
}
}
pub fn is_planar(&self) -> bool {
matches!(self, Texture::Planar(_))
}
pub fn to_frame(&self) -> Result<Frame> {
match self {
Texture::Cpu(f) => Ok(f.clone()),
Texture::Gpu {
token,
ctx,
timestamp,
..
} => {
let mut frame = ctx.download(*token)?;
frame.timestamp = *timestamp;
Ok(frame)
}
Texture::Planar(_) => Err(Error::State),
}
}
pub fn to_frame_async(&self) -> Result<crate::backend::FrameReadback> {
match self {
Texture::Gpu { token, ctx, .. } => ctx.download_async(*token),
Texture::Cpu(_) => Err(Error::State),
Texture::Planar(_) => Err(Error::State),
}
}
pub fn size(&self) -> (i32, i32) {
match self {
Texture::Gpu { width, height, .. } => (*width, *height),
Texture::Planar(p) => (p.width, p.height),
Texture::Cpu(f) => (f.width, f.height),
}
}
pub fn format(&self) -> PixelFormat {
match self {
Texture::Gpu { format, .. } => *format,
Texture::Planar(_) => PixelFormat::F32,
Texture::Cpu(f) => f.format,
}
}
pub fn backend(&self) -> BackendKind {
match self {
Texture::Gpu { backend, .. } => *backend,
Texture::Planar(p) => p.ctx.kind(),
Texture::Cpu(_) => BackendKind::Cpu,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frame_allocate_and_linesize() {
let mut f = Frame::new();
let mut p = VideoParamsPod::default();
p.width = 4;
p.height = 3;
f.set_video_params(p);
assert_eq!(f.width, 4);
assert_eq!(f.height, 3);
assert_eq!(f.channels, 4);
assert_eq!(f.linesize_bytes(), 4 * 4 * 4);
assert!(f.allocate());
assert_eq!(f.data.len(), 4 * 3 * 4 * 4);
assert!(f.data.iter().all(|&b| b == 0));
}
#[test]
fn allocate_rejects_invalid() {
let mut f = Frame::new();
assert!(!f.allocate());
f.width = 0;
f.height = 10;
f.channels = 4;
f.format = PixelFormat::F32;
assert!(!f.allocate());
}
#[test]
fn dummy_frame_semantics() {
let d = Frame::dummy();
assert!(d.is_dummy());
assert_eq!(d.width, 0);
let t = Texture::dummy();
assert!(t.is_dummy());
assert_eq!(t.size(), (0, 0));
assert_eq!(t.backend(), BackendKind::Cpu);
}
#[test]
fn video_params_roundtrip_and_pointers() {
let mut f = Frame::new();
let mut p = VideoParamsPod::default();
p.width = 6;
p.height = 4;
p.divider = 2;
p.pixel_aspect_num = 2;
p.pixel_aspect_den = 1;
f.set_video_params(p);
assert_eq!(f.width, 3);
assert_eq!(f.height, 2);
let pod = f.video_params();
assert_eq!(pod.width, 3);
assert_eq!(pod.pixel_aspect_num, 2);
assert!(f.is_float());
assert_eq!(f.pixel_count(), 6);
f.allocate();
assert!(!f.data().is_null());
assert!(!f.data_mut().is_null());
assert!(f.timestamp.is_null());
}
#[test]
fn wrap_and_to_frame_roundtrip() {
let mut f = Frame::new();
let mut p = VideoParamsPod::default();
p.width = 2;
p.height = 2;
f.set_video_params(p);
f.allocate();
f.data[0] = 0xAB;
let t = Texture::wrap_frame(f.clone());
assert_eq!(t.to_frame().unwrap(), f);
}
#[test]
fn planar_descriptor_matrix_geometry_and_formats() {
use wgpu::TextureFormat as F;
let d = PlanarDescriptor::yuv_planar(Subsampling::Cs420, PlaneStorage::U8);
assert_eq!(d.plane_count(), 3);
assert_eq!(d.plane_size(0, 6, 4), Some((6, 4)));
assert_eq!(d.plane_size(1, 6, 4), Some((3, 2)));
assert_eq!(d.plane_size(2, 6, 4), Some((3, 2)));
assert_eq!(d.plane_size(3, 6, 4), None);
for plane in 0..3 {
assert_eq!(d.plane_texture_format(plane), Some(F::R8Unorm));
}
assert_eq!(d.uv_scale(), (0.5, 0.5));
assert_eq!(d.bit_depth(), 8);
assert_eq!(d.plane_source_components(1), Some(1));
let d = PlanarDescriptor::yuv_planar(Subsampling::Cs422, PlaneStorage::U16);
assert_eq!(d.plane_size(0, 5, 3), Some((5, 3)));
assert_eq!(d.plane_size(1, 5, 3), Some((3, 3)));
assert_eq!(d.plane_size(2, 5, 3), Some((3, 3)));
assert_eq!(d.plane_texture_format(0), Some(F::R16Unorm));
assert_eq!(d.uv_scale(), (0.5, 1.0));
assert_eq!(d.bit_depth(), 16);
assert_eq!(d.storage.bytes_per_component(), 2);
let d = PlanarDescriptor::yuv_planar(Subsampling::Cs444, PlaneStorage::F16);
assert_eq!(d.plane_size(2, 9, 7), Some((9, 7)));
assert_eq!(d.plane_texture_format(1), Some(F::R16Float));
assert_eq!(d.uv_scale(), (1.0, 1.0));
let d = PlanarDescriptor::yuv_planar(Subsampling::Cs444, PlaneStorage::F32);
assert_eq!(d.plane_texture_format(0), Some(F::R32Float));
let d = PlanarDescriptor::yuv_semi_planar(Subsampling::Cs422, PlaneStorage::U16);
assert_eq!(d.plane_count(), 2);
assert_eq!(d.plane_size(1, 6, 3), Some((3, 3)));
assert_eq!(d.plane_texture_format(0), Some(F::R16Unorm));
assert_eq!(d.plane_texture_format(1), Some(F::Rg16Unorm));
assert_eq!(d.plane_source_components(1), Some(2));
assert_eq!(d.plane_texture_components(1), Some(2));
let d = PlanarDescriptor::yuv_semi_planar(Subsampling::Cs420, PlaneStorage::U8);
assert_eq!(d.plane_texture_format(1), Some(F::Rg8Unorm));
let d = PlanarDescriptor::yuv_semi_planar(Subsampling::Cs420, PlaneStorage::F16);
assert_eq!(d.plane_texture_format(0), Some(F::R16Float));
assert_eq!(d.plane_texture_format(1), Some(F::Rg16Float));
let d = PlanarDescriptor::rgb(PlaneStorage::U8);
assert_eq!(d.plane_count(), 1);
assert_eq!(d.plane_size(0, 5, 3), Some((5, 3)));
assert_eq!(d.plane_source_components(0), Some(3));
assert_eq!(d.plane_texture_components(0), Some(4));
assert_eq!(d.plane_texture_format(0), Some(F::Rgba8Unorm));
assert_eq!(d.uv_scale(), (1.0, 1.0));
let d = PlanarDescriptor::rgba(PlaneStorage::F32);
assert_eq!(d.plane_source_components(0), Some(4));
assert_eq!(d.plane_texture_format(0), Some(F::Rgba32Float));
let d = PlanarDescriptor::rgba(PlaneStorage::U16);
assert_eq!(d.plane_texture_format(0), Some(F::Rgba16Unorm));
for storage in [
PlaneStorage::U8,
PlaneStorage::U16,
PlaneStorage::F16,
PlaneStorage::F32,
] {
for components in [1usize, 2, 4] {
let format = texture_format_for(storage, components);
assert_eq!(
PlaneStorage::from_texture_format(format),
Some(storage),
"{format:?}"
);
}
}
assert_eq!(PlanarDescriptor::rgba(PlaneStorage::U8).name(), "rgba-u8");
assert_eq!(
PlanarDescriptor::yuv_planar(Subsampling::Cs422, PlaneStorage::U16).name(),
"yuv-planar-422-u16"
);
}
#[test]
fn subsampling_inference_from_dims() {
assert_eq!(
Subsampling::from_chroma_size(3, 2, 6, 4),
Some(Subsampling::Cs420)
);
assert_eq!(
Subsampling::from_chroma_size(3, 4, 6, 4),
Some(Subsampling::Cs422)
);
assert_eq!(
Subsampling::from_chroma_size(6, 4, 6, 4),
Some(Subsampling::Cs444)
);
assert_eq!(
Subsampling::from_chroma_size(3, 2, 5, 3),
Some(Subsampling::Cs420)
);
assert_eq!(
Subsampling::from_chroma_size(3, 3, 5, 3),
Some(Subsampling::Cs422)
);
assert_eq!(Subsampling::from_chroma_size(2, 2, 6, 4), None);
}
#[test]
fn planar_format_legacy_adapters() {
use wgpu::TextureFormat as F;
assert_eq!(
PlanarFormat::Nv12.descriptor(),
PlanarDescriptor::yuv_semi_planar(Subsampling::Cs420, PlaneStorage::U8)
);
assert_eq!(PlanarFormat::Nv12.y_format(), F::R8Unorm);
assert_eq!(PlanarFormat::Nv12.uv_format(), F::Rg8Unorm);
assert_eq!(PlanarFormat::Nv12.bit_depth(), 8);
assert_eq!(PlanarFormat::Nv12.chroma_size(6, 4), (3, 2));
assert_eq!(PlanarFormat::P010.y_format(), F::R16Unorm);
assert_eq!(PlanarFormat::P010.uv_format(), F::Rg16Unorm);
assert_eq!(PlanarFormat::P010.bit_depth(), 16);
assert_eq!(PlanarFormat::P010.chroma_size(5, 3), (3, 2));
assert_eq!(PlanarFormat::Yuv422P10.y_format(), F::R16Unorm);
assert_eq!(PlanarFormat::Yuv422P10.uv_format(), F::Rg16Unorm);
assert_eq!(PlanarFormat::Yuv422P10.bit_depth(), 16);
assert_eq!(PlanarFormat::Yuv422P10.name(), "yuv422p10");
assert_eq!(PlanarFormat::Yuv422P10.uv_scale(), (0.5, 1.0));
assert_eq!(PlanarFormat::Yuv422P10.chroma_size(5, 3), (3, 3));
assert!(PlanarFormat::Yuv422P10.is_422());
assert!(!PlanarFormat::Nv12.is_422());
assert_eq!(
PlanarDescriptor::from(PlanarFormat::Yuv422P10),
PlanarFormat::Yuv422P10.descriptor()
);
assert_eq!(
PlanarFormat::Nv12.descriptor().legacy_format(),
Some(PlanarFormat::Nv12)
);
assert_eq!(
PlanarFormat::Yuv422P10.descriptor().legacy_format(),
Some(PlanarFormat::Yuv422P10)
);
assert_eq!(
PlanarDescriptor::yuv_planar(Subsampling::Cs444, PlaneStorage::U8).legacy_format(),
None
);
assert_eq!(
PlanarDescriptor::rgb(PlaneStorage::U8).legacy_format(),
None
);
}
#[derive(Default)]
struct CountingCtx {
destroyed: std::sync::Mutex<Vec<u64>>,
}
impl GpuContextLike for CountingCtx {
fn kind(&self) -> BackendKind {
BackendKind::Cpu
}
fn destroy_texture(&self, token: u64) {
self.destroyed.lock().unwrap().push(token);
}
fn upload(&self, _token: u64, _frame: &Frame) -> crate::error::Result<()> {
Err(crate::error::Error::State)
}
fn download(&self, _token: u64) -> crate::error::Result<Frame> {
Err(crate::error::Error::State)
}
fn blit(
&self,
_src: u64,
_dst: u64,
_processor: Option<&crate::color::ColorProcessor>,
) -> crate::error::Result<()> {
Err(crate::error::Error::State)
}
}
#[test]
fn planar_texture_descriptor_planes_and_leases() {
let counting = Arc::new(CountingCtx::default());
let ctx: Arc<dyn GpuContextLike> = counting.clone();
let two = PlanarTexture::new(
ctx.clone(),
PlanarFormat::Nv12,
(6, 4),
(11, 12),
YuvTransform::bt709_limited(),
(1, 1),
);
assert_eq!(two.plane_count, 2);
assert_eq!(two.plane_tokens(), &[11, 12]);
assert_eq!(two.y, 11);
assert_eq!(two.uv, 12);
assert_eq!(two.uv_size(), (3, 2));
assert_eq!(two.uv_scale(), (0.5, 0.5));
assert_eq!(two.bit_depth(), 8);
assert_eq!(two.legacy_format(), Some(PlanarFormat::Nv12));
let three = PlanarTexture::from_descriptor(
ctx.clone(),
PlanarDescriptor::yuv_planar(Subsampling::Cs444, PlaneStorage::F32),
(6, 4),
&[21, 22, 23],
YuvTransform::bt709_full(),
(1, 1),
)
.unwrap();
assert_eq!(three.plane_count, 3);
assert_eq!(three.plane_tokens(), &[21, 22, 23]);
assert_eq!(three.y, 21);
assert_eq!(three.uv, 22);
assert_eq!(three.uv_size(), (6, 4));
assert_eq!(three.uv_scale(), (1.0, 1.0));
assert_eq!(three.bit_depth(), 32);
assert_eq!(three.legacy_format(), None);
let rgb = PlanarTexture::from_descriptor(
ctx.clone(),
PlanarDescriptor::rgb(PlaneStorage::U8),
(5, 3),
&[31],
YuvTransform::bt709_limited(),
(1, 1),
)
.unwrap();
assert_eq!(rgb.plane_count, 1);
assert_eq!(rgb.plane_tokens(), &[31]);
assert_eq!(rgb.y, 31);
assert_eq!(rgb.uv, 31);
assert_eq!(rgb.uv_scale(), (1.0, 1.0));
assert!(PlanarTexture::from_descriptor(
ctx.clone(),
PlanarDescriptor::rgb(PlaneStorage::U8),
(2, 2),
&[1, 2],
YuvTransform::bt709_limited(),
(1, 1),
)
.is_err());
assert!(PlanarTexture::from_descriptor(
ctx.clone(),
PlanarDescriptor::yuv_planar(Subsampling::Cs420, PlaneStorage::U8),
(2, 2),
&[1, 2],
YuvTransform::bt709_limited(),
(1, 1),
)
.is_err());
assert!(PlanarTexture::from_descriptor(
ctx.clone(),
PlanarDescriptor::rgb(PlaneStorage::U8),
(0, 2),
&[1],
YuvTransform::bt709_limited(),
(1, 1),
)
.is_err());
drop(two);
drop(three);
drop(rgb);
let mut got = counting.destroyed.lock().unwrap().clone();
got.sort_unstable();
assert_eq!(got, vec![11, 12, 21, 22, 23, 31]);
}
}