use smallvec::smallvec;
use crate::allocator::create_and_fill_uniform_buffer;
use crate::renderer::{
DrawData, DrawError, DrawInstruction, DrawableCollectionViewInfo, Renderer,
screen_triangle_vertex_shader,
};
use crate::wgpu_resources::{
BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuBindGroupLayoutHandle,
GpuRenderPipelineHandle, GpuTexture, PipelineLayoutDesc, RenderPipelineDesc,
};
use crate::{DrawableCollector, RenderContext, include_shader_module};
#[expect(non_camel_case_types)]
#[derive(Clone, Copy, Debug)]
pub enum YuvPixelLayout {
Y_U_V444 = 0,
Y_U_V422 = 1,
Y_U_V420 = 2,
Y_UV420 = 100,
YUYV422 = 200,
Y400 = 300,
}
impl std::fmt::Display for YuvPixelLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Y_U_V444 => write!(f, "4:4:4 (planar)"),
Self::Y_U_V422 => write!(f, "4:2:2 (planar)"),
Self::Y_U_V420 => write!(f, "4:2:0 (planar)"),
Self::Y_UV420 => write!(f, "4:2:0 (semi-planar)"),
Self::YUYV422 => write!(f, "4:2:2 (interleaved"),
Self::Y400 => write!(f, "4:0:0"),
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum YuvMatrixCoefficients {
Identity = 0,
Bt601 = 1,
Bt709 = 2,
}
impl std::fmt::Display for YuvMatrixCoefficients {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Identity => write!(f, "identity"),
Self::Bt601 => write!(f, "BT.601"),
Self::Bt709 => write!(f, "BT.709"),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub enum YuvRange {
#[default]
Limited = 0,
Full = 1,
}
impl std::fmt::Display for YuvRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Limited => write!(f, "limited"),
Self::Full => write!(f, "full"),
}
}
}
impl YuvPixelLayout {
pub fn data_texture_width_height(&self, [decoded_width, decoded_height]: [u32; 2]) -> [u32; 2] {
match self {
Self::Y_U_V444 => [decoded_width, decoded_height * 3],
Self::Y_U_V422 => [decoded_width, decoded_height * 2],
Self::Y_U_V420 | Self::Y_UV420 => [decoded_width, decoded_height + decoded_height / 2],
Self::YUYV422 => [decoded_width * 2, decoded_height],
Self::Y400 => [decoded_width, decoded_height],
}
}
pub fn data_texture_format(&self) -> wgpu::TextureFormat {
#[expect(clippy::match_same_arms)]
match self {
Self::Y_U_V444 | Self::Y_U_V422 | Self::Y_U_V420 => wgpu::TextureFormat::R8Uint,
Self::Y_UV420 => wgpu::TextureFormat::R8Uint,
Self::YUYV422 => wgpu::TextureFormat::R8Uint,
Self::Y400 => wgpu::TextureFormat::R8Uint,
}
}
pub fn num_data_buffer_bytes(&self, decoded_width: [u32; 2]) -> usize {
let data_texture_width_height = self.data_texture_width_height(decoded_width);
let data_texture_format = self.data_texture_format();
(data_texture_format
.block_copy_size(None)
.expect("data texture formats are expected to be trivial")
* data_texture_width_height[0]
* data_texture_width_height[1]) as usize
}
}
mod gpu_data {
use crate::wgpu_buffer_types;
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct UniformBuffer {
pub yuv_layout: u32,
pub yuv_matrix_coefficients: u32,
pub target_texture_size: [u32; 2],
pub yuv_range: wgpu_buffer_types::U32RowPadded,
pub _end_padding: [wgpu_buffer_types::PaddingRow; 16 - 2],
}
}
pub struct YuvFormatConversionTask {
bind_group: GpuBindGroup,
target_texture: GpuTexture,
}
impl DrawData for YuvFormatConversionTask {
type Renderer = YuvFormatConverter;
fn collect_drawables(
&self,
_view_info: &DrawableCollectionViewInfo,
_collector: &mut DrawableCollector<'_>,
) {
}
}
impl YuvFormatConversionTask {
pub const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
pub const REQUIRED_TARGET_TEXTURE_USAGE_FLAGS: wgpu::TextureUsages =
wgpu::TextureUsages::RENDER_ATTACHMENT;
pub fn new(
ctx: &RenderContext,
yuv_layout: YuvPixelLayout,
yuv_range: YuvRange,
yuv_matrix_coefficients: YuvMatrixCoefficients,
input_data: &GpuTexture,
target_texture: &GpuTexture,
) -> Self {
let target_label = target_texture.creation_desc.label.clone();
let renderer = ctx.renderer::<YuvFormatConverter>();
let uniform_buffer = create_and_fill_uniform_buffer(
ctx,
format!("{target_label}_conversion").into(),
gpu_data::UniformBuffer {
yuv_layout: yuv_layout as _,
yuv_matrix_coefficients: yuv_matrix_coefficients as _,
target_texture_size: [
target_texture.creation_desc.size.width,
target_texture.creation_desc.size.height,
],
yuv_range: (yuv_range as u32).into(),
_end_padding: Default::default(),
},
);
let bind_group = ctx.gpu_resources.bind_groups.alloc(
&ctx.device,
&ctx.gpu_resources,
&BindGroupDesc {
label: "RectangleInstance::bind_group".into(),
entries: smallvec![
uniform_buffer,
BindGroupEntry::DefaultTextureView(input_data.handle),
],
layout: renderer.bind_group_layout,
},
);
Self {
bind_group,
target_texture: target_texture.clone(),
}
}
pub fn convert_input_data_to_texture(self, ctx: &RenderContext) -> Result<(), DrawError> {
let mut encoder = ctx.active_frame.before_view_builder_encoder.lock();
let mut pass = encoder
.get()
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some(self.target_texture.creation_desc.label.get()),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.target_texture.default_view,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
ctx.renderer::<YuvFormatConverter>().draw(
&ctx.gpu_resources.render_pipelines.resources(),
crate::draw_phases::DrawPhase::Opaque, &mut pass,
&[DrawInstruction {
draw_data: &self,
drawables: &[],
}],
)
}
}
pub struct YuvFormatConverter {
render_pipeline: GpuRenderPipelineHandle,
bind_group_layout: GpuBindGroupLayoutHandle,
}
impl Renderer for YuvFormatConverter {
type RendererDrawData = YuvFormatConversionTask;
fn create_renderer(ctx: &RenderContext) -> Self {
let vertex_handle = screen_triangle_vertex_shader(ctx);
let bind_group_layout = ctx.gpu_resources.bind_group_layouts.get_or_create(
&ctx.device,
&BindGroupLayoutDesc {
label: "YuvFormatConverter".into(),
entries: vec![
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: (std::mem::size_of::<gpu_data::UniformBuffer>()
as u64)
.try_into()
.ok(),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Uint,
},
count: None,
},
],
},
);
let pipeline_layout = ctx.gpu_resources.pipeline_layouts.get_or_create(
ctx,
&PipelineLayoutDesc {
label: "YuvFormatConverter".into(),
entries: vec![bind_group_layout],
},
);
let shader_modules = &ctx.gpu_resources.shader_modules;
let render_pipeline = ctx.gpu_resources.render_pipelines.get_or_create(
ctx,
&RenderPipelineDesc {
label: "TestTriangle::render_pipeline".into(),
pipeline_layout,
vertex_entrypoint: "main".into(),
vertex_handle,
fragment_entrypoint: "fs_main".into(),
fragment_handle: shader_modules.get_or_create(
ctx,
&include_shader_module!("../../shader/conversions/yuv_converter.wgsl"),
),
vertex_buffers: smallvec![],
render_targets: smallvec![Some(YuvFormatConversionTask::OUTPUT_FORMAT.into())],
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
},
);
Self {
render_pipeline,
bind_group_layout,
}
}
fn draw(
&self,
render_pipelines: &crate::wgpu_resources::GpuRenderPipelinePoolAccessor<'_>,
_phase: crate::draw_phases::DrawPhase,
pass: &mut wgpu::RenderPass<'_>,
draw_instructions: &[DrawInstruction<'_, Self::RendererDrawData>],
) -> Result<(), DrawError> {
let pipeline = render_pipelines.get(self.render_pipeline)?;
pass.set_pipeline(pipeline);
for DrawInstruction { draw_data, .. } in draw_instructions {
pass.set_bind_group(0, &draw_data.bind_group, &[]);
pass.draw(0..3, 0..1);
}
Ok(())
}
}