use soul_terminal_core::{Color, RenderCommand};
use wgpu::util::DeviceExt;
use crate::backend::{RenderBackend, RenderError};
use crate::glyph::GlyphPipeline;
use crate::quad::{QuadPipeline, QuadUniforms};
use crate::texture::TexturePipeline;
pub struct WgpuBackend {
device: wgpu::Device,
queue: wgpu::Queue,
surface: wgpu::Surface<'static>,
surface_config: wgpu::SurfaceConfiguration,
quad_pipeline: QuadPipeline,
glyph_pipeline: GlyphPipeline,
texture_pipeline: TexturePipeline,
width: u32,
height: u32,
scale_factor: f32,
current_texture: Option<wgpu::SurfaceTexture>,
}
impl WgpuBackend {
pub async fn new(
surface: wgpu::Surface<'static>,
adapter: &wgpu::Adapter,
width: u32,
height: u32,
scale_factor: f32,
) -> Result<Self, RenderError> {
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("soul_terminal_device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits()),
memory_hints: wgpu::MemoryHints::default(),
trace: wgpu::Trace::Off,
})
.await
.map_err(|e| RenderError::DeviceError(e.to_string()))?;
let surface_caps = surface.get_capabilities(adapter);
let format = surface_caps
.formats
.iter()
.find(|f| f.is_srgb())
.copied()
.unwrap_or(surface_caps.formats[0]);
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width,
height,
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &surface_config);
let quad_pipeline = QuadPipeline::new(&device, format);
let glyph_pipeline = GlyphPipeline::new(&device, &queue, format);
let texture_pipeline = TexturePipeline::new(&device);
Ok(Self {
device,
queue,
surface,
surface_config,
quad_pipeline,
glyph_pipeline,
texture_pipeline,
width,
height,
scale_factor,
current_texture: None,
})
}
fn process_command(&mut self, cmd: &RenderCommand) {
match cmd {
RenderCommand::FillRect {
rect,
color,
corner_radius,
} => {
self.quad_pipeline.push_rect(
rect.x,
rect.y,
rect.width,
rect.height,
color.to_array(),
*corner_radius,
);
}
RenderCommand::DrawBorder { rect, border } => {
let w = border.width;
let c = border.color.to_array();
let r = border.radius;
self.quad_pipeline
.push_rect(rect.x, rect.y, rect.width, w, c, r);
self.quad_pipeline.push_rect(
rect.x,
rect.y + rect.height - w,
rect.width,
w,
c,
r,
);
self.quad_pipeline
.push_rect(rect.x, rect.y, w, rect.height, c, 0.0);
self.quad_pipeline.push_rect(
rect.x + rect.width - w,
rect.y,
w,
rect.height,
c,
0.0,
);
}
RenderCommand::DrawText {
text,
x,
y,
color,
font_size,
bold,
italic,
monospace,
} => {
self.glyph_pipeline.push_text(
text,
*x,
*y,
color.to_array(),
*font_size,
*bold,
*italic,
*monospace,
);
}
RenderCommand::DrawShadow {
rect,
color,
offset_x,
offset_y,
blur_radius,
corner_radius,
} => {
let expand = *blur_radius;
self.quad_pipeline.push_rect(
rect.x + offset_x - expand,
rect.y + offset_y - expand,
rect.width + expand * 2.0,
rect.height + expand * 2.0,
color.to_array(),
corner_radius + expand,
);
}
RenderCommand::DrawImage {
rect: _,
texture_id: _,
} => {
}
RenderCommand::DrawLine {
x1,
y1,
x2,
y2,
color,
width,
} => {
let dx = x2 - x1;
let dy = y2 - y1;
let len = (dx * dx + dy * dy).sqrt();
if len > 0.0 {
if dy.abs() < 0.001 {
self.quad_pipeline
.push_rect(*x1, *y1 - width / 2.0, len, *width, color.to_array(), 0.0);
} else if dx.abs() < 0.001 {
self.quad_pipeline
.push_rect(*x1 - width / 2.0, *y1, *width, len, color.to_array(), 0.0);
} else {
self.quad_pipeline
.push_rect(*x1, *y1, dx.abs(), dy.abs(), color.to_array(), 0.0);
}
}
}
RenderCommand::PushClip { .. } | RenderCommand::PopClip => {
}
}
}
}
impl RenderBackend for WgpuBackend {
fn begin_frame(&mut self, _clear_color: Color) -> Result<(), RenderError> {
self.quad_pipeline.clear();
self.glyph_pipeline.clear();
let output = self
.surface
.get_current_texture()
.map_err(|e| RenderError::SurfaceError(e.to_string()))?;
self.current_texture = Some(output);
Ok(())
}
fn submit(&mut self, commands: &[RenderCommand]) -> Result<(), RenderError> {
for cmd in commands {
self.process_command(cmd);
}
Ok(())
}
fn present(&mut self) -> Result<(), RenderError> {
let output = self
.current_texture
.take()
.ok_or_else(|| RenderError::SurfaceError("no current texture".into()))?;
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
self.queue.write_buffer(
&self.quad_pipeline.uniform_buffer,
0,
bytemuck::cast_slice(&[QuadUniforms {
screen_size: [self.width as f32, self.height as f32],
_padding: [0.0; 2],
}]),
);
let vertex_buffer;
let index_buffer;
let has_quads = !self.quad_pipeline.vertices.is_empty();
if has_quads {
vertex_buffer =
self.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("quad_vertex_buffer"),
contents: bytemuck::cast_slice(&self.quad_pipeline.vertices),
usage: wgpu::BufferUsages::VERTEX,
});
index_buffer =
self.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("quad_index_buffer"),
contents: bytemuck::cast_slice(&self.quad_pipeline.indices),
usage: wgpu::BufferUsages::INDEX,
});
} else {
vertex_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: 4,
usage: wgpu::BufferUsages::VERTEX,
mapped_at_creation: false,
});
index_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: 4,
usage: wgpu::BufferUsages::INDEX,
mapped_at_creation: false,
});
}
self.glyph_pipeline
.prepare(&self.device, &self.queue, self.width, self.height);
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("soul_terminal_encoder"),
});
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("soul_terminal_render_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.04,
g: 0.04,
b: 0.06,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
if has_quads {
render_pass.set_pipeline(&self.quad_pipeline.pipeline);
render_pass.set_bind_group(0, &self.quad_pipeline.uniform_bind_group, &[]);
render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..self.quad_pipeline.indices.len() as u32, 0, 0..1);
}
self.glyph_pipeline.render(&mut render_pass);
}
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
self.glyph_pipeline.trim();
Ok(())
}
fn resize(&mut self, width: u32, height: u32) {
if width > 0 && height > 0 {
self.width = width;
self.height = height;
self.surface_config.width = width;
self.surface_config.height = height;
self.surface.configure(&self.device, &self.surface_config);
}
}
fn load_texture(&mut self, width: u32, height: u32, data: &[u8]) -> Result<u64, RenderError> {
self.texture_pipeline
.load(&self.device, &self.queue, width, height, data)
}
fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
fn scale_factor(&self) -> f32 {
self.scale_factor
}
}