use anyhow::{bail, Context, Result};
use sdl2::video::Window;
use tracing::info;
use wgpu::util::DeviceExt;
pub struct Dimensions {
pub width: usize,
pub height: usize,
}
#[derive(Clone, Debug)]
pub enum Orientation {
Vertical,
Horizontal,
}
impl std::fmt::Display for Orientation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Orientation::Vertical => write!(f, "vertical"),
Orientation::Horizontal => write!(f, "horizontal"),
}
}
}
impl std::str::FromStr for Orientation {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Orientation> {
match s {
"|" | "v" | "V" | "vert" | "Vert" | "vertical" | "Vertical" => {
Ok(Orientation::Vertical)
}
"-" | "h" | "H" | "horiz" | "Horiz" | "horizontal" | "Horizontal" => {
Ok(Orientation::Horizontal)
}
_ => bail!("unrecognized orientation: '{}'", s),
}
}
}
pub const COLOR_BYTES: usize = 4;
pub const VALUE_BLACK: u32 = 255 * 16777216;
const INDICES_RECT: &[u16] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const VERTICES_HORIZ: &[Vertex] = &[
Vertex {
position: [1.0, -1.0], tex_coords: [0.0, 1.0], },
Vertex {
position: [-1.0, -1.0], tex_coords: [0.0, 0.0], },
Vertex {
position: [1.0, -12. / 16.],
tex_coords: [1. / 32., 1.0],
},
Vertex {
position: [-1.0, -12. / 16.],
tex_coords: [1. / 32., 0.0],
},
Vertex {
position: [1.0, -6. / 16.],
tex_coords: [4. / 32., 1.0],
},
Vertex {
position: [-1.0, -6. / 16.],
tex_coords: [4. / 32., 0.0],
},
Vertex {
position: [1.0, 2. / 16.],
tex_coords: [12. / 32., 1.0],
},
Vertex {
position: [-1.0, 2. / 16.],
tex_coords: [12. / 32., 0.0],
},
Vertex {
position: [1.0, 1.0], tex_coords: [1.0, 1.0], },
Vertex {
position: [-1.0, 1.0], tex_coords: [1.0, 0.0], },
];
const VERTICES_VERT: &[Vertex] = &[
Vertex {
position: [-1.0, -1.0], tex_coords: [0.0, 0.0], },
Vertex {
position: [-1.0, 1.0], tex_coords: [0.0, 1.0], },
Vertex {
position: [-12. / 16., -1.0],
tex_coords: [1. / 32., 0.0],
},
Vertex {
position: [-12. / 16., 1.0],
tex_coords: [1. / 32., 1.0],
},
Vertex {
position: [-6. / 16., -1.0],
tex_coords: [4. / 32., 0.0],
},
Vertex {
position: [-6. / 16., 1.0],
tex_coords: [4. / 32., 1.0],
},
Vertex {
position: [2. / 16., -1.0],
tex_coords: [12. / 32., 0.0],
},
Vertex {
position: [2. / 16., 1.0],
tex_coords: [12. / 32., 1.0],
},
Vertex {
position: [1.0, -1.0], tex_coords: [1.0, 0.0], },
Vertex {
position: [1.0, 1.0], tex_coords: [1.0, 1.0], },
];
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
position: [f32; 2],
tex_coords: [f32; 2],
}
impl Vertex {
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
},
],
}
}
}
pub struct WgpuSizedState {
vertex_buffer: wgpu::Buffer,
texture: wgpu::Texture,
bind_group: wgpu::BindGroup,
}
impl WgpuSizedState {
pub fn new(
state: &WgpuState,
texture_dims: Dimensions,
orientation: &Orientation,
) -> WgpuSizedState {
let vertex_buffer = state
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("vertex_buffer"),
contents: bytemuck::cast_slice(match orientation {
Orientation::Vertical => VERTICES_VERT,
Orientation::Horizontal => VERTICES_HORIZ,
}),
usage: wgpu::BufferUsages::VERTEX,
});
let texture = state.device.create_texture(&wgpu::TextureDescriptor {
label: Some("buf_texture"),
size: wgpu::Extent3d {
width: texture_dims.width as u32,
height: texture_dims.height as u32,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: state.preferred_format,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[state.preferred_format],
});
let bind_group = state.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("bind_group"),
layout: &state.bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(
&texture.create_view(&wgpu::TextureViewDescriptor::default()),
),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&state.device.create_sampler(
&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
},
)),
},
],
});
WgpuSizedState {
vertex_buffer,
texture,
bind_group,
}
}
}
pub struct WgpuState {
surface: wgpu::Surface<'static>,
device: wgpu::Device,
queue: wgpu::Queue,
render_pipeline: wgpu::RenderPipeline,
bind_group_layout: wgpu::BindGroupLayout,
index_buffer: wgpu::Buffer,
pub preferred_format: wgpu::TextureFormat,
config: wgpu::SurfaceConfiguration,
}
impl WgpuState {
pub async fn new(window: &Window) -> Result<Self> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
let surface = unsafe {
let window = wgpu::SurfaceTargetUnsafe::from_window(&window)
.context("Couldn't cast SDL2 window as wgpu window")?;
instance
.create_surface_unsafe(window)
.context("Failed to create wgpu surface with SDL2 window")?
};
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: Some(&surface),
})
.await
.context("Failed to initialize graphics adapter. Bad driver? Reboot needed?")?;
let adapter_info = adapter.get_info();
info!(
"Rendering with {:?} to: {} (driver: {} {})",
adapter_info.backend, adapter_info.name, adapter_info.driver, adapter_info.driver_info
);
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("device-descriptor"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::Performance,
},
None, )
.await
.context("Failed to initialize graphics device. Bad driver? Reboot needed?")?;
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
label: Some("bind_group_layout"),
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("soundview-shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
});
let formats = surface.get_capabilities(&adapter).formats;
let preferred_format = *formats
.first()
.context("No formats found for graphics adapter")?;
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("render_pipeline"),
layout: Some(
&device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("render_pipeline_layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
}),
),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vertex_shader_main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
buffers: &[Vertex::desc()],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fragment_shader_main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format: preferred_format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent::REPLACE,
alpha: wgpu::BlendComponent::REPLACE,
}),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
strip_index_format: None,
front_face: wgpu::FrontFace::Cw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("index_buffer"),
contents: bytemuck::cast_slice(INDICES_RECT),
usage: wgpu::BufferUsages::INDEX,
});
let (width, height) = window.size();
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: preferred_format,
width,
height,
present_mode: wgpu::PresentMode::Fifo,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: (&[preferred_format]).to_vec(),
desired_maximum_frame_latency: 0,
};
surface.configure(&device, &config);
Ok(WgpuState {
surface,
device,
queue,
render_pipeline,
bind_group_layout,
index_buffer,
preferred_format,
config,
})
}
pub fn resize(&mut self, new_size: Option<Dimensions>) {
if let Some(new_size) = new_size {
if new_size.width > 0 && new_size.height > 0 {
self.config.width = new_size.width as u32;
self.config.height = new_size.height as u32;
}
}
self.surface.configure(&self.device, &self.config);
}
pub fn window_dims(&self) -> Dimensions {
Dimensions {
width: self.config.width as usize,
height: self.config.height as usize,
}
}
pub fn surface_texture(&mut self) -> Result<wgpu::SurfaceTexture, wgpu::SurfaceError> {
self.surface.get_current_texture()
}
pub fn write_texture(
&mut self,
sized_state: &WgpuSizedState,
buf: &[u8],
image_offset: usize,
image_dims: Dimensions,
extent_dims: Dimensions,
origin_y: usize,
) {
self.queue.write_texture(
wgpu::ImageCopyTexture {
aspect: wgpu::TextureAspect::All,
texture: &sized_state.texture,
mip_level: 0,
origin: match origin_y {
0 => wgpu::Origin3d::ZERO,
y => wgpu::Origin3d {
x: 0,
y: y as u32,
z: 0,
},
},
},
buf,
wgpu::ImageDataLayout {
offset: (COLOR_BYTES * image_offset) as u64,
bytes_per_row: Some((COLOR_BYTES * image_dims.width) as u32),
rows_per_image: Some(image_dims.height as u32),
},
wgpu::Extent3d {
width: extent_dims.width as u32,
height: extent_dims.height as u32,
depth_or_array_layers: 1,
},
);
}
pub fn render(
&mut self,
sized_state: &WgpuSizedState,
output: wgpu::SurfaceTexture,
) -> Result<()> {
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut command = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
{
let mut render_pass = command.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("render_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, &sized_state.bind_group, &[]);
render_pass.set_vertex_buffer(0, sized_state.vertex_buffer.slice(..));
render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
render_pass.draw_indexed(0..INDICES_RECT.len() as u32, 0, 0..1);
}
self.queue.submit(std::iter::once(command.finish()));
output.present();
Ok(())
}
}