vk-video 0.2.1

A library for hardware video coding using Vulkan Video, with wgpu integration.
Documentation
#[cfg(vulkan)]
fn main() {
    use std::{io::Write, num::NonZeroU32};
    use vk_video::{
        Frame, VulkanInstance,
        parameters::{RateControl, VideoParameters},
    };

    let subscriber = tracing_subscriber::FmtSubscriber::builder()
        .with_max_level(tracing::Level::INFO)
        .finish();

    tracing::subscriber::set_global_default(subscriber).expect("Failed to initialize tracing");

    let args = std::env::args().collect::<Vec<_>>();

    if args.len() != 4 {
        println!("usage: {} WIDTH HEIGHT FRAME_COUNT", args[0]);
        return;
    }

    let width = args[1].parse::<NonZeroU32>().expect("parse width");
    let height = args[2].parse::<NonZeroU32>().expect("parse height");
    let frame_count = args[3].parse::<u32>().expect("parse frame count");

    let vulkan_instance = VulkanInstance::new().unwrap();
    let vulkan_adapter = vulkan_instance.create_adapter(None).unwrap();
    let vulkan_device = vulkan_adapter
        .create_device(
            wgpu::Features::IMMEDIATES,
            wgpu::ExperimentalFeatures::disabled(),
            wgpu::Limits {
                max_immediate_size: 4,
                ..Default::default()
            },
        )
        .unwrap();

    let wgpu_state = WgpuState::new(
        vulkan_device.wgpu_device(),
        vulkan_device.wgpu_queue(),
        width,
        height,
    );

    let mut encoder = vulkan_device
        .create_wgpu_textures_encoder(
            vulkan_device
                .encoder_parameters_high_quality(
                    VideoParameters {
                        width,
                        height,
                        target_framerate: 30.into(),
                    },
                    RateControl::VariableBitrate {
                        average_bitrate: 500_000,
                        max_bitrate: 2_000_000,
                        virtual_buffer_size: std::time::Duration::from_secs(2),
                    },
                )
                .unwrap(),
        )
        .unwrap();

    let mut output_file = std::fs::File::create("output.h264").unwrap();

    for i in 0..frame_count {
        let time = 1.0 / 30.0 * i as f32;
        wgpu_state.render(time);

        let res = unsafe {
            encoder
                .encode(
                    Frame {
                        data: wgpu_state.texture.clone(),
                        pts: None,
                    },
                    false,
                )
                .unwrap()
        };

        output_file.write_all(&res.data).unwrap();
    }
}

#[cfg(vulkan)]
struct WgpuState {
    texture: wgpu::Texture,
    y_renderer: PlaneRenderer,
    uv_renderer: PlaneRenderer,
    device: wgpu::Device,
    queue: wgpu::Queue,
}

#[cfg(vulkan)]
impl WgpuState {
    fn new(
        device: wgpu::Device,
        queue: wgpu::Queue,
        width: std::num::NonZeroU32,
        height: std::num::NonZeroU32,
    ) -> WgpuState {
        let shader = wgpu::include_wgsl!("encode_wgpu.wgsl");
        let shader = device.create_shader_module(shader);

        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("wgpu pipeline layout"),
            bind_group_layouts: &[],
            immediate_size: 4,
        });

        let texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("wgpu render target"),
            format: wgpu::TextureFormat::NV12,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
            dimension: wgpu::TextureDimension::D2,
            sample_count: 1,
            view_formats: &[],
            mip_level_count: 1,
            size: wgpu::Extent3d {
                width: width.get(),
                height: height.get(),
                depth_or_array_layers: 1,
            },
        });

        let y_renderer = PlaneRenderer::new(
            &device,
            &pipeline_layout,
            &shader,
            "fs_main_y",
            &texture,
            wgpu::TextureAspect::Plane0,
        );
        let uv_renderer = PlaneRenderer::new(
            &device,
            &pipeline_layout,
            &shader,
            "fs_main_uv",
            &texture,
            wgpu::TextureAspect::Plane1,
        );

        WgpuState {
            texture,
            y_renderer,
            uv_renderer,
            device,
            queue,
        }
    }

    fn render(&self, time: f32) {
        let mut encoder = self
            .device
            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("wgpu encoder"),
            });

        self.y_renderer.render(&mut encoder, time);
        self.uv_renderer.render(&mut encoder, time);

        encoder.transition_resources(
            [].into_iter(),
            [wgpu::TextureTransition {
                texture: &self.texture,
                state: wgpu::TextureUses::COPY_SRC,
                selector: None,
            }]
            .into_iter(),
        );

        let buffer = encoder.finish();

        self.queue.submit([buffer]);
    }
}

#[cfg(vulkan)]
struct PlaneRenderer {
    pipeline: wgpu::RenderPipeline,
    plane: wgpu::TextureAspect,
    plane_view: wgpu::TextureView,
}

#[cfg(vulkan)]
impl PlaneRenderer {
    fn new(
        device: &wgpu::Device,
        pipeline_layout: &wgpu::PipelineLayout,
        shader: &wgpu::ShaderModule,
        fragment_entry_point: &str,
        texture: &wgpu::Texture,
        plane: wgpu::TextureAspect,
    ) -> Self {
        let format = texture.format().aspect_specific_format(plane).unwrap();
        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("wgpu pipeline"),
            layout: Some(pipeline_layout),
            cache: None,
            vertex: wgpu::VertexState {
                module: shader,
                buffers: &[],
                entry_point: None,
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: shader,
                entry_point: Some(fragment_entry_point),
                compilation_options: Default::default(),
                targets: &[Some(wgpu::ColorTargetState {
                    blend: None,
                    format,
                    write_mask: wgpu::ColorWrites::ALL,
                })],
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                cull_mode: Some(wgpu::Face::Back),
                polygon_mode: wgpu::PolygonMode::Fill,
                front_face: wgpu::FrontFace::Ccw,
                conservative: false,
                unclipped_depth: false,
                strip_index_format: None,
            },
            multiview_mask: None,
            multisample: wgpu::MultisampleState {
                count: 1,
                mask: !0,
                alpha_to_coverage_enabled: false,
            },
            depth_stencil: None,
        });

        let plane_view = texture.create_view(&wgpu::TextureViewDescriptor {
            label: Some("wgpu render target plane view"),
            aspect: plane,
            usage: Some(wgpu::TextureUsages::RENDER_ATTACHMENT),
            ..Default::default()
        });

        Self {
            pipeline,
            plane,
            plane_view,
        }
    }

    fn render(&self, encoder: &mut wgpu::CommandEncoder, time: f32) {
        let clear_color = match self.plane {
            wgpu::TextureAspect::Plane0 => wgpu::Color::BLACK,
            wgpu::TextureAspect::Plane1 => wgpu::Color {
                r: 0.5,
                g: 0.5,
                b: 0.0,
                a: 1.0,
            },
            _ => unreachable!(),
        };

        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: Some("wgpu render pass"),
            timestamp_writes: None,
            occlusion_query_set: None,
            depth_stencil_attachment: None,
            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                view: &self.plane_view,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Clear(clear_color),
                    store: wgpu::StoreOp::Store,
                },
                resolve_target: None,
                depth_slice: None,
            })],
            multiview_mask: None,
        });

        render_pass.set_pipeline(&self.pipeline);
        render_pass.set_immediates(0, &time.to_ne_bytes());
        render_pass.draw(0..3, 0..1);
    }
}

#[cfg(not(vulkan))]
fn main() {
    println!(
        "This crate doesn't work on your operating system, because it does not support vulkan"
    );
}