use crate::{Color, Error, Frame, Size, Surface};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Layout {
#[cfg(target_os = "macos")]
Nv12,
I420,
}
pub(super) struct Source {
pub layout: Layout,
pub color: Color,
pub plane0: wgpu::TextureView,
pub plane1: wgpu::TextureView,
pub plane2: wgpu::TextureView,
}
#[derive(Default)]
pub(super) struct Cache {
planes: Option<Planes>,
#[cfg(target_os = "macos")]
metal: Option<super::metal::Import>,
}
struct Planes {
size: Size,
y: wgpu::Texture,
u: wgpu::Texture,
v: wgpu::Texture,
views: [wgpu::TextureView; 3],
}
impl Cache {
pub fn import(&mut self, device: &wgpu::Device, surface: &Surface) -> Result<Option<Source>, Error> {
match surface {
#[cfg(target_os = "macos")]
Surface::PixelBuffer(buffer) => {
let metal = match &mut self.metal {
Some(metal) => metal,
None => self.metal.insert(super::metal::Import::new(device)?),
};
metal.import(device, buffer).map(Some)
}
_ => {
let _ = device;
Ok(None)
}
}
}
pub fn upload(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, frame: &Frame) -> Result<Source, Error> {
let i420 = frame.surface.to_i420()?;
let size = Size::new(i420.width(), i420.height());
size.validate("render source")?;
if self.planes.as_ref().is_none_or(|planes| planes.size != size) {
self.planes = Some(Planes::new(device, size));
}
let planes = self.planes.as_ref().expect("planes were just created");
let half = Size::new(size.width / 2, size.height / 2);
write(queue, &planes.y, size, i420.y());
write(queue, &planes.u, half, i420.u());
write(queue, &planes.v, half, i420.v());
let [plane0, plane1, plane2] = planes.views.clone();
Ok(Source {
layout: Layout::I420,
color: i420.color().unwrap_or_else(|| Color::infer(size)),
plane0,
plane1,
plane2,
})
}
}
impl Planes {
fn new(device: &wgpu::Device, size: Size) -> Self {
let plane = |label: &str, size: Size| {
device.create_texture(&wgpu::TextureDescriptor {
label: Some(label),
size: wgpu::Extent3d {
width: size.width,
height: size.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
})
};
let half = Size::new(size.width / 2, size.height / 2);
let (y, u, v) = (
plane("moq-video luma", size),
plane("moq-video chroma u", half),
plane("moq-video chroma v", half),
);
let views = [
y.create_view(&Default::default()),
u.create_view(&Default::default()),
v.create_view(&Default::default()),
];
Self { size, y, u, v, views }
}
}
fn write(queue: &wgpu::Queue, texture: &wgpu::Texture, size: Size, data: &[u8]) {
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(size.width),
rows_per_image: Some(size.height),
},
wgpu::Extent3d {
width: size.width,
height: size.height,
depth_or_array_layers: 1,
},
);
}