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>,
}
impl GpuLease {
pub fn new(ctx: Arc<dyn GpuContextLike>, token: u64) -> Arc<Self> {
Arc::new(Self { token, ctx })
}
pub fn token(&self) -> u64 {
self.token
}
}
impl Drop for GpuLease {
fn drop(&mut self) {
self.ctx.destroy_texture(self.token);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PlanarFormat {
Nv12,
P010,
}
impl PlanarFormat {
pub fn y_format(self) -> wgpu::TextureFormat {
match self {
PlanarFormat::Nv12 => wgpu::TextureFormat::R8Unorm,
PlanarFormat::P010 => wgpu::TextureFormat::R16Unorm,
}
}
pub fn uv_format(self) -> wgpu::TextureFormat {
match self {
PlanarFormat::Nv12 => wgpu::TextureFormat::Rg8Unorm,
PlanarFormat::P010 => wgpu::TextureFormat::Rg16Unorm,
}
}
pub fn name(self) -> &'static str {
match self {
PlanarFormat::Nv12 => "nv12",
PlanarFormat::P010 => "p010",
}
}
pub fn bit_depth(self) -> u32 {
match self {
PlanarFormat::Nv12 => 8,
PlanarFormat::P010 => 16,
}
}
}
#[derive(Clone)]
pub struct PlanarTexture {
pub y: u64,
pub uv: u64,
pub format: PlanarFormat,
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: 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 (width, height) = size;
let (y, uv) = planes;
let (color_primaries, color_trc) = color;
Self {
y,
uv,
format,
width,
height,
transform,
color_primaries,
color_trc,
y_lease: GpuLease::new(ctx.clone(), y),
uv_lease: GpuLease::new(ctx.clone(), uv),
keep_alive: None,
ctx,
}
}
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("y", &self.y)
.field("uv", &self.uv)
.field("format", &self.format)
.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>,
},
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,
}
}
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, .. } => ctx.download(*token),
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);
}
}