#[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"
);
}